Мобильное программирование приложений реального времени в стандарте POSIX

Одношаговое порождение процессов


При стандартизации средств одношагового порождения процессов преследовались две основные цели:

  • возможность использования на аппаратных конфигурациях без устройства управления памятью и без какого-либо специфического оборудования;
  • совместимость с существующими POSIX-стандартами.

В качестве дополнительных целей объявлены:

  • возможность эффективной реализации;
  • способность заменить двухшаговое порождение по крайней мере в половине типичных случаев;
  • системы, где имеется только одношаговое порождение процессов, должны быть практически полезны, по крайней мере для приложений реального времени;
  • на системах с двухшаговым порождением процессов должна быть возможна библиотечная реализация одношагового порождения.

Еще одна дополнительная цель – простота интерфейса. Семейство exec*() насчитывает шесть членов; для posix_spawn*() хватило двух с единым списком аргументов в духе execve() и небольшими отличиями в трактовке имени файла с образом нового процесса (напоминающими разницу между execve() и execvp()).

Более точно: для одношагового порождения процессов служат функции posix_spawn() и posix_spawnp() (см. листинг 3.1).

#include <spawn.h>

int posix_spawn (pid_t *restrict pid, const char *restrict path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv [restrict], char *const envp [restrict]); int posix_spawnp (pid_t *restrict pid, const char *restrict file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *restrict attrp, char *const argv [restrict], char *const envp [restrict]);

Листинг 3.1. Описание функций одношагового порождения процессов. (html, txt)

Аргументами, отличающими posix_spawn() и posix_spawnp() от функций семейства exec*() являются pid, file_actions, attrp. Опишем их назначение.

По указателю pid (если он отличен от NULL) возвращается идентификатор успешно порожденного процесса.

Аргументы file_actions и attrp отвечают за контроль сущностей, наследуемых при одношаговом порождении процессов.
Чтобы достичь перечисленных выше целей, функции posix_spawn() и posix_spawnp() контролируют шесть видов наследуемых сущностей:

  • файловые дескрипторы;
  • идентификатор группы процессов;
  • идентификаторы пользователя и группы процесса;
  • параметры планирования;
  • маску сигналов;
  • способ обработки сигналов, игнорируемых родительским процессом.


Контроль файловых дескрипторов, в первую очередь стандартных ввода, вывода и протокола, который осуществляется с помощью аргумента file_actions, позволяет порожденному процессу получить доступ к потокам данных, открытым или даже порожденным родительским процессом, без встраивания в исходный текст или передачи в качестве аргументов main() конкретных имен файлов или номеров дескрипторов.

Как правило, все открытые дескрипторы родительского процесса остаются таковыми и в порожденном, за исключением тех, у которых установлен флаг FD_CLOEXEC. Кроме того, если значение аргумента file_actions отлично от NULL, до обработки флагов FD_CLOEXEC принимается во внимание указуемый объект типа posix_spawn_file_actions_t, который содержит действия по закрытию, открытию и/или дублированию файловых дескрипторов. Для формирования объектов типа posix_spawn_file_actions_t служат функции posix_spawn_file_actions_init(), posix_spawn_file_actions_addclose(), posix_spawn_file_actions_addopen() и posix_spawn_file_actions_adddup2(); функция posix_spawn_file_actions_destroy() ликвидирует подобный объект (см. листинг 3.2).

#include <spawn.h>

int posix_spawn_file_actions_init (posix_spawn_file_actions_t *file_actions);

int posix_spawn_file_actions_destroy (posix_spawn_file_actions_t *file_actions);



int posix_spawn_file_actions_addclose (posix_spawn_file_actions_t *file_actions, int fildes);

int posix_spawn_file_actions_addopen ( posix_spawn_file_actions_t *restrict file_actions, int fildes, const char *restrict path, int oflag, mode_t mode);

int posix_spawn_file_actions_adddup2 (posix_spawn_file_actions_t *file_actions, int fildes, int newfildes);

Листинг 3.2.


Описание функций формирования и ликвидации объектов типа posix_spawn_file_actions_t. (html, txt)

Функция posix_spawn_file_actions_addclose() добавляет дескриптор fildes к числу закрываемых перед началом выполнения порожденного процесса. Функция posix_spawn_file_actions_addopen() предписывает открыть дескриптор fildes, как если бы был выполнен вызов open (path, oflag, mode). Наконец, функция posix_spawn_file_actions_adddup2() специфицирует дублирование дескриптора fildes в newfildes (close (newfildes); fcntl (fildes, F_DUPFD, newfildes)). Таким образом, функции posix_spawn() и posix_spawnp(), отправляясь от набора открытых дескрипторов родительского процесса, выполняют действия, заданные аргументом file_actions, и получают набор дескрипторов, открытых в порождаемом процессе, то есть родительский процесс берет на себя согласование по файловым дескрипторам с независимо созданным новым образом процесса.

