Skip to content

Latest commit

 

History

History
68 lines (46 loc) · 6.52 KB

终端退出与信号处理.md

File metadata and controls

68 lines (46 loc) · 6.52 KB

又是一个工作上遇到的问题,即如何在终端意外关闭的情况下让进程最后发送出一条指令

上面这个问题其实涉及到两个方面:

  1. 终端意外关闭的情况下会话进程会如何?
  2. 信号处理

prcess session

linux下进程并不是没有联系的,正如ppid记录了父进程一样,task_struct有一个sid的属性,又叫会话ID,这儿就需要知道什么叫做会话。所有的进程都属于某个进程组,而进程组又属于某个会话。 一个很通俗的解释就是当打开一个终端时就可以看作是开启了一个会话,然后每执行一条命令就相当于给这个会话添加一个进程组,而命令本身也有区别,比如单条命令与同时执行多个命令,又比如添加&符号将命令放入后台执行,这就自然的分出来前台进程后台进程 在一个会话中同一时刻可以存在多个后台进程组但是只能有一个前台进程组,而且只有前台进程组中的进程才能在控制终端中读取输入,而同样的用户在终端中输入信号生成符也只会发送到前台进程组。说了这么多什么叫做组?说白了就是进程之间是否有协作关系,例如利用|管道符号连接的两个进程,他们就同属于一个进程组。 引入组的概念是为了方便管理,当一个信号发送到进程组的时候,该组内所有的进程都会接收到这个信号,而引入会话的概念则是将多进程工作都囊括在一个终端中,仅选取某个进程来作为前台接收终端输入和信号

who kill jobs?

伴随着终端的退出,自然需要针对一个会话内的所有进程作处理,这些job在不同场景下会收到来自kernel或者是shell发出的SIGHUP,那么这个场景是什么情况呢?

kernel send SIGHUP

kernel发送的SIGHUP的对象有如下两种:

  1. 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.
  1. other process意思很明显,就是非控制进程

先说第一种,这种情况往往发生在terminal/pseudoterminal退出的情况下发生,kernel的终端驱动检测到终端退出(真实终端插拔或是伪终端的master关闭)后会发送SIGHUPcontrolling process,后续会话中的jobs则交由controlling process来管理,这个情况就比如直接关闭终端窗口。 那如果controlling processkill或是主动退出了呢?这便牵扯到了第二种情况,kernel非controlling process发送SIGHUP,当controlling process退出后kernel会向会话的前台进程组发送SIGHUP,还有一种特殊情况也是由kernel直接发送的SIGHUP,那就是整个进程组成为了孤儿进程组(orphaned process group),当一个进程组成为孤儿进程组的时候,若该组中存在stopped memberskernel向该组中的所有进程先发送SIGHUP再发送SIGCONT信号。

总结来说就是终端的退出或是controlling process的退出都可以看作是一次会话的结束,那么kernel就需要率先对此作出反应:

  1. 终端异常关闭则向controlling process发送SIGHUP信号,后续的jobs管理则由controlling process负责
  2. controlling process退出则向其会话的前台进程组(foreground process group)发送SIGHUP信号
  3. 若刚产生的孤儿进程组中有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下执行这个命令后会产生一个后台进程组,这个进程组中有两个进程pingcat,其中cat会因为第二个命令进入stop状态,第三个命令则是为了刷新jobs防止bash发送SIGHUP信号,当kill掉这个shell的时候整个后台进程组(background process group)就会成为一个孤儿进程组然后走上述的3流程。

controlling process send SIGHUP

回到上面kernel发送信号的第一点,终端异常关闭会向controlling process发送SIGHUP以后kernel就不管了,那此时一个会话中是只有一个进程收到信号的那么其他进程是怎么退出的呢? 以bash为例子,当一个bash接收到SIGHUP后会转发此信号到all jobs,不管前台jobs还是后台jobs都会收到,这是依靠其job control来实现的,其会记录下由bash启动的所有进程,虽然这是有条件的:

  1. when it receives SIGHUP, and it is an interactive shell (and job control support is enabled at compile-time);
  2. 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