Вопрос или проблема
Я использую 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. Это знание может помочь избежать непреднамеренных завершений и сохранить стабильность системы.