千言万语,不如实验来的直接基于sleep的小实验???首先通过实验直观感受一下后台服务的运行状况(请注意,前方高能,相关概念在更后面才有解释)。在命令行上以不同方式执行sle 千
在命令行上以不同方式执行 sleep
确定登录 shell 和伪终端。 [root@YOYO ~]# ps ajxf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND... 0 1 1 1 ? -1 Ss 0 0:02 /sbin/init... 1 1654 1654 1654 ? -1 Ss 0 0:00 /usr/sbin/sshd 1654 15437 15437 15437 ? -1 Ss 0 0:00 \_ sshd: root@pts/0,pts/115437 15441 15441 15441 pts/0 15441 Ss+ 0 0:00 \_ -bash15437 16237 16237 16237 pts/1 16258 Ss 0 0:00 \_ -bash16237 16258 16258 16237 pts/1 16258 R+ 0 0:00 \_ ps ajxf...[root@YOYO ~]# 分别以后台方式(&)、setsid、nohup 和前台方式执行 sleep [root@YOYO ~]# jobs -l [root@YOYO ~]# [root@YOYO ~]# sleep 600 & -- 通过 & 后台运行 -- 1[1] 16261[root@YOYO ~]# [root@YOYO ~]# setsid sleep 660 -- 通过 setsid 后台运行 -- 2[root@YOYO ~]# [root@YOYO ~]# nohup sleep 720 & -- 通过 nohup + & 后台运行 -- 3[2] 16271[root@YOYO ~]# nohup: 忽略输入并把输出追加到"nohup.out"[root@YOYO ~]# [root@YOYO ~]# sleep 780 -- 前台运行 -- 4^Z -- 挂起[3]+ Stopped sleep 780[root@YOYO ~]# [root@YOYO ~]# jobs -l[1] 16261 Running sleep 600 &[2]- 16271 Running nohup sleep 720 &[3]+ 16274 停止 sleep 780[root@YOYO ~]# [root@YOYO ~]# bg 3 -- 放入后台运行[3]+ sleep 780 &[root@YOYO ~]# [root@YOYO ~]# jobs -l[1] 16261 Running sleep 600 &[2]- 16271 Running nohup sleep 720 &[3]+ 16274 Running sleep 780 &[root@YOYO ~]# 查看此时的进程关系 [root@YOYO ~]# ps ajxf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND... 0 1 1 1 ? -1 Ss 0 0:02 /sbin/init... 1 1654 1654 1654 ? -1 Ss 0 0:00 /usr/sbin/sshd 1654 15437 15437 15437 ? -1 Ss 0 0:00 \_ sshd: root@pts/0,pts/115437 15441 15441 15441 pts/0 15441 Ss+ 0 0:00 \_ -bash15437 16237 16237 16237 pts/1 16282 Ss 0 0:00 \_ -bash16237 16261 16261 16237 pts/1 16282 S 0 0:00 \_ sleep 600 -- 116237 16271 16271 16237 pts/1 16282 S 0 0:00 \_ sleep 720 -- 316237 16274 16274 16237 pts/1 16282 S 0 0:00 \_ sleep 780 -- 416237 16282 16282 16237 pts/1 16282 R+ 0 0:00 \_ ps ajxf... 1 16265 16265 16265 ? -1 Ss 0 0:00 sleep 660 -- 2[root@YOYO ~]# 叉掉 ssh 连接窗口,查看此时的 sleep 进程状态 [root@YOYO ~]# ps ajxf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND... 0 1 1 1 ? -1 Ss 0 0:02 /sbin/init... 1 1654 1654 1654 ? -1 Ss 0 0:00 /usr/sbin/sshd 1654 15437 15437 15437 ? -1 Ss 0 0:00 \_ sshd: root@pts/0 15437 15441 15441 15441 pts/0 16300 Ss 0 0:00 \_ -bash15441 16300 16300 15441 pts/0 16300 R+ 0 0:00 \_ ps ajxf... 1 16265 16265 16265 ? -1 Ss 0 0:00 sleep 660 -- 2 1 16271 16271 16237 ? -1 S 0 0:00 sleep 720 -- 3[root@YOYO ~]# 实验结论: 以不同方式启动进程,在 ssh 连接窗口被叉掉的时候会造成不同的影响。标号为 1 和 4 的两个进程都消失了,标号为 3 的进程有属性发生了变化,只有标号为 2 的进程没有任何改变。
在 shell 脚本中上以不同方式执行 sleep
测试一(前台进程组) [root@Betty ~]# vi test_1.sh #!/bin/sh sleep 600 # 会卡住当前 shell 脚本 [root@Betty ~]#[root@Betty ~]# ./test_1.sh(卡住) 在另一个窗口中查看 [root@Betty ~]# ps ajxf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND... 1 1860 1860 1860 ? -1 Ss 0 0:00 /usr/sbin/sshd 1860 13331 13331 13331 ? -1 Ss 0 0:00 \_ sshd: root@pts/1,pts/213331 16993 16993 16993 pts/1 18649 Ss 0 0:00 \_ -bash16993 18649 18649 16993 pts/1 18649 R+ 0 0:00 | \_ ps ajxf13331 18572 18572 18572 pts/2 18632 Ss 0 0:00 \_ -bash18572 18632 18632 18572 pts/2 18632 S+ 0 0:00 \_ /bin/sh ./test_1.sh18632 18633 18632 18572 pts/2 18632 S+ 0 0:00 \_ sleep 600 此时叉掉启动 test_1.sh 脚本的窗口,可以看到对应的进程全部消失。 [root@Betty ~]# ps ajxf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND... 1 1860 1860 1860 ? -1 Ss 0 0:00 /usr/sbin/sshd 1860 13331 13331 13331 ? -1 Ss 0 0:00 \_ sshd: root@pts/113331 16993 16993 16993 pts/1 18706 Ss 0 0:00 \_ -bash16993 18706 18706 16993 pts/1 18706 R+ 0 0:00 \_ ps ajxf 测试二(孤儿后台进程组) [root@Betty ~]# vi test_2.sh #!/bin/sh sleep 600 & # 不会卡住当前 shell 脚本,因为放在后台执行 [root@Betty ~]#[root@Betty ~]# ./test_2.sh[root@Betty ~]# 在另一个窗口中查看 [root@Betty ~]# ps ajxf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND... 1 1860 1860 1860 ? -1 Ss 0 0:00 /usr/sbin/sshd 1860 13331 13331 13331 ? -1 Ss 0 0:00 \_ sshd: root@pts/1,pts/013331 16993 16993 16993 pts/1 18778 Ss 0 0:00 \_ -bash16993 18778 18778 16993 pts/1 18778 R+ 0 0:00 | \_ ps ajxf13331 18734 18734 18734 pts/0 18734 Ss+ 0 0:00 \_ -bash ...1 18763 18762 18734 pts/0 18734 S 0 0:00 sleep 600 -- 对应后台执行 sleep 的进程,由于是后台执行, 所以不会卡住 test.sh 脚本的执行 test.sh 脚本执行结束后,与 test.sh 对应的进程 会自行退出,从而 sleep 进程被 init 进程收养 此时叉掉启动 test_2.sh 脚本的窗口,可以看到 sleep 600 对应进程的 TTY 和 TPGID 发生了变化,但进程并未消失。 [root@Betty ~]# ps ajxf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND... 1 1860 1860 1860 ? -1 Ss 0 0:00 /usr/sbin/sshd 1860 13331 13331 13331 ? -1 Ss 0 0:00 \_ sshd: root@pts/113331 16993 16993 16993 pts/1 18816 Ss 0 0:00 \_ -bash16993 18816 18816 16993 pts/1 18816 R+ 0 0:00 \_ ps ajxf... 1 18763 18762 18734 ? -1 S 0 0:00 sleep 600 测试三(前台进程组) [root@Betty ~]# vi test_3.sh #!/bin/sh sleep 600 &sleep 720 [root@Betty ~]#[root@Betty ~]# ./test_3.sh(卡住) 在另一个窗口中查看 [root@Betty ~]# ps ajxf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND... 1 1860 1860 1860 ? -1 Ss 0 0:00 /usr/sbin/sshd 1860 13331 13331 13331 ? -1 Ss 0 0:00 \_ sshd: root@pts/1,pts/213331 16993 16993 16993 pts/1 18918 Ss 0 0:00 \_ -bash16993 18918 18918 16993 pts/1 18918 R+ 0 0:00 | \_ ps ajxf13331 18856 18856 18856 pts/2 18908 Ss 0 0:00 \_ -bash18856 18908 18908 18856 pts/2 18908 S+ 0 0:00 \_ /bin/sh ./test_3.sh18908 18909 18908 18856 pts/2 18908 S+ 0 0:00 \_ sleep 60018908 18910 18908 18856 pts/2 18908 S+ 0 0:00 \_ sleep 720...[root@Betty ~]# 此时叉掉启动 test_3.sh 脚本的窗口,可以看到对应的进程全部消失。 [root@Betty ~]# ps ajxf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND... 1 1860 1860 1860 ? -1 Ss 0 0:00 /usr/sbin/sshd 1860 13331 13331 13331 ? -1 Ss 0 0:00 \_ sshd: root@pts/113331 16993 16993 16993 pts/1 18963 Ss 0 0:00 \_ -bash16993 18963 18963 16993 pts/1 18963 R+ 0 0:00 \_ ps ajxf...[root@Betty ~]# 测试四(后台进程组) [root@Betty ~]# cat test_1.sh #!/bin/shsleep 600 [root@Betty ~]# [root@Betty ~]# [root@Betty ~]# ./test_1.sh & -- 后台执行该脚本[1] 19016[root@Betty ~]# 在另外一个窗口中查看 [root@Betty ~]# ps ajxf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND... 1 1860 1860 1860 ? -1 Ss 0 0:00 /usr/sbin/sshd 1860 13331 13331 13331 ? -1 Ss 0 0:00 \_ sshd: root@pts/1,pts/013331 16993 16993 16993 pts/1 19032 Ss 0 0:00 \_ -bash16993 19032 19032 16993 pts/1 19032 R+ 0 0:00 | \_ ps ajxf13331 18993 18993 18993 pts/0 18993 Ss+ 0 0:00 \_ -bash18993 19016 19016 18993 pts/0 18993 S 0 0:00 \_ /bin/sh ./test_1.sh19016 19017 19016 18993 pts/0 18993 S 0 0:00 \_ sleep 600...[root@Betty ~]# 此时叉掉后台启动 test_1.sh 脚本的窗口,可以看到对应的进程全部消失。 [root@Betty ~]# ps ajxf PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND... 1 1860 1860 1860 ? -1 Ss 0 0:00 /usr/sbin/sshd 1860 13331 13331 13331 ? -1 Ss 0 0:00 \_ sshd: root@pts/113331 16993 16993 16993 pts/1 19052 Ss 0 0:00 \_ -bash16993 19052 19052 16993 pts/1 19052 R+ 0 0:00 \_ ps ajxf...[root@Betty ~]# 实验结论:- 如果 shell 脚本中存在前台执行的命令,则在其未执行结束前,会“卡住”当前 shell 脚本对应的进程,进而“卡住”bash 进程。即整个进程树在 ps ajxf 中都会作为前台进程组显示。
- & 的使用在 shell 脚本内外会产生不同的效果,在 shell 脚本内可以产生孤儿进程组(前提是没有其他命令的执行导致 shell 脚本无法退出),在脚本外则产生普通的后台进程组;
- 对于孤儿进程组和普通后台进程组 SIGHUP 信号在处理细节上是不同的;
相关概念
要想理解上面的实验结果,首先必须理解如下一些概念: 【进程组】- 一个或多个进程的集合。通常与同一作业(job)相关联。
- 每个进程组都可以有一个组长进程,组长进程的特征是“进程组 ID 等于其进程 ID”,即PGID = PID。
- 组长进程自身可以在创建一个进程组后,再创建该进程组中的其他进程,然后终止自己。
- 只要在某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。
- 进程可以通过调用 setpgid 来加入一个现有的进程组或者创建一个新进程组(也可以通过 setsid 创建一个新的进程组)。
- 一个进程只能为它自己或它的子进程设置进程组 ID。
- POSIX.1 引入会话(session)的概念。
- 登录 shell 是一个会话的开始,而终端或伪终端则是会话的控制终端。
- 会话是一个或多个进程组的集合。
- 进程通过调用 setsid 函数建立一个新会话。
- 如果调用 setsid 函数的进程不是一个进程组的组长,则调用 setsid 函数就会创建一个新会话,同时发生下面 3 件事, (a) 该进程变成会话首进程(session leader)(会话首进程是创建该会话的进程); (b) 该进程成为一个新进程组的组长进程。新进程组 ID 是该调用进程的进程 ID; (c) 该进程没有控制终端,如果在调用 setsid 之前,该进程有一个控制终端,那么这种联系也会被中断。
- 如果调用此函数的进程已经是一个进程组的组长,则此函数返回出错。为了保证不会发生这种情况,通常先调用 fork,然后使其父进程终止,而子进程则继续。因为子进程继承了父进程的进程组 ID,而其进程 ID 则是新分配的,两者不可能相等,所以就保证了子进程不会是一个进程组的组长。
- 会话首进程总是一个进程组的组长进程,所以两者是等价的。所以可以认为 会话首进程ID = 会话ID = 进程组ID = 进程组组长ID
- 一个会话中包含的多个进程组可以被分成一个前台进程组(foreground process group)以及一个或几个后台进程组(background process group)。
- 一个会话可以有一个控制终端(controlling terminal),这通常是登陆到其上的终端设备(终端登陆)或伪终端设备(网络登录)。
- 只有建立与控制终端连接的会话首进程被称为控制进程(controlling process)。
- 如果一个会话有一个控制终端,则它有一个前台进程组,会话中的其他进程组则为后台进程组。
- 无论何时键入终端的中断键(Ctrl+C),就会将中断信号 SIGINT 发送给前台进程组中的所有进程;
- 无论何时键入终端的退出键(Ctrl+\),就会将退出信号 SIGQUIT 发送给前台进程组中的所有进程;
- 如果终端接口检测到调制解调器或网路已经断开连接,则将挂断信号 SIGHUP 发送给控制进程(会话首进程)。
- 如果在调用setsid之前某进程有一个控制终端,那么在调用后该控制终端会被中断。
- SIGTTOU - 后台作业试图输出到控制终端,若用户设置了禁止后台作业写到控制终端(stty tostop),终端驱动程序会将该写操作标识为来自于后台进程,会向其发送该信号。
- SIGTTIN - 后台作业试图读取控制终端,终端驱动程序发现后,会向后台作业发送该信号。
- SIGTSTP - 键入Ctrl+Z挂起键与终端驱动程序进行交互,令其将该信号送至前台进程组中的所有进程,后台进程组作业不受影响。
- SIGHUP - Hangup detected on controlling terminal or death of controlling process (signal(7))
- 守护进程也称为 daemon,是生存期较长的一种进程。常常在系统自举时启动,仅在系统关闭时才终止。没有控制终端,在后台运行。
- 大多数守护进程都以超级用户(UID 为 0)特权运行。
- 守护进程没有控制终端,ps 输出时,其控制终端显示为问号(?),前台进程组 ID 为 -1。
- 内核守护进程一般会以无控制终端方式启动;而用户实现的守护进程若没有控制终端,则可能是因为创建守护进程时调用了 setsid。
- 大多数守护进程的父进程是 init 进程。
- 一个其父进程已经终止的进程称为孤儿进程,这种进程会被 init 进程“收养”;
- POSIX.1 将孤儿进程组定义为:该进程组中每个成员的父进程要么是该组的一个成员,要么不是该组所属会话的成员;
- 对孤儿进程组的另一种描述为:一个进程组不是孤儿进程组的条件是,该组中有一个进程,其父进程在属于同一会话的另一个组中。
- 若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到 SIGSTOP 或 SIGTSTP 信号后被挂起),信号 SIGHUP 会被发送到该进程组中的每一个被挂起的进程。
进程消失的原因--SIGHUP
之前整理了一篇关于 SIGHUP 信号的博文,下面给出一些结论:- 系统对 SIGHUP 信号的默认处理是终止收到该信号的进程。所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出;
- 终端(或伪终端)被关闭时,信号 SIGHUP 会被内核发送到具有控制终端的会话的会话首进程;
- 会话首进程退出前,信号 SIGHUP 会被内核发送到当前会话中的前台进程组中的每一个进程;
- bash 收到 SIGHUP 时,会给其下运行的各个作业(包括前后台)发送 SIGHUP,然后自己退出;
- 前后台的各个作业收到来自 bash 的 SIGHUP 后将退出(如果存在针对 SIGHUP 的处理,就不会退出)
基于strace研究各种运行方式的差别
既然知道了进程消失是因为 SIGHUP 信号导致,那么就可以通过 strace 观察各种运行方式下,都做了哪些相关处理。 跟踪前台运行,可以看到其中没有针对 SIGHUP 信号做任何处理。 [root@YOYO ~]# strace sleep 10execve("/bin/sleep", ["sleep", "10"], [/* 28 vars */]) = 0brk(0) = 0xafa000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe54ec09000access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=70566, ...}) = 0mmap(NULL, 70566, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe54ebf7000close(3) = 0open("/lib64/libc.so.6", O_RDONLY) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\356\0018?\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=1926760, ...}) = 0mmap(0x3f38000000, 3750152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3f38000000mprotect(0x3f3818a000, 2097152, PROT_NONE) = 0mmap(0x3f3838a000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x3f3838a000mmap(0x3f3838f000, 18696, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x3f3838f000close(3) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe54ebf6000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe54ebf5000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe54ebf4000arch_prctl(ARCH_SET_FS, 0x7fe54ebf5700) = 0mprotect(0x3f3838a000, 16384, PROT_READ) = 0mprotect(0x3f37a1f000, 4096, PROT_READ) = 0munmap(0x7fe54ebf7000, 70566) = 0brk(0) = 0xafa000brk(0xb1b000) = 0xb1b000open("/usr/lib/locale/locale-archive", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=99154480, ...}) = 0mmap(NULL, 99154480, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe548d64000close(3) = 0nanosleep({10, 0}, NULL) = 0 -- 对应 sleep 10close(1) = 0close(2) = 0exit_group(0) = ?[root@YOYO ~]# 跟踪后台运行,可以看到其中同样没有针对SIGHUP信号做任何处理。 [root@YOYO ~]# strace sleep 10 &[1] 2727[root@YOYO ~]# execve("/bin/sleep", ["sleep", "10"], [/* 28 vars */]) = 0brk(0) = 0x1406000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff18790f000access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=70566, ...}) = 0mmap(NULL, 70566, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ff1878fd000close(3) = 0open("/lib64/libc.so.6", O_RDONLY) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\356\0018?\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=1926760, ...}) = 0mmap(0x3f38000000, 3750152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3f38000000mprotect(0x3f3818a000, 2097152, PROT_NONE) = 0mmap(0x3f3838a000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x3f3838a000mmap(0x3f3838f000, 18696, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x3f3838f000close(3) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff1878fc000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff1878fb000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff1878fa000arch_prctl(ARCH_SET_FS, 0x7ff1878fb700) = 0mprotect(0x3f3838a000, 16384, PROT_READ) = 0mprotect(0x3f37a1f000, 4096, PROT_READ) = 0munmap(0x7ff1878fd000, 70566) = 0brk(0) = 0x1406000brk(0x1427000) = 0x1427000open("/usr/lib/locale/locale-archive", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=99154480, ...}) = 0mmap(NULL, 99154480, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ff181a6a000close(3) = 0nanosleep({10, 0}, NULL) = 0 -- 对应 sleep 10close(1) = 0close(2) = 0exit_group(0) = ?[1]+ Done strace sleep 10[root@YOYO ~]# 跟踪 setsid 的使用,可以看到其中同样没有针对 SIGHUP 信号做任何处理(通过 setsid 执行后不会退出的原因后续再说明)。 [root@YOYO ~]# strace setsid sleep 10 execve("/usr/bin/setsid", ["setsid", "sleep", "10"], [/* 28 vars */]) = 0brk(0) = 0x1274000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2dc78d4000access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=70566, ...}) = 0mmap(NULL, 70566, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2dc78c2000close(3) = 0open("/lib64/libc.so.6", O_RDONLY) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\356\0018?\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=1926760, ...}) = 0mmap(0x3f38000000, 3750152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3f38000000mprotect(0x3f3818a000, 2097152, PROT_NONE) = 0mmap(0x3f3838a000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x3f3838a000mmap(0x3f3838f000, 18696, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x3f3838f000close(3) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2dc78c1000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2dc78c0000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2dc78bf000arch_prctl(ARCH_SET_FS, 0x7f2dc78c0700) = 0mprotect(0x3f3838a000, 16384, PROT_READ) = 0mprotect(0x3f37a1f000, 4096, PROT_READ) = 0munmap(0x7f2dc78c2000, 70566) = 0brk(0) = 0x1274000brk(0x1295000) = 0x1295000open("/usr/lib/locale/locale-archive", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=99154480, ...}) = 0mmap(NULL, 99154480, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2dc1a2f000close(3) = 0getpgrp() = 2743 -- 获取进程组 idgetpid() = 2744 -- 获取进程 idsetsid() = 2744 -- 创建新的会话execve("/usr/lib64/qt-3.3/bin/sleep", ["sleep", "10"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)execve("/usr/local/sbin/sleep", ["sleep", "10"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)execve("/usr/local/bin/sleep", ["sleep", "10"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)execve("/sbin/sleep", ["sleep", "10"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)execve("/bin/sleep", ["sleep", "10"], [/* 28 vars */]) = 0brk(0) = 0x1f96000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f60207fc000access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=70566, ...}) = 0mmap(NULL, 70566, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f60207ea000close(3) = 0open("/lib64/libc.so.6", O_RDONLY) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\356\0018?\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=1926760, ...}) = 0mmap(0x3f38000000, 3750152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3f38000000mprotect(0x3f3818a000, 2097152, PROT_NONE) = 0mmap(0x3f3838a000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x3f3838a000mmap(0x3f3838f000, 18696, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x3f3838f000close(3) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f60207e9000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f60207e8000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f60207e7000arch_prctl(ARCH_SET_FS, 0x7f60207e8700) = 0mprotect(0x3f3838a000, 16384, PROT_READ) = 0mprotect(0x3f37a1f000, 4096, PROT_READ) = 0munmap(0x7f60207ea000, 70566) = 0brk(0) = 0x1f96000brk(0x1fb7000) = 0x1fb7000open("/usr/lib/locale/locale-archive", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=99154480, ...}) = 0mmap(NULL, 99154480, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f601a957000close(3) = 0nanosleep({10, 0}, NULL) = 0 -- 对应 sleep 10close(1) = 0close(2) = 0exit_group(0) = ?[root@YOYO ~]# 跟踪 nohup 的使用,可以看到内部设置了对 SIGHUP 信号的忽略处理。 [root@YOYO ~]# strace nohup sleep 10 &[1] 763[root@YOYO ~]# execve("/usr/bin/nohup", ["nohup", "sleep", "10"], [/* 28 vars */]) = 0brk(0) = 0x138b000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f41c123d000access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=70566, ...}) = 0mmap(NULL, 70566, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f41c122b000close(3) = 0open("/lib64/libc.so.6", O_RDONLY) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\356\0018?\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=1926760, ...}) = 0mmap(0x3f38000000, 3750152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3f38000000mprotect(0x3f3818a000, 2097152, PROT_NONE) = 0mmap(0x3f3838a000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x3f3838a000mmap(0x3f3838f000, 18696, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x3f3838f000close(3) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f41c122a000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f41c1229000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f41c1228000arch_prctl(ARCH_SET_FS, 0x7f41c1229700) = 0mprotect(0x3f3838a000, 16384, PROT_READ) = 0mprotect(0x3f37a1f000, 4096, PROT_READ) = 0munmap(0x7f41c122b000, 70566) = 0brk(0) = 0x138b000brk(0x13ac000) = 0x13ac000open("/usr/lib/locale/locale-archive", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=99154480, ...}) = 0mmap(NULL, 99154480, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f41bb398000close(3) = 0ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig -icanon -echo ...}) = 0ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig -icanon -echo ...}) = 0ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig -icanon -echo ...}) = 0open("/dev/null", O_WRONLY) = 3 -- 以“只写”权限打开 /dev/null dup2(3, 0) = 0 -- 将标准输入重定向到 /dev/nullclose(3) = 0umask(037777777177) = 022open("nohup.out", O_WRONLY|O_CREAT|O_APPEND, 0600) = 3 -- 打开 nohup.out 文件dup2(3, 1) = 1 -- 将标准输出重定向到 nohup.outclose(3) = 0umask(022) = 0177open("/usr/share/locale/locale.alias", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=2512, ...}) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f41c123c000read(3, "# Locale name alias data base.\n#"..., 4096) = 2512read(3, "", 4096) = 0close(3) = 0munmap(0x7f41c123c000, 4096) = 0open("/usr/share/locale/zh_CN.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)open("/usr/share/locale/zh_CN.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)open("/usr/share/locale/zh_CN/LC_MESSAGES/coreutils.mo", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=286636, ...}) = 0mmap(NULL, 286636, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f41bb352000close(3) = 0open("/usr/lib64/gconv/gconv-modules.cache", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=26060, ...}) = 0mmap(NULL, 26060, PROT_READ, MAP_SHARED, 3, 0) = 0x7f41bb34b000close(3) = 0write(2, "nohup: ", 7nohup: ) = 7write(2, "\345\277\275\347\225\245\350\276\223\345\205\245\345\271\266\346\212\212\350\276\223\345\207\272\350\277\275\345\212\240\345\210"..., 44忽略输入并把输出追加到"nohup.out") = 44write(2, "\n", 1) = 1fcntl(2, F_DUPFD, 3) = 3fcntl(3, F_GETFD) = 0fcntl(3, F_SETFD, FD_CLOEXEC) = 0dup2(1, 2) = 2 -- 将标准出错重定向到 nohuo.outrt_sigaction(SIGHUP, {SIG_IGN, [HUP], SA_RESTORER|SA_RESTART, 0x3f380326a0}, {SIG_DFL, [], 0}, 8) = 0 -- 设置 SIGHUP 信号处理函数为 SIG_IGNexecve("/usr/lib64/qt-3.3/bin/sleep", ["sleep", "10"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)execve("/usr/local/sbin/sleep", ["sleep", "10"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)execve("/usr/local/bin/sleep", ["sleep", "10"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)execve("/sbin/sleep", ["sleep", "10"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)execve("/bin/sleep", ["sleep", "10"], [/* 28 vars */]) = 0brk(0) = 0x86a000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc801ecc000access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=70566, ...}) = 0mmap(NULL, 70566, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc801eba000close(3) = 0open("/lib64/libc.so.6", O_RDONLY) = 3read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\356\0018?\0\0\0"..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=1926760, ...}) = 0mmap(0x3f38000000, 3750152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x3f38000000mprotect(0x3f3818a000, 2097152, PROT_NONE) = 0mmap(0x3f3838a000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x3f3838a000mmap(0x3f3838f000, 18696, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x3f3838f000close(3) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc801eb9000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc801eb8000mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc801eb7000arch_prctl(ARCH_SET_FS, 0x7fc801eb8700) = 0mprotect(0x3f3838a000, 16384, PROT_READ) = 0mprotect(0x3f37a1f000, 4096, PROT_READ) = 0munmap(0x7fc801eba000, 70566) = 0brk(0) = 0x86a000brk(0x88b000) = 0x88b000open("/usr/lib/locale/locale-archive", O_RDONLY) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=99154480, ...}) = 0mmap(NULL, 99154480, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc7fc027000close(3) = 0nanosleep({10, 0}, NULL) = 0 -- 对应 sleep 10close(1) = 0close(2) = 0exit_group(0) = ?[1]+ Done strace nohup sleep 10[root@YOYO ~]#setsid 和 nohup 的源码实现
通过上面的 strace 输出没有看出为何通过 setsid 启动程序不会因为 SIGHUP 而退出(但从理论上讲,我们知道是因为创建的进程没有控制终端的缘故)。下面看一下这两命令的源码实现。 下面是 setsid 的核心源码(取自 util-linux-2.26) 下面给出 nohup 的核心源码(取自 coreutils-8.24) 可以看到,源码实现中的逻辑与 strace 看到的内容完全对应上了。 部署工具脚本中的问题 基于以上的内容,就可以很容易发现或解释我们实际使用中的脚本存在哪些问题(公司内容,略)。 更多参考 1.《SIGHUP问题梳理》 2.《Bg, Fg, &, Ctrl-Z – 5 Examples to Manage Unix Background Jobs》【原创】Linux后台服务相关问题总结