Отметим, что с помощью функции posix_spawn_file_actions_addopen() удобно перенаправлять ввод/вывод порожденного процесса.

За контроль других сущностей, наследуемых при одношаговом порождении процессов, отвечает атрибутный объект , заданный аргументом attrp. Для формирования и опроса подобных объектов служат функции, показанные на листингах 3.3, 3.4 и 3.5.

#include <spawn.h>

int posix_spawnattr_init ( posix_spawnattr_t *attr);

int posix_spawnattr_destroy ( posix_spawnattr_t *attr);

int posix_spawnattr_getflags ( const posix_spawnattr_t *restrict attr, short *restrict flags);

int posix_spawnattr_setflags ( posix_spawnattr_t *attr, short flags);

int posix_spawnattr_getpgroup ( const posix_spawnattr_t *restrict attr, pid_t *restrict pgroup);

int posix_spawnattr_setpgroup ( posix_spawnattr_t *attr, pid_t pgroup);

Листинг 3.3. Описание функций формирования и опроса атрибутных объектов порождаемых процессов. (html, txt)

#include <spawn.h> #include <sched.h>

int posix_spawnattr_getschedparam ( const posix_spawnattr_t *restrict attr, struct sched_param *restrict schedparam);



int posix_spawnattr_setschedparam ( posix_spawnattr_t * restrict attr, const struct sched_param *restrict schedparam);

int posix_spawnattr_getschedpolicy ( const posix_spawnattr_t *restrict attr, int *restrict schedpolicy);

int posix_spawnattr_setschedpolicy ( posix_spawnattr_t *attr, int schedpolicy);

Листинг 3.4. Описание функций опроса и установки параметров и политики планирования в атрибутных объектах порождаемых процессов. (html, txt)

#include <spawn.h> #include <signal.h> int posix_spawnattr_getsigdefault ( const posix_spawnattr_t *restrict attr, sigset_t *restrict sigdefault);

int posix_spawnattr_setsigdefault ( posix_spawnattr_t *restrict attr, const sigset_t *restrict sigdefault);

int posix_spawnattr_getsigmask ( const posix_spawnattr_t *restrict attr, sigset_t *restrict sigmask);

int posix_spawnattr_setsigmask ( posix_spawnattr_t *restrict attr, const sigset_t *restrict sigmask);

Листинг 3.5. Описание функций опроса и установки подразумеваемой обработки и маски сигналов в атрибутных объектах порождаемых процессов. (html, txt)



POSIX_SPAWN_SETSIGMASK

Установить начальную маску сигналов по атрибутному объекту.

POSIX_SPAWN_SETSCHEDPARAM

Установить параметры планирования по атрибутному объекту.

POSIX_SPAWN_SETSCHEDULER

Установить политику и параметры планирования по атрибутному объекту (независимо от состояния флага POSIX_SPAWN_SETSCHEDPARAM).

Если значение аргумента attrp равно NULL, используются подразумеваемые значения атрибутов.

Все характеристики нового процесса, на которые не воздействуют аргументы attrp и file_actions, устанавливаются так, как если бы применялось двухшаговое порождение fork()/exec(). Будут ли при одношаговом порождении выполняться обработчики разветвления процессов, зарегистрированные с помощью функции atfork(), зависит от реализации.

При библиотечной реализации функций posix_spawn() и posix_spawnp() некоторые ошибки могут быть выявлены только после порождения процесса. В таком случае родительский процесс может узнать о них, анализируя с помощью макросов WIFEXITED, WEXITSTATUS (см. курс [1]) значение stat_val, возвращаемое функциями wait() и/или waitpid(). Предлагается, чтобы статус "аварийного завершения до начала реального выполнения" равнялся 127. Это не очень естественно и удобно, но иного выхода не видно.

В целом средства одношагового порождения процессов позволяют достичь сформулированных выше целей. Они просты, но обладают достаточной выразительной силой и, несомненно, способны заменить двухшаговое порождение по крайней мере в половине типичных случаев.

Чтобы лучше понять семантику одношагового порождения процессов, приведем фрагмент возможной библиотечной реализации функции posix_spawn(), представленной в четвертой, информационной части стандарта POSIX-2001 (см. листинги 3.6 и 3.7).

typedef struct { short posix_attr_flags; pid_t posix_attr_pgroup; sigset_t posix_attr_sigmask; sigset_t posix_attr_sigdefault; int posix_attr_schedpolicy; struct sched_param posix_attr_schedparam; } posix_spawnattr_t;

typedef char *posix_spawn_file_actions_t;

int posix_spawn (pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv [], char *const envp []);



Листинг 3.6. Фрагмент возможного содержимого файла spawn.h.

