Опубликован: 06.12.2004 | Доступ: свободный | Студентов: 1179 / 142 | Оценка: 4.76 / 4.29 | Длительность: 20:58:00
ISBN: 978-5-9556-0021-5
Лекция 3:

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

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

POSIX_SPAWN_RESETIDS

Установить действующие идентификаторы пользователя и группы по соответствующим реальным идентификаторам родительского процесса. (Если у файла с образом нового процесса взведены биты ПДИП и/или ПДИГ, то, независимо от состояния флага POSIX_SPAWN_RESETIDS, действующие идентификаторы наследуются у этого файла, а не у родительского процесса.)

POSIX_SPAWN_SETPGROUP

Установить идентификатор группы процессов по соответствующему атрибуту объекта, на который указывает аргумент attrp. При нулевом значении этого атрибута порожденный процесс выделяется в новую группу с идентификатором, равным его (процесса) идентификатору.

POSIX_SPAWN_SETSIGDEF

Установить подразумеваемый способ обработки сигналов, заданных в атрибутном объекте. Отметим, что это существенно только для сигналов, игнорируемых родительским процессом.

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().