Группы процессов POSIX

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

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

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

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

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

Спасибо заранее.

Ответ 1

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

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

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

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

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

3.291 Идентификатор группы процессов

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

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

3.292 Лидер группы процессов

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

3.293 Продолжительность жизни группы процессов

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

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

[...]

3.337 сеанс

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

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

3.338 Лидер сеанса

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

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

3.339 Срок службы сеанса

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


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

NAME

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

СИНТАКСИС

   #include <unistd.h>

   pid_t setsid(void);

ОПИСАНИЕ

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

и

NAME

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

СИНТАКСИС

   #include <unistd.h>

   int setpgid(pid_t pid, pid_t pgid);

ОПИСАНИЕ

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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 вверх по течению.

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

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, и теперь каждый раз из одного родителя печатается только один sigint:

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

Вы по-прежнему можете убить родителя, как и раньше, с помощью SIGQUIT:

Ctrl + \

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

ps aux | grep setpgid

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

kill -9 16470

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

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