int posix_spawn (pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv [], char *const envp []) {

/* Создадим новый процесс */ if ((*pid = fork()) == (pid_t) 0) { /* Порожденный процесс */ /* Позаботимся о группе процессов */ if (attrp->posix_attr_flags & POSIX_SPAWN_SETPGROUP) { /* Изменим унаследованную группу */ if (setpgid (0, attrp->posix_attr_pgroup) != 0) { /* Неудача */ exit (127); } }

/* Позаботимся о действующих идентификаторах */ /* пользователя и группы */ if (attrp->posix_attr_flags & POSIX_SPAWN_RESETIDS) { /* В данном случае неудачи быть не может */ setuid (getuid ()); setgid (getgid ()); }

/* Позаботимся о подразумеваемом способе */ /* обработки сигналов */ if (attrp->posix_attr_flags & POSIX_SPAWN_SETSIGDEF) { struct sigaction deflt; sigset_t all_signals; int s;

deflt.sa_handler = SIG_DFL; deflt.sa_flags = 0;

sigfillset (&all_signals);

/* Цикл по всем сигналам */ for (s = 0; sigismember (&all_signals, s); s++) { if (sigismember (&attrp->posix_attr_sigdefault, s)) { if (sigaction (s, &deflt, NULL) == -1) { exit (127); } } } }

/* Проконтролируем остальные атрибуты */ /* . . . */

/* Подменим образ процесса */ execve (path, argv, envp); exit (127); } else { /* Родительский (вызывающий) процесс */ if (*pid == (pid_t) (-1)) return errno; return 0; } }

Листинг 3.7. Фрагмент возможной библиотечной реализации функции posix_spawn().

Если для порожденного процесса нужно особым образом установить характеристику, не принадлежащую к числу описанных выше контролируемых сущностей (например, значение переменной окружения), приходится перед обращением к posix_spawn() сохранить и переустановить ее, а затем восстановить прежнее значение (см. листинг 3.8). Правда, при этом необходимо, чтобы все потоки управления, выполняющиеся в рамках родительского процесса, были устойчивы к подобным манипуляциям.



/* Запуск процесса с произвольным идентификатором */ /* пользователя */

uid_t old_uid; uid_t new_uid = ...;

old_uid = getuid (); setuid (new_uid);

posix_spawn (...);

setuid (old_uid);

Листинг 3.8. Пример установки характеристики порожденного процесса, не принадлежащей к числу контролируемых стандартными средствами.

На листинге 3.9 показан пример перенаправления стандартных ввода и вывода порождаемого процесса с помощью формирования и использования объекта типа posix_spawn_file_actions_t. В данном случае стандартный вывод (дескриптор 1) направляется в файл outfile, а стандартный ввод (дескриптор 0) отождествляется с открытым ранее дескриптором socket_pair [1]. Попутно обеспечивается закрытие в новом процессе дескрипторов socket_pair [0] и socket_pair [1].

posix_spawn_file_actions_t file_actions;

posix_spawn_file_actions_init ( &file_actions); posix_spawn_file_actions_addopen ( &file_actions, 1, "outfile", ...); posix_spawn_file_actions_adddup2 ( &file_actions, socket_pair [1], 0); posix_spawn_file_actions_addclose ( &file_actions, socket_pair [0]); posix_spawn_file_actions_addclose ( &file_actions, socket_pair [1]);

posix_spawn (..., &file_actions, ...); posix_spawn_file_actions_destroy ( &file_actions);

Листинг 3.9. Пример перенаправления стандартных ввода и вывода порождаемого процесса.

Любопытно сопоставить реальные накладные расходы на одношаговое и двухшаговое порождение процессов. Программа, порождающая с помощью функции posix_spawn() практически пустые процессы, показана на листинге 3.10. На листинге 3.11 приведены данные о времени ее работы, полученные с помощью команды time -p. Полученные результаты практически не отличаются от измеренного ранее времени двухшагового порождения. Это означает, что в используемой нами версии ОС Linux функции posix_spawn() и posix_spawnp() реализованы как библиотечные, а их применение выигрыша в эффективности в данном случае не дает (но по соображениям "мобильной потенциальной эффективности" их все равно есть смысл использовать).

#include <spawn.h> #include <stdio.h> #include <sys/wait.h> #include <errno.h>

#define N 10000

int main (void) { char *s_argv [] = {"dummy", NULL}; char *s_env [] = {NULL};

int i;

for (i = 0; i &lt; N; i++) { if ((errno = posix_spawn ( NULL, "./dummy", NULL, NULL, s_argv, s_env)) != 0) { perror ("POSIX_SPAWN"); return (errno); } (void) wait (NULL); }

return 0; }

Листинг 3.10. Пример программы, порождающей в цикле практически пустые процессы с помощью функции posix_spawn().

real 34.37 user 12.01 sys 22.07

Листинг 3.11. Возможные результаты измерения времени работы программы, порождающей в цикле практически пустые процессы с помощью функции posix_spawn().


Содержание раздела