目录
一, 进程相关的概念
二, 关闭会话时子进程进程被杀死
三, nohup 的原理
四, setsid 原理
五, daemon & 和守护进程的区别
六, 服务进程为什么要 fork 两次
七, systemd 管理 daemon
八, 僵尸进程
九, 进程名字和启动时指定进程名字
十, source command 和./command 和 exec 命令的区别
一, 进程相关的概念
进程需要了解 进程, 父进程, 进程组, 会话和控制终端的相关概念.
进程和父进程: 每个进程都有父进程, 而所有的进程以 init 进程为根, 形成一个树状结构
进程组: 每个进程都会属于一个进程组(process group), 每个进程组中可以包含多个进程. 进程组会有一个进程组领导进程 (process group leader), 领导进程的 PID 成为进程组的 ID (process group ID, PGID), 以识别进程组.
kill 给组发送信号进程组号前加负号如: kill -9 -2189
会话: 一个或是多个进程组集合. 进程可以通过调用 pid_t setsid(); 来建立一个新会话, 如果调用此函数的进程不是进程组长, 就会创建一个新的会话, 那么此时会:
该进程称为会话首进程 (session leader)
该进程称为进程组组长
该进程没有控制终端, 即使之前有控制终端这种联系也会断掉
可以使用第三个特性来创建 daemon 进程. 调用 getsid 可以获得会话首进程进程组 pid, 也就是会话首进程进程 id.
控制终端:
一个会话持有一个控制终端 (controlling terminal), 可以是终端设备也可以是伪终端
建立与控制终端连接的会话首进程被称为控制进程 (controlling process)
一个会话有多个进程组, 允许存在多个后台进程组 (backgroup process group) 和一个前台进程组 (foregroup process group)
键入终端的中断键 (Ctrl+C) 会发送中断信号给前台进程组所有进程
键入终端的退出键 (Ctrl+) 会发送退出信号给前台进程组所有进程
终端或是网络断开会将挂断信号发送给会话首进程
可以看到执行 ps -fj 结果如下:
- UID PID PPID PGID SID C STIME TTY TIME CMD
- chen 36829 36825 36829 36829 0 10:56 pts/0 00:00:00 -bash
- chen 37247 36829 37247 36829 0 10:57 pts/0 00:00:00 VIM
- chen 90490 36829 90490 36829 0 11:57 pts/0 00:00:00 ps -fj
其中 PID 就是进程 id,PPID 是父进程 id,PGID 为进程组 id,SID 为会话 ID
二, 关闭会话时子进程进程被杀死
终端在关闭时会发送 SIGHUP 信号给 session leader, 此处就是 bash 进程, bash 收到后向 session 内的所有进程发送 SIGHUP 然后退出.
SIGHUP 信号如果为注册处理函数默认行为就是退出. 所以会话退出时子进程都被杀死.
解决方案:
注册 SIGHUP 信号处理函数: 可以在代码中处理或者使用 nohup 命令
重新设置 setsid: 可以在代码中处理或者使用 setsid 命令
三, nohup 的原理
其实很简单就是注册了 SIGHUP 的一个处理函数, 忽略这个信号, 然后去执行实际的命令.
源码地址:
关键代码:
- // 注册处理函数
- signal (SIGHUP, SIG_IGN);
- char **cmd = argv + optind;
- // 执行实际的代码
- execvp (*cmd, cmd);
四, setsid 原理
fork 进程之后的子进程共享父进程的很多东西, 并且会话组长就是父进程的会长组长, 所以会收到来自父进程会话组长的信号.
setsid 用余新建一个会话, 调用这个函数之后会当当前进程成为进程组组长和会话组组长, 那么原来的会话产生的信号便不会发送到这个进程, 从而不会受影响.
五, daemon & 和守护进程的区别
六, 服务进程为什么要 fork 两次
首先说明两次不是必须的, 有很多程序都采用了一次 fork.
第一次: 为了调用 setsid, 这也解释了为什么调用 setsid 之前需要先 fork 的原因:
Linux 规定调用这个函数之前, 当前进程不允许是 session leader. 进程组 leader 是该进程组的第一个进程, fork 出来的进程必定不是第一个, 所以可以调用 setsid. 另外父进程一般直接退出, 可以让 shell 收到进程结束的通知继续执行, 而不是等待他结束.
第二次: 为了限制进程打开控制终端, 只有会话组长能打开控制终端(非必须, 相当于加了个限制条件 Daemon 不需要打开终端)
七, systemd 管理 daemon
现在很多的 Linux 发行版都采用 systemd 来代替原来的 init 程序, systemd 提供了很优秀的进程管理功能, 我们需要注册服务时可以利用 systemd 功能, 可以参看鸟哥的 systemd 介绍.
另外补充点内核进程和 Systemd 进程:
0 号进程为内核进程, 1 号为 Systemd 进程, 其他还有些内核进程在 ps 命令查看时以 [] 包裹. 具体关系见: Linux PID 1 和 SYSTEMD https://coolshell.cn/articles/17998.html
八, 僵尸进程
这个定义摘抄自维基百科: 在类 UNIX 系统中, 僵尸进程是指完成执行 (通过 exit 系统调用, 或运行时发生致命错误或收到终止信号所致) 但在操作系统的进程表中仍然有一个表项(进程控制块 PCB), 处于 "终止状态" 的进程. 这发生于子进程需要保留表项以允许其父进程读取子进程的 exit status: 一旦退出态通过 wait 系统调用读取, 僵尸进程条目就从进程表中删除, 称之为 "回收(reaped)".
九, 进程名字和启动时指定进程名字
kill,ps,top,pstree 这些命令都比较熟悉就不再提了.
至于还有一组命令则不是通过进程号而是通过进程名字来操作进程, pkill 和 killall 一样都是通过名字来杀死进程, 而 pgrep 是通过名字来寻找进程.
他们的原理都是通过查找 / proc 这个内存文件系统.
在启动的时候可以通过 exec 命令重命名:
bash -c "exec -a myname sleep 500 &"
你可以通过 ps -ef|grep myname 来查看进程的详细信息
十, source command 和./command 和 exec 命令的区别
通常执行脚本有三种方式
- ./command(同 sh command)
- source command(同. command)
- exec command
简单说明下上面三种方式:
第一种其实就是对应了 Linux 的 fork 系统调用, 在执行 command 时候, command 是在子进程中执行的, 当前 shell 等待直到子进程的 command 运行完毕在返回到当前 shell. 第二种则是直接在当前的进程中直接执行, 执行完继续接受用户输入. 第三种则对应了 Linux 的 exec 系统调用, 当前进程的执行流程会转向 command,command 是在当前进程直接执行, 但是执行完之后便会直接退出.
所以我们一般用的是第一和第二两种, 这种的主要区别就是开不开新的进程(开进程是要一定开销的), 另外因为第二种是在当前进程执行的, 所以如果在 command 中设置了变量, 那么相当于在当前进程中设置了变量, 所以我们一般是用第一种去执行避免当前进程的变量被污染.
思考:
现在加入你在终端已经运行了一个非常耗时的任务, 你按 ctrl+z 放入了后台, 然后利用 bg 开始任务, 因为终端断开就会收到 SIGHUP 信号, 有没有办法忽略这个信号或者终端断开不收到这个信号?
遗留:
进程调试工具: ltrace strace ftrace
参考链接:
Linux 进程组和会话 https://my.oschina.net/hosee/blog/507098
在线 APUE 译文 http://zdyi.com/books/apue/
Linux 终端关闭时为什么会导致在其上启动的进程退出?
来源: https://www.cnblogs.com/chenfangzhi/p/10660355.html