文档章节

【整理】SIGHUP问题梳理

摩云飞
 摩云飞
发布于 2015/08/08 16:23
字数 3199
阅读 1807
收藏 5

本文梳理了网络上找到的一些和 SIGHUP 问题相关的资料,并根据自己的理解,对其中若干说法进行了修正。

原文出处:
1.《关闭终端后,后台作业退出的分析
2.《后台进程退出时,关于SIGHUP信号的讨论
3.《SIGHUP信号与控制终端
4.《为什么linux下sshd被kill会导致所有子进程被终止
5.《where is SIGHUP from? (sshd forks a child to create a new session, kill this child and all processes in the session dies)》

 

==== 我是火影两周年纪念分割线 ====

 

《关闭终端后,后台作业退出的分析》

先把结论写在上面:

  • 内核驱动发现终端(或伪终端)关闭,给终端对应的控制进程(bash)发 SIGHUP  
  • bash 收到 SIGHUP 后,会给各个作业(包括前后台)发送 SIGHUP,然后自己退出
  • 前后台的各个作业收到来自 bash 的 SIGHUP 后退出(如果程序会处理 SIGHUP,就不会退出)

PS: 这里所谓终端关闭就是指内核感知不到终端了,例如远程登录时的网络断开、sshd 挂掉、手动叉掉 ssh 登陆窗口之类的情况也算在内。

PPS: SIGHUP 会在以下情况发出

  • 终端关闭时,该信号被内核发送到 session 首进程(比如登陆 shell 进程,即上面说的 bash 进程)
  • session 首进程退出前,该信号被内核发送到该 session 中的前台进程组中的每一个进程(是内核发的?还是会话首进程发的?如果是内核发的,那么在 session 首进程为 bash 的情况下,前台进程组不是会收到两次 SIGHUP ?此问题后面作了回答
  • 若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到过 SIGSTOP 或 SIGTSTP 信号后被挂起),该信号会被发送到该进程组中的每一个进程(如果孤儿进程组中没有进程被挂起,难道就不发了?是发给每一个子进程,还是只发给被挂起的子进程?)。

PPPS: bash 收到 SIGHUP 后会转发给各个任务(job)

查看 bash 的手册,终于看到

The shell exits by default upon receipt of a SIGHUP. Before exiting, an interactive shell resends the SIGHUP to all jobs, running or stopped. Stopped jobs are sent SIGCONT to ensure that they receive the SIGHUP.

真相接近大白:原来是 bash 在收到内核 driver 发出的 SIGHUP 后,转发的 !

 

==== 我是火影两周年纪念分割线 ====

 

《为什么linux下sshd被kill会导致所有子进程被终止》

问题描述:
使用 SecureCRT 远程登陆,打开两个终端,在其中一个(终端A)运行测试程序 a.out;在另一个(终端B)中使用 pstree 可以看到

