linux进程组,会话,控制终端关系
本文最后更新于:2025年11月19日 下午
从思考题开始
先打开一个终端,运行如下命令:
1 | |
我们可以看到两个进程都在后台运行,ping结果都打印到当前终端。
如果此时我关闭当前终端,这两个ping程序会如何。
结论:
ping baidu会停止。
ping qq会持续运行,但是没有输出。
进程组、会话、控制终端
进程组
每个进程都属于一个进程组,进程组是一个或多个进程的集合。进程组一般也可理解为作业。
进程组有一个组长进程,组长进程的ID也是进程组ID。组长创建该组中的进程后可以退出,进程组中只要有一个进程存在,进程组就存在,和组长进程是否退出无关。
会话
会话由一个或多个进程组组成。进程可以调用setsid函数建立一个新的会话。
调用setsid的进程如果不是一个进程组的组长,那么创建会话成功,否则失败。所以创建守护进程前会先fork,然后在子进程中调用setsid。
setsid创建新会话成功后,它会做如下的事情:
(1)该进程新会话的会话首进程(session leader)
(2)该进程成为一个新进程组的组长进程。
(3)该进程脱离控制终端。
控制终端
(1)一个会话可以有一个控制终端,这个控制终端通常是终端设备(终端登录),或者伪终端设备(网络登录)。
(2)建立与终端连接的会话首进程成为控制进程
(3)一个会话中的进程组分为前台进程组和后台进程组
(4)一个会话如果有控制终端,那么它是一个前台进程组,其他的为后台进程组
(5)终端发出的信号(如ctrl + c, ctrl + z)将送到前台进程组的所有进程
后台进程组与控制终端
后台进程组如何与控制终端交互呢?
如果后台进程组要从终端读取输入,那么会产生SIGTTIN信号,默认动作时暂停该进程。串口会有打印,表明该进程已暂停。我们可通过作业控制把该进程放回前台。
如下:(fg是作业控制命令)
1 | |
如果后台进程要从终端输出,默认会直接输出到会话的控制终端。如果会话禁止了后台作业输出至控制终端(命令:stty tostop),那么进程会收到SIGTTOU信号,默认动作是暂停。
1 | |
stty -tostop 可以开启后台作业输出到控制终端,默认就是开启的
如果**后台进程组要设置控制终端,那么也会遇到SIGTTOU信号(也有可能是SIGSTOP信号)**,默认动作也是暂停该进程。
比如top命令就会设置终端。
1 | |
孤儿进程组
进程组中的所有进程的父进程都不在所属的会话中,那么这个进程组就是孤儿进程组。
因为孤儿进程组的父进程(init进程,ubuntu中为systemd进程)不在它所属的会话中,所以如果孤儿进程暂停后,会导致没有人去唤醒它。所以给孤儿进程发送SIGTTIO,SIGTTOU,SIGSTOP都没有意义。
- 如果孤儿进程要读终端、设置终端、关闭后台进程组输出到会话的控制终端后写终端,都会产生EIO错误,而不会产生信号使进程暂停。
关系图

- 写终端产生SIGTTOU这里指:设置终端或者关闭后台进程组输出到会话的控制终端后写终端。
回到开头
1 | |
运行两条ping之后的进程关系如下:
1 | |
两条ping命令都属于同一个会话31034,都拥有控制终端pts/1。两者属于不同的后台进程组。ping qq的父进程是6653(systemd),它所在的进程组为孤儿进程组。
关闭当前终端后,zsh退出产生SIGHUP信号给子进程,子进程链式产生SIGHUP给子子进程,SIGHUP默认动作是退出,所以当前会话内有亲缘关系的进程都会退出。
因为ping qq进程的父进程不在当前会话内,所以它不会退出。
另外开一个终端,查看到ping qq进程如下:
1 | |
只有关联的终端没有了,strace跟踪它的话,会发现,它的输出都会产生EIO错误。
1 | |
编写守护进程
守护进程的主要意义在于摆脱控制终端的影响。
一般的编写方式如下:
1 | |
讲解:
- 因为守护进程没有控制终端,标准输入输出都会关闭, 所以一般采用syslog进行输出。
- 第一次fork,可以确保进程不是进程组的组长进程。这是调用setsid的前提。
- setsid可以脱离当前的控制终端,创建新的会话,但是当前进程为会话的首进程。
- 为防止程序再次手动打开的终端成为控制终端,我们进行了第二次fork。因为会话首进程打开的终端才有可能变为控制终端。这一步也可以省略。
- 更改工作目录为’/‘,避免当前工作在其他目录上(如usb目录),导致挂载的文件系统无法卸载。
- 设置umask,继承的umask可能不是我们想要的。
- 关闭所有继承的描述符。
如上步骤创建守护进程看起来很长,有一个库函数daemon可以简化。
daemon函数
1 | |
- nochdir如果为0,则将工作目录设置为’/‘。否则不会更改。
- noclose如果为0,则将标准输入,标准输出,标准错误输出重定向到/dev/null。否则不会更改。
注意:
根据MAN文档说明,GNU C库的daemon实现来自BSD,它只fork一次。而不是fork两次。即调用返回后,当前进程是新会话的首进程。在遵循 System V 语义的系统(例如 Linux)上,这意味着如果守护程序打开的终端还不是另一个会话的控制终端,则该终端将无意中成为守护程序的控制终端。