又是一个工作上遇到的问题,即如何在终端意外关闭的情况下让进程最后发送出一条指令
上面这个问题其实涉及到两个方面:
- 终端意外关闭的情况下会话进程会如何?
- 信号处理
linux下进程并不是没有联系的,正如ppid
记录了父进程一样,task_struct
有一个sid
的属性,又叫会话ID
,这儿就需要知道什么叫做会话。所有的进程都属于某个进程组,而进程组又属于某个会话。
一个很通俗的解释就是当打开一个终端时就可以看作是开启了一个会话,然后每执行一条命令就相当于给这个会话添加一个进程组,而命令本身也有区别,比如单条命令与同时执行多个命令,又比如添加&
符号将命令放入后台执行,这就自然的分出来前台进程
和后台进程
在一个会话中同一时刻可以存在多个后台进程组但是只能有一个前台进程组,而且只有前台进程组中的进程才能在控制终端中读取输入,而同样的用户在终端中输入信号生成符也只会发送到前台进程组。说了这么多什么叫做组?说白了就是进程之间是否有协作关系,例如利用|
管道符号连接的两个进程,他们就同属于一个进程组。
引入组的概念是为了方便管理,当一个信号发送到进程组的时候,该组内所有的进程都会接收到这个信号,而引入会话的概念则是将多进程工作都囊括在一个终端中,仅选取某个进程来作为前台接收终端输入和信号
伴随着终端的退出,自然需要针对一个会话内的所有进程作处理,这些job
在不同场景下会收到来自kernel
或者是shell
发出的SIGHUP
,那么这个场景是什么情况呢?
kernel
发送的SIGHUP
的对象有如下两种:
controlling process
这个在一般情况下都是创建会话的进程,用原本的解释来说
The session leader that established the connection to the controlling terminal. If the terminal subsequently ceases to be a controlling terminal for this session, the session leader ceases to be the controlling process.
other process
意思很明显,就是非控制进程
先说第一种,这种情况往往发生在terminal/pseudoterminal
退出的情况下发生,kernel
的终端驱动检测到终端退出(真实终端插拔或是伪终端的master关闭)后会发送SIGHUP
到controlling process
,后续会话中的jobs
则交由controlling process
来管理,这个情况就比如直接关闭终端窗口。
那如果controlling process
被kill
或是主动退出了呢?这便牵扯到了第二种情况,kernel
向非controlling process
发送SIGHUP
,当controlling process
退出后kernel
会向会话的前台进程组发送SIGHUP
,还有一种特殊情况也是由kernel
直接发送的SIGHUP
,那就是整个进程组成为了孤儿进程组(orphaned process group)
,当一个进程组成为孤儿进程组
的时候,若该组中存在stopped members
则kernel
向该组中的所有进程先发送SIGHUP
再发送SIGCONT
信号。
总结来说就是终端的退出或是controlling process
的退出都可以看作是一次会话的结束,那么kernel
就需要率先对此作出反应:
- 终端异常关闭则向
controlling process
发送SIGHUP
信号,后续的jobs
管理则由controlling process
负责 controlling process
退出则向其会话的前台进程组(foreground process group)
发送SIGHUP
信号- 若刚产生的
孤儿进程组
中有stopped members
则向所有孤儿进程组
中进程先发送SIGHUP
信号再发送SIGCONT
信号
这儿的孤儿进程组
是什么呢?
POSIX defines an orphaned process group as a group in which the parent of each process belonging to that group is either a member of that same group or is part of another session.
In other words, a process group is not orphaned as long as at least one process in the group has a parent in a different process group but in the same session.
So why is it important to know if a group is orphaned? Because of processes that are stopped. If a process group is orphaned, and there is at least one process in that group that is stopped (e.g. it was suspended with SIGSTOP or SIGTSTP), then POSIX.1 requires that every process in the orphaned group be sent SIGHUP followed by SIGCONT.
一个非常简单的例子就是如下的命令
$ping 8.8.8.8 | cat &
$kill -TSTP `catpid`
$exec bash
在一个终端的shell
下执行这个命令后会产生一个后台进程组,这个进程组中有两个进程ping
和cat
,其中cat
会因为第二个命令进入stop
状态,第三个命令则是为了刷新jobs
防止bash
发送SIGHUP
信号,当kill
掉这个shell
的时候整个后台进程组(background process group)
就会成为一个孤儿进程组
然后走上述的3
流程。
回到上面kernel
发送信号的第一点,终端异常关闭会向controlling process
发送SIGHUP
以后kernel
就不管了,那此时一个会话中是只有一个进程收到信号的那么其他进程是怎么退出的呢?
以bash
为例子,当一个bash
接收到SIGHUP
后会转发此信号到all jobs
,不管前台jobs
还是后台jobs
都会收到,这是依靠其job control
来实现的,其会记录下由bash
启动的所有进程,虽然这是有条件的:
- when it receives SIGHUP, and it is an interactive shell (and job control support is enabled at compile-time);
- when it exits, it is an interactive login shell, and huponexit option is set (and job control support is enabled at compile-time).
但是倘若某个程序也实现了这样的信号处理逻辑并成为了controlling procress
的话,那么即时其不是shell
也是一样能正确处理整个会话。
- does-linux-kill-background-processes-if-we-close-the-terminal-from-which-it-has
- why-doesnt-the-linux-kernel-send-sigcont-first-and-then-sighup-to-a-newly-orpha