Группы процессов POSIX - PullRequest
12 голосов
/ 26 июня 2009

В настоящее время я внедряю группы процессов в подсистему POSIX проекта моей операционной системы. Тем не менее, я немного запутался в спецификации POSIX (setsid) (наряду со страницей Википедии по группам процессов).

Наш уровень терминала отправляет SIGINT процессу переднего плана (группа, чей идентификатор должен равняться PID лидера группы). В этом случае этот процесс переднего плана (наше приложение для входа в систему) становится лидером группы, вызывая setsid. Когда пользователь входит в систему, программа разветвляется и запускает оболочку пользователя. На этом этапе я понимаю, что я звоню setpgid от разветвленного ребенка, прежде чем позвонить exec*. Это означает, что выполненная программа с самого начала будет частью группы процессов.

Если бы я хотел запустить вновь разветвленного потомка за пределами группы процессов, я бы просто позвонил setsid в разветвленном потомке перед вызовом exec*.

Это правильно? Есть ли какие-то неясные вещи, которые я должен проверять или делать?

В качестве дополнительного вопроса, который, я полагаю, я уже знаю, является ли требование для fork передать членство в группе? Или это должно быть сделано с помощью setpgid после каждого fork вызова? Я понимаю, что группы процессов переносятся на fork из определения POSIX fork.

Заранее спасибо.

Ответы [ 2 ]

15 голосов
/ 26 июня 2009

Интересный вопрос - не в последнюю очередь потому, что он так долго оставался без частичного ответа.

Базовые определения POSIX

Некоторые цитаты из части определений POSIX:

3.290 Группа процессов

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

3.291 ID группы процессов

Уникальный положительный целочисленный идентификатор, представляющий группу процессов в течение ее жизненного цикла.

Примечание: См. Также Повторное использование идентификатора группы процессов, определенное в разделе Повторное использование идентификатора процесса.

3.292 Руководитель группы процессов

Процесс, идентификатор процесса которого совпадает с идентификатором его группы процессов.

3.293 Время жизни группы процессов

Период времени, который начинается, когда создается группа процессов, и заканчивается, когда последний оставшийся процесс в группе покидает группу, из-за окончания времени жизни последнего процесса или последнего оставшегося процесса, вызывающего setsid () или setpgid ().

Примечание: Функции setsid () и setpgid () подробно описаны в разделе «Системные интерфейсы» POSIX.1-2008.

[...]

3.337 Сессия

Набор групп процессов, созданных для контроля работы. Каждая группа процессов является членом сеанса. Процесс считается участником сеанса, членом которого является его группа процессов. Вновь созданный процесс присоединяется к сеансу своего создателя. Процесс может изменить свое членство в сеансе; см. setsid (). В одном сеансе может быть несколько групп процессов.

Примечание: Функция setsid () подробно описана в разделе «Системные интерфейсы» POSIX.1-2008.

3.338 Лидер сессии

Процесс, который создал сеанс.

Примечание: Для получения дополнительной информации см. Функцию setsid (), определенную в томе «Системные интерфейсы» POSIX.1-2008.

3.339 Время жизни сессии

Период между созданием сеанса и окончанием времени жизни всех групп процессов, которые остаются членами сеанса.


Системные интерфейсы POSIX

ИМЯ * * тысяча пятьдесят-одна

setsid - создать сеанс и установить идентификатор группы процессов

СИНТАКСИС

   #include <unistd.h>

   pid_t setsid(void);
* +1057 * ОПИСАНИЕ * * тысяча пятьдесят-восемь

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

И

ИМЯ

setpgid - установить идентификатор группы процессов для управления заданиями

СИНТАКСИС

   #include <unistd.h>

   int setpgid(pid_t pid, pid_t pgid);
* 1 072 * ОПИСАНИЕ

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

Идентификатор группы процессов лидера сеанса не должен изменяться.

После успешного завершения идентификатор группы процессов для процесса с идентификатором процесса, который соответствует pid, должен быть установлен в pgid.

В особом случае, если pid равен 0, должен использоваться идентификатор процесса вызывающего процесса. Также, если pgid равно 0, должен использоваться идентификатор процесса указанного процесса.


Интерпретация

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

Копирование части вопроса:

Наш конечный уровень отправляет SIGINT процессу переднего плана (группе, чей идентификатор должен равняться PID лидера группы). В этом случае этот процесс переднего плана (наше приложение «login») становится лидером группы, вызывая setsid. Когда пользователь входит в систему, программа разветвляется и запускает оболочку пользователя. На этом этапе я понимаю, что я вызываю setpgid от разветвленного потомка перед вызовом exec *. Это означает, что выполненная программа с самого начала будет частью группы процессов.

Я подозреваю, что круглые скобки должны быть «группой процессов переднего плана (чей идентификатор должен равняться PID лидера группы)». По определению (3.292) лидер группы процессов - это процесс, PID которого совпадает с идентификатором группы процессов. Я не цитирую соответствующий материал, но считаю, что отправка сигнала руководителю группы процессов правильна

