Вопрос или проблема
У меня есть следующие две простые программы.
Родитель:
package main
import (
"fmt"
"syscall"
)
func main() {
attr := &syscall.ProcAttr{
Files: []uintptr{0, 1, 2},
Sys: &syscall.SysProcAttr{ // child in its own group
Setpgid: true,
Pgid: 0,
},
}
_, err := syscall.ForkExec("./child/child", []string{"child"}, attr)
if err != nil {
fmt.Println("Error:", err)
return
}
}
Ребенок:
package main
import (
"fmt"
"time"
)
func main() {
for {
fmt.Println("hi from child")
time.Sleep(time.Second * 5)
}
}
Вывод команды ps
:
yakog@yakog-computer:~/goprojects/parent$ ps -o pid,ppid,pgid,uid,wchan,stat,tt,command -t /dev/pts/19
PID PPID PGID UID WCHAN STAT TT COMMAND
1867701 1867320 1867701 1000 do_sel Ss+ pts/19 bash
1870508 2118 1870508 1000 ep_pol Sl pts/19 child
Когда я нажимаю CTRL-Z
или CTRL-C
, это не оказывает никакого эффекта. Это именно то, что я ожидал, поскольку процесс 1870508 не является частью переднего плана, а CTRL-Z
/CTRL-C
вызывает kill -SIGTSTP -1867701
/kill -SIGINT -1867701
. Следовательно, 1870508 не получает эти сигналы.
Также, когда я вызываю kill -SIGINT 1870508
или kill -SIGSTOP 1870508
, процесс завершается/приостанавливается. Я могу это понять. Хотя 1870508 не является частью переднего плана, с помощью команды kill
мы “напрямую” отправляем сигнал процессу.
Однако почему kill -SIGTSTP 1870508
не работает? После запуска процесса ./parent
и вызова команды kill -SIGTSTP 1870508
буквально ничего не происходит (статус 1870508 все еще R
/S
и он продолжает печатать в терминал). Я не могу понять, почему это не приостановило процесс (не перевело его в T
). Это должно быть аналогично -SIGINT
и -SIGSTOP
(мы “напрямую” отправляем -SIGTSTP процессу), однако в этом случае это не имеет эффекта.
Кроме того, когда я вызываю kill -SIGSTOP 1870508
(перевожу процесс в состояние T
) и затем вызываю kill -SIGINT 1870508
, процесс не завершается… Почему?
Все процессы получают сигналы постоянно. Им не важно, находятся ли они в переднем или заднем плане, или приостановлены.
Клавиши ctrl-z
и ctrl-c
отправляют сигнал процессу в переднем плане. Это не значит, что процесс должен быть в переднем плане, чтобы получить сигнал, но это значит, что если процесс не в переднем плане, ctrl-c
отправляет сигнал какому-то другому процессу. То есть поведение не меняется, просто другой процесс получает это поведение. Всегда существует какой-то процесс в переднем плане. Так что, если ваш целевой процесс находится в фоне и вы нажимаете ctrl-c
, ваша оболочка получает ctrl-c и обрабатывает его или игнорирует его, но не завершает выполнение.
Если процесс приостановлен (состояние T
), он получает сигнал. Однако процесс может поймать SIGINT и выполнить дополнительную обработку, но если он приостановлен, сигнал будет помещен в его очередь ожидания, и он выполнит эту обработку (и возможно завершит выполнение) когда он больше не будет приостановлен.
Экспериментируя с отправкой сигналов приостановленным процессам, я заметил, что некоторые сигналы заставляют их завершаться немедленно, а некоторые требуют, чтобы процесс был возобновлен, прежде чем сигнал вступит в силу. Возможно, есть что-то в процессе (например, библиотека?), что устанавливает обработчик сигнала для этих сигналов, чтобы можно было выполнить некоторую очистку (например, сброс буферов) перед завершением процесса.
Ответ или решение
Вопрос о том, почему команда kill
эффективно работает с некоторыми сигналами, такими как SIGINT
и SIGSTOP
, но не с SIGTSTP
, требует более глубокого понимания механизма сигналов в системах Unix/Linux.
Теория
Сигналы в Unix/Linux — это средства взаимодействия между процессами, которые позволяют одному процессу посылать запросы на выполнение определенных действий в другой процесс. Однако поведение сигналов может изменяться в зависимости от того, как они обрабатываются целевым процессом:
-
SIGINT (
Interrupt
) — стандартный сигнал, используемый для прерывания процесса. Обычно он инициируется пользователем с клавиатуры (например, черезCtrl+C
). По умолчанию этот сигнал завершает процесс, но его можно перехватить, и в этом случае процесс может обработать его иначе. -
SIGSTOP — сигнал, который невозможно перехватить, блокировать или проигнорировать. Он немедленно останавливает процесс, переводя его в состояние приостановки (T).
-
SIGTSTP (
Terminal Stop
) — сигнал, который генерируется при нажатииCtrl+Z
. По умолчанию он приостанавливает выполнение процесса, однако процесс может установить обработчик для этого сигнала, чтобы его проигнорировать или изменить поведение.
Пример
В вашем примере есть два процесса: родительский и дочерний. Дочерний процесс работает в бесконечном цикле, и вы обращаете внимание на то, как он реагирует на различные сигналы.
Когда вы посылаете сигнал SIGINT
через команду kill
, процесс завершается, потому что, как правило, этот сигнал приводит к завершению процесса, если не перехвачен.
Сигнал SIGSTOP
, как обсуждалось, приостанавливает процесс независимо от установленных обработчиков.
В то же время, SIGTSTP
может не иметь никакого эффекта, если в дочернем процессе был установлен обработчик для этого сигнала. Это, как правило, объясняет, почему отправка этого сигнала через kill
не приводит к ожидаемому поведению.
Применение
Для практического применения и понимания необходимо учитывать, как определенные реализации или библиотеки, используемые в процессе, могут устанавливать свои обработчики сигналов. Также важно помнить, что SIGTSTP
, в отличие от SIGSTOP
, может управляться (перехватываться) приложением. Чтобы SIGTSTP
работал как ожидается, процесс не должен устанавливать обработчик, который игнорирует его.
Если задача требует контроля процесса посредством стандартных сигналов, убедитесь, что никакие сторонние библиотеки или ваш код не изменяют обработку сигналов в неожиданном направлении. Навыки эффективного управления сигналами и понимания их обработки будут полезны при решении подобных задач в разработке системных приложений или серверных решений.