Почему завершение родительского процесса приводит к завершению дочернего процесса, когда он находится в состоянии приостановки (T)?

Вопрос или проблема

Я использую Ubuntu (Linux).

У меня есть две простые программы.

Родитель:

package main

import (
    "fmt"
    "syscall"
    "time"
)

func main() {
    attr := &syscall.ProcAttr{
        Files: []uintptr{0, 1, 2},
        Sys: &syscall.SysProcAttr{ // дочерний процесс в своей группе
            Setpgid: true,
            Pgid:    0,
        },
    }

    _, err := syscall.ForkExec("./child/child", []string{"child"}, attr)
    if err != nil {
        fmt.Println("Ошибка:", err)
        return
    }

    for {
        fmt.Println("Родитель активен")
        time.Sleep(10 * time.Second)
    }
}

Дочерний (дочерний процесс в своей группе):

package main

import (
    "fmt"
    "time"
)

func main() {
    for {
        fmt.Println("Привет от дочернего процесса")
        time.Sleep(time.Second * 20)
    }
}

После запуска родительской программы (./parent), результат выполнения команды ps (в частности, ps -t /dev/pts/0 -o pid,ppid,pgid,stat,comm) будет следующим:

    PID    PPID    PGID STAT COMMAND
 466922  466896  466922 Ss   bash
 467049  466922  467049 Sl+  parent
 467054  467049  467054 Sl   child

После завершения родительского процесса (либо с помощью kill -SIGKILL 467049, kill -SIGINT 467049 или CTRL-C), дочерний процесс продолжает работать (состояние S/R). Это именно то, что я ожидал.

    PID    PPID    PGID STAT COMMAND
 466922  466896  466922 Ss+   bash
 467054  467049  467054 Sl   child

Что меня смущает, так это следующий сценарий. Сначала я запускаю родительский процесс (./parent). Результат команды ps такой же, как в предыдущем случае. Затем я приостанавливаю дочерний процесс с помощью kill -SIGTSTP 467054 или kill -SIGSTOP 467054. Результат команды ps следующий:

    PID    PPID    PGID STAT COMMAND
 466922  466896  466922 Ss   bash
 467049  466922  467049 Sl+  parent
 467054  467049  467054 Tl   child

Затем я завершаю родительский процесс (либо с помощью kill -SIGKILL 467049, kill -SIGINT 467049 или CTRL-C). По какой-то причине в этом случае дочерний процесс также завершен! Результат команды ps:

    PID    PPID    PGID STAT COMMAND
 466922  466896  466922 Ss+   bash

Как? Почему?

Спасибо, Stéphane Chazelas. Цитата из _exit:

Если из-за выхода процесса группа процессов становится осиротевшей,
и если какой-либо член вновь осиротевшей группы процессов остановлен,
тогда сигнал SIGHUP, за которым следует сигнал SIGCONT, должен быть
отправлен каждому процессу в вновь осиротевшей группе процессов.

.

Ответ или решение

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

### Теория

В Unix-подобных системах процессы организованы в деревья. Каждый процесс имеет идентификатор процесса (PID) и идентификатор родительского процесса (PPID). Когда родительский процесс завершается, его дочерние процессы становятся «усыновленными» процессом init (PID 1), если они не указаны в атрибутах управления процессом.

Основное правило заключается в том, что когда процесс завершает свою работу, это может привести к “осиротению” группы процессов. Если любая из этих осиротевших групп процессов имеет приостановленные процессы, то система отправляет сигнал SIGHUP каждому процессу в этой группе, а затем следует сигнал SIGCONT. SIGHUP обычно используется для уведомления процесса о потере управляющего терминала или завершении управляющего процесса, что может быть воспринято процессом как сигнал завершения.

### Пример

Давайте разберём ваш конкретный пример с родительским и дочерним процессами на языке Go. Родительский процесс создаёт дочерний и продолжает выполнять бесконечный цикл вывода сообщения. Дочерний процесс также выполняет подобный цикл, находясь в своей группе процессов, однако приостановка его выполнения (сигналы SIGSTOP, SIGTSTP) помещает его в состояние паузы.

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

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

### Применение

Когда вы используете системные вызовы для управления процессами, всегда следует учитывать управления группами процессов и как система обрабатывает состояния процессов при их завершении. В вашем примере проблемы возникли из-за неправильного предположения о блокировке состояния T. При работе с приостановленными процессами Linux автоматически посылает SIGHUP и затем SIGCONT к приостановленным процессам в случае осиротения, обеспечивая тем самым целостность и контроль над процессами.

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

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

Оцените материал
Добавить комментарий

Капча загружается...