Обратите внимание, что процесс переднего плана становится лидером сеанса, вызывая setsid(), а также становится лидером группы процессов. Я ожидаю, что программа входа в систему установит пользовательскую оболочку в качестве лидера группы процессов (и, вероятно, лидера сеанса) после разветвления, но до запуска оболочки. Все дочерние процессы автоматически наследуют группу процессов и сеанс от своих родительских процессов; Вы должны переопределить это, если хотите, чтобы оно было другим.

Если бы я хотел запустить вновь разветвленного потомка за пределами группы процессов, я бы просто вызвал setsid в разветвленном потомке перед вызовом exec *.

Вы можете сделать это, но это также создаст новую сессию. Вы, вероятно, хотите использовать setpgid() (современный стандарт; возможно, setpgrp(), который является более старым стандартом от SVID), а не setsid().

Это правильно? Есть ли какие-то неясные вещи, которые я должен проверять или делать?

Да, это в основном правильно. Да, вероятно, есть некоторые неясные вещи, которые нужно отслеживать. Например, вам может понадобиться подумать об управляющем TTY.

В качестве дополнительного вопроса, который, я полагаю, я уже знаю, является ли обязательным требование для команды fork для передачи членства в группах? Или это должно быть сделано с помощью setpgid после каждого вызова fork? Я понимаю, что группы процессов передаются с помощью fork из определения POSIX для fork.

Дочерний процесс после fork() принадлежит к тому же набору групп (как в /etc/group), а также к тому же сеансу и той же группе процессов - но он не является лидером сеанса и не является группой процессов лидер.

0 голосов

setpgid Пример минимальной группы процессов POSIX C

Я считаю, что игра с базовыми API-интерфейсами часто является лучшим способом изучения новых концепций, поэтому давайте попробуем.

Это показывает, как сигнал действительно отправляется ребенку, если ребенок не изменил свою группу процессов с помощью setpgid.

main.c

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

volatile sig_atomic_t is_child = 0;

void signal_handler(int sig) {
    char parent_str[] = "sigint parent\n";
    char child_str[] = "sigint child\n";
    signal(sig, signal_handler);
    if (sig == SIGINT) {
        if (is_child) {
            write(STDOUT_FILENO, child_str, sizeof(child_str) - 1);
        } else {
            write(STDOUT_FILENO, parent_str, sizeof(parent_str) - 1);
        }
    }
}

int main(int argc, char **argv) {
    pid_t pid, pgid;

    (void)argv;
    signal(SIGINT, signal_handler);
    signal(SIGUSR1, signal_handler);
    pid = fork();
    assert(pid != -1);
    if (pid == 0) {
        /* Change the pgid.
         * The new one is guaranteed to be different than the previous, which was equal to the parent's,
         * because `man setpgid` says:
         * > the child has its own unique process ID, and this PID does not match
         * > the ID of any existing process group (setpgid(2)) or session.
         */
        is_child = 1;
        if (argc > 1) {
            setpgid(0, 0);
        }
        printf("child pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)getpgid(0));
        assert(kill(getppid(), SIGUSR1) == 0);
        while (1);
        exit(EXIT_SUCCESS);
    }
    /* Wait until the child sends a SIGUSR1. */
    pause();
    pgid = getpgid(0);
    printf("parent pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)pgid);
    /* man kill explains that negative first argument means to send a signal to a process group. */
    kill(-pgid, SIGINT);
    while (1);
}

GitHub upstream .

Компилировать с:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -Wpedantic -o setpgid setpgid.c

Запуск без setpgid

Без аргументов CLI setpgid не выполняется:

./setpgid

Возможный исход:

child pid, pgid = 28250, 28249
parent pid, pgid = 28249, 28249
sigint parent
sigint child

и программа зависает.

Как мы видим, pgid обоих процессов одинаков, поскольку он наследуется через fork.

Тогда всякий раз, когда вы нажимаете:

Ctrl + C

Выводит снова:

sigint parent
sigint child

Это показывает, как:

  • для отправки сигнала всей группе процессов с kill(-pgid, SIGINT)
  • Ctrl + C на терминале отправляет уничтожение всей группе процессов по умолчанию

Выйдите из программы, отправив разные сигналы обоим процессам, например, SIGQUIT с Ctrl + \.

Запуск с setpgid

Если вы запускаете с аргументом, например ::

./setpgid 1

тогда дочерний объект меняет свой pgid, и теперь каждый раз из родительского файла печатается только один знак:

child pid, pgid = 16470, 16470
parent pid, pgid = 16469, 16469
sigint parent

А теперь, когда вы нажмете:

Ctrl + C

только родитель также получает сигнал:

sigint parent

Вы по-прежнему можете убить родителя, используя SIGQUIT:

Ctrl + \

однако у ребенка теперь другой PGID, и он не получает этот сигнал! Это видно из:

ps aux | grep setpgid

Вам придется убить его явно с помощью:

kill -9 16470

Это проясняет, почему существуют группы сигналов: в противном случае мы бы получили кучу процессов, которые будут постоянно очищаться вручную.

Проверено на Ubuntu 18.04.

...