Одношаговое порождение процессов
При стандартизации средств одношагового порождения процессов преследовались две основные цели:
- возможность использования на аппаратных конфигурациях без устройства управления памятью и без какого-либо специфического оборудования;
- совместимость с существующими 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 < 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().