|-sshd(2555)-+-sshd(16568)---bash(16572)---pstree(16862)
        |            `-sshd(16635)---bash(16637)---a.out(16860)---a.out(16861)

如果在终端 B 中运行 kill -9 16635 后,则标红的这些进程全会终止掉;
使用 logout、exit 命令也会断开连接,但是 a.out(在后台进程组)会被 init 收管,并不会挂掉。
请教各方高人,这是为何?

PS: 粗略看了一下 sshd 的实现代码,和 sshd 自身应该没什么关系,父进程自己都挂了怎么会来得及处理子进程, 莫非在 kill -9 16635 的时候,内核会检测将要结束的进程是 sshd ,然后干掉他的子进程?

费解啊!费解

自己来结贴
google 上找了好久,又看了看 APUE,大致得出了结论

  1. ssh 远程登录时,实际上是伪终端(网络连接断开、kill 掉 sshd 实际上都被内核感知为关闭终端);
  2. 终端被关闭后,内核会给与之对应的会话首进程发送 SIGHUP ,会话首进程如果挂了,内核会给该会话下的所有前台进程组发送 SIGHUP(前台进程组只会有一个,这里用“所有”不太好),而后台进程组,如果没有进程处于停止状态,是不会收到来自内核的 SIGHUP 的(这里的说法保留意见);
  3. bash 在收到 SIGHUP 后会退出,但在退出前,会给各个作业(jobs,即前后台进程组)发送 SIGHUP 信号;

所以上面 kill -9 16635 后:

  1. 内核发现连接断开(伪终端关闭) 
  2. 给对应的控制进程(bash)发 SIGHUP 
  3. bash 收到 SIGHUP 后,给各个作业发送 SIGHUP ,然后自己退出
  4. a.out 收到来自 bash 的 SIGHUP,退出

 

==== 我是火影两周年纪念分割线 ====

 

where is SIGHUP from? (sshd forks a child to create a new session, kill this child and all processes in the session dies)

ssh (along with terminal emulators, screen, tmux, script, and some other programs) uses a thing called a "pseudo-tty" (or "pty"), which behaves like a dialup modem connection. I describe it that way because that's the historical origin of this behavior: if you lost your modem connection for some reason, the tty (or pty) driver detected the loss of carrier and sent SIGHUP ("Hangup") to your session. This enables programs to save their state (for example, vi/vim will save any files you had modified but not saved for recovery) and shut down cleanly. Similarly, if the network connection goes away for some reason (someone tripped over the power or network cable? ...or sssh dumped core for some odd reason), the pty sends SIGHUP to your session so it gets a chance to save any unsaved data.

Technically, the tty/pty driver sends the signal to every process in the process group attached to the terminal (process groups are also related to shell job control, but this was their original purpose). Some other terminal signals are handled the same way, for example Ctrl + C sends SIGINT and Ctrl + \ sends SIGQUIT (and Ctrl + Z sends SIGTSTP, and programs that don't handle SIGTSTP by suspending themselves are sent SIGSTOP; this double signal allows vim to set the terminal back from editing mode to normal mode and in many terminal emulators swap to the pre-editing screen buffer).

 

==== 我是火影两周年纪念分割线 ====

 

《后台进程退出时,关于SIGHUP信号的讨论》

问题的提出:
  我一直纠结于一个问题。在 Linux 上,我用“command &”启动了一个后台进程。如果这个后台进程既没有 nohup, 又没有 setsid,也没有 disown 的话(如下程序),那么这个进程在终端被关闭时,会被某个信号 kill 。 我的问题是: 该进程会被谁 kill; 谁发的信号;

 

[root@xxx ~]# cat test.sh

#!/bin/bash

sleep 300

[root@xxx ~]# 
[root@xxx ~]# ./test.sh &

APUE (中文第二版 9.6节)

如果终端接口检测到调制解调器(或网络)已经断开连接,则将挂掉信号发送给控制进程(会话首进程)。

(原文中给出是简版,下面是完整版)

[root@Betty ~]# man bash
...
INVOCATION
       A login shell is one whose first character of argument zero is a -, or one started with the --login option.


       An  interactive  shell  is  one  started  without  non-option  arguments and without the -c option whose standard input and error are both connected to terminals (as determined by
       isatty(3)), or one started with the -i option.  PS1 is set and $- includes i if bash is interactive, allowing a shell script or a startup file to test this state.
...
SIGNALS
       When  bash  is interactive, in the absence of any traps, it ignores SIGTERM (so that kill 0 does not kill an interactive shell), and SIGINT is caught and handled (so that the wait
       builtin is interruptible).  In all cases, bash ignores SIGQUIT.  If job control is in effect, bash ignores SIGTTIN, SIGTTOU, and SIGTSTP.


       Non-builtin commands run by bash have signal handlers set to the values inherited by the shell from its parent.  When job control is not in effect,  asynchronous  commands  ignore
       SIGINT  and  SIGQUIT in addition to these inherited handlers.  Commands run as a result of command substitution ignore the keyboard-generated job control signals SIGTTIN, SIGTTOU,
       and SIGTSTP.


       The shell exits by default upon receipt of a SIGHUP.  Before exiting, an interactive shell resends the SIGHUP to all jobs, running or stopped.  Stopped jobs are  sent  SIGCONT  to
       ensure that they receive the SIGHUP.  To prevent the shell from sending the signal to a particular job, it should be removed from the jobs table with the disown builtin (see SHELL
       BUILTIN COMMANDS below) or marked to not receive SIGHUP using disown -h.


       If the huponexit shell option has been set with shopt, bash sends a SIGHUP to all jobs when an interactive login shell exits.


       If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes.  When bash is  waiting
       for  an  asynchronous  command  via the wait builtin, the reception of a signal for which a trap has been set will cause the wait builtin to return immediately with an exit status
       greater than 128, immediately after which the trap is executed.
...

APUE 和 Bash manpage 肯定是权威资料。第三条资料是某网友的分析,也很好。如下是该网友的结论(纯属转载):

  • 内核驱动发现终端(或伪终端)关闭,给对应终端的控制进程(bash)发 SIGHUP  
  • bash 收到 SIGHUP 后,会给各个作业(包括前后台)发送 SIGHUP,然后自己退出

 

==== 我是火影两周年纪念分割线 ====


《SIGHUP信号与控制终端》

unix 中进程组织结构为(一个)session 中(可能会)包含一个前台进程组及一个或多个后台进程组,一个进程组(可能)包含多个进程。
一个 session 中(最终)可能会包含 session 首进程(也可能不包含,即已经退出),而一个 session 可能会拥有一个控制终端。
一个进程组中可能会包含进程组组长进程(也可能不包含,即已经退出)。进程组组长进程的进程 ID 与该进程组 ID 相等。
与终端交互的进程是前台进程,否则便是后台进程。

SIGHUP 会在以下 3 种情况下被发送给相应的进程:

 

  1. 终端关闭时,该信号被发送到 session 首进程以及作为 job 提交的(后台)进程(即用 & 符号提交的进程)
  2. session 首进程退出时,该信号被发送到该 session 中的前台进程组中的每一个进程
  3. 若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到 SIGSTOP 或 SIGTSTP 信号后被挂起),该信号会被发送到该进程组中的每一个进程(这里说的有问题,应该是发送给被挂起的进程)。

系统对 SIGHUP 信号的默认处理是终止收到该信号的进程。所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出。

下面观察几种因终端关闭导致进程退出的情况,在这儿进程退出是因为收到了 SIGHUP 信号。login shell 是 session 首进程。
首先写一个测试程序,代码如下:

#include <stdio.h>
#include <signal.h>
char **args;
void exithandle(int sig)
{
    printf("%s : sighup received\n",args[1]);
}
int main(int argc,char **argv)
{
    args=argv;
    signal(SIGHUP,exithandle);
    pause();
    return 0;
}

程序中捕捉 SIGHUP 信号后打印一条信息,pause() 使程序暂停。
编译后的执行文件为 sigtest 。

1、
   命令: sigtest front > tt.txt
   操作: 关闭终端
   结果: tt文件的内容为 front : sighup received
   原因:  sigtest是前台进程,终端关闭后,根据上面提到的第1种情况,
          login shell作为session首进程,会收到SIGHUP信号然后退出,
          根据第2种情况,sigtest作为前台进程,
          会收到login shell发出的SIGHUP信号。

2、
   命令:sigtest back > tt.txt &
   操作: 关闭终端
   结果: tt文件的内容为 back : sighup received
   原因:  sigtest是提交的job,根据上面提到的第1种情况,
          sigtest会收到SIGHUP信号
3、
   写一个 shell,内容为

./sigtest &

   执行该 shell
   操作:关闭终端
   结果:ps -ef | grep sigtest 会看到该进程还在,tt文件为空
   原因: 执行该shell时,sigtest作为job提交,然后该shell退出,
         致使sigtest变成了孤儿进程,(从这里开始就是错误的)不再是当前session的job了,
         因此sigtest既不是session首进程也不是job,不会收到SIGHUP
         同时孤儿进程属于后台进程,因此login shell退出后不会发送SIGHUP
         给sigtest,因为它只将该信号发送给前台进程(一直错到这里)。
         第3条说过若进程组变成孤儿进程组的时候,若有进程处于停止状态,
         也会收到SIGHUP信号,但sigtest没有处于停止状态,
         所以不会收到SIGHUP信号  

4、
   命令:nohup sigtest > tt
   操作: 关闭终端 
   结果: tt 文件为空
   原因:  nohup 可以防止进程收到 SIGHUP 信号

至此,我们就清楚了何种情况下终端关闭后进程会退出,何种情况下不会退出。
要想终端关闭后进程不退出有以下几种方法,均为通过 shell 的方式:
1、 编写 shell,内容如下

trap "" SIGHUP  #该句的作用是屏蔽SIGHUP信号,trap可以屏蔽很多信号
sigtest

2、nohup sigtest 可以直接在命令行执行, 若想做完该操作后继续别的操作,可以 nohup sigtest &
3、编写 shell,内容如下

sigtest &

其实任何将进程变为孤儿进程的方式都可以,包括 fork 后父进程马上退出

另一个人的回答:

在这个环境下 GNU bash, version 3.2.25 ,前台进程,终端关闭后,进程会收到 2 次 SIGHUP 信号,一次为 bash 退出前由 bash 自己发送,一次为会话首进程(同样是 bash)退出后由 kernel 发送。

 

© 著作权归作者所有

共有 人打赏支持
摩云飞
粉丝 371
博文 534
码字总数 952694
作品 0
徐汇
程序员
私信 提问
Nohup源码分析

在我们日常工作中, 总是不可避免的需要将进程放置后台运行, 于是我们就会使用& 或者nohup ... &, 我们有时会疑虑, 其实为什么多余添加一个nohup, 于是就是谷歌/百度, 然后就会得出一个答案:...

Lin_R
2016/12/04
63
0
玩转 Linux 之:由 Nginx log rotation 聊聊 mv 的妙用

1、Nginx 下如何正确的做日志切分 今天发现有个 Nginx 日志 rotation 出来大小是 0,很奇怪,按公司的业务场景来说,这是不可能的。 瞅了下前同事留下来的 rotation 脚本,看到了这么两行,也...

大数据之路
2013/12/24
0
1
Tomcat进程意外退出的问题分析

节前某个部门的测试环境反馈tomcat会意外退出,我们到实际环境排查后发现不是jvm crash,日志里有进程销毁的记录,从pause到destory的整个过程: org.apache.coyote.AbstractProtocol pause...

陶邦仁
2015/03/24
0
1
Linux中nohup与&启动程序的区别

在Linux中可以使用以下2个命令让程序以job的方式在后端运行,以便让出终端来干其他事情。那么nohup 与& 有什么区别呢? 当关闭终端时,shell默认会发送SIGHUP信号给与该终端关联的进程,从而...

暮色伊人
2017/12/28
0
0
2017-04-28

学习内容:整理问题文档及交代其他工作 完成内容:问题文档基本的问题解决 工作问题:登录页面改成提示信息 alert去掉 用-html并不好使 需写拦截器 未完成:后台总商城中心问题没有梳理...

Full_Session
2017/04/28
1
0

没有更多内容

加载失败,请刷新页面

加载更多

租房软件隐私保护如同虚设

近日,苏州市民赵先生向江苏新闻广播新闻热线025-84658888反映,他在“安居客”手机应用软件上浏览二手房信息,并且使用该软件自动生成的虚拟号码向当地一家中介公司进行咨询。可电话刚挂不久...

linux-tao
15分钟前
0
0
分布式项目(五)iot-pgsql

书接上回,在Mapping server中,我们已经把数据都整理好了,现在利用postgresql存储历史数据。 iot-pgsql 构建iot-pgsql模块,这里我们写数据库为了性能考虑不在使用mybatis,换成spring jd...

lelinked
今天
2
0
一文分析java基础面试题中易出错考点

前言 这篇文章主要针对的是笔试题中出现的通过查看代码执行结果选择正确答案题材。 正式进入题目内容: 1、(单选题)下面代码的输出结果是什么? public class Base { private Strin...

一看就喷亏的小猿
今天
1
0
cocoapods 用法

cocoapods install pod install 更新本地已经install的仓库 更新所有的仓库 pod update --verbose --no-repo-update 更新制定的仓库 pod update ** --verbose --no-repo-update...

HOrange
今天
3
0
linux下socket编程实现一个服务器连接多个客户端

使用socekt通信一般步骤 1)服务器端:socker()建立套接字,绑定(bind)并监听(listen),用accept()等待客户端连接。 2)客户端:socker()建立套接字,连接(connect)服务器,连接上后...

shzwork
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部