仪陇家园分类信息网、仪陇生活网、仪陇家园网

搜索

linux终端关闭时为什么会导致在其上启动的进程退出?

[复制链接]
seo 发表于 2022-5-31 13:28:51 | 显示全部楼层 |阅读模式
linux终端关闭时为什么会导致在其上启动的进程退出?发布时间:2022/5/31 13:17:01
            
                                                       
                                                       
            
        
        
               
                     现象
经常在linux下开发的人应该都有这样的经验,就是在终端上启动的程序,在关闭终端时,这个程序的进程也被一起关闭了。看下面这个程序,为了使进程永远运行,在输出helloworld后,循环调用sleep:




直接关闭这个终端,在另一个终端上查找该进程,已经找不到了:




这个行为看起来似乎是理所当然的,也符合人的第一感觉:”在终端上启动的程序是属于终端的,所以当关闭终端时,这个终端里的一包裹进程都一起被解决掉了”。但这种说法是不能使一个会思考且充满好奇心的人信服的。

下面我们就从linux进程管理的细节来剖析其根本原因。

终端进程
linux系统是基于进程的,几乎每个命令都可以在相应的目录下找到它们的程序,执行一个命令相当于启动一个或多个程序,终端也不例外,在我centos下面终端对应一个bash程序(不同操作系统终端的bash程序可能不一样),它位于/usr/bin/下面:




每当打开一个终端都会启动一个bash进程,我这里启动了两个终端,可以看到有两个bash进程:




终端进程与启动进程的关系
linux系统里面所有的进程的关系可以看做一个树形结构,系统持续运行,进程的不断启动就是不断fork的过程(fork是linux系统api,作用是复制自己来生成子进程),从系统启动、初始化、登录终端、到执行命令都是生成子进程的过程:




init进程是所有进程的祖先,它的pid(进程id)为1,ppid(父进程id)也为1,因为它没有父进程,系统内的其他进程都是由它或者它的子进程fork而来。

我们在linux上作业的终端对应了一个bash进程,在其上运行的命令和程序都是bash的子进程,或由bash的子进程衍生。

用hw程序验证一下,可以看到hw进程的父进程正好是bash进程:




但这并不能解释为什么终端关闭了在上面运行的程序也跟着退出,因为在linux下,进程之间的关系并不像线程那样,当主线程退出时,子线程一起被强制退出。进程之间没有主次的区别,但有父子关系,而父子进程的运行是相对独立的,一方的退出不会导致另一方退出。

进程session-揭开真相
在linux下,一个session是由一组进程组构成的,每个进程组又由多个进程构成。

在一个bash上运行的程序都归属于一个session(除非特别处理),而这个bash就是这个session的leader。每个session又可以关联一个控制终端(Controlling Terminal)。




图片:

hw进程的ppid=5933,说明父进程为第一个bash,这个bash的父进程为gnome-ternimal进程,gnome-ternimal是centos可视化界面的终端管理进程,每打开一个终端,它都会启动一个bash进程,而用户的命令也是直接由bash进程执行的。hw程序和第一个bash同属于一个session(sid=5933),这个sid等于bash的pid,所以第一个bash是这个session的leader。图片中还显示了bash和hw进程拥有共同的终端设备pts/2,它是一种字符设备,不同于上面提到的gnome-ternimal进程。当控制终端(对应gnome-ternimal)检测到终端设备断(对应pts/2)开连接时,会通知设备的控制进程,即发送SIGHUP信号给session leader(对应bash进程)。bash进程在收到SIGHUP后,将信号发给session下的所有进程,导致用户启动的进程退出。[/ol]
下面通过strace命令来验证以上结论:

跟踪hw进程(命令意为跟踪pid为6367的进程上与signal有关的系统调用):
strace -e trace=signal -p 6367
  跟踪bash进程(命令意为跟踪pid为5933的进程上与signal有关的系统调用):
strace -e trace=signal -p 5933
  关闭启动hw程序的终端,观察strace输出.
[/ol]
hwd的strace如下,si_pid=5933说明是5933这个进程发了SIGHUP给它,也就是bash进程:




bash的strace略微复杂:




kill(4294960929, SIGHUP)
kill第一个参数是32位有符号整数,转换成int就是-6367,当参数为负时表示发送给这个数绝对值的进程组,即pgrp=6367的所有进程,在上面的图片中可以看到hw进程正好属于该进程组。
  kill(5933, SIGHUP)
5933是自己的pid,bash在第一次收到SIGHUP时先把信号发给session内其他进程,然后再次发送SIGHUP命令给自己,将自己杀死,后面的si_pid=5933也证实了这一点。
[/ol]
如何让终端关闭时进程不退出
根据上面的结论,要使终端关闭时进程不退出,有以下几种情况:

用户进程拦截SIGHUP信号。用户进程和bash进程不在一个session。[/ol]
下面依次验证这两种情况

拦截SIGHUP
修改hw程序,忽略SIGHUP信号:

signal(SIGHUP, SIG_IGN);
12
执行hw程序,并查看进程,可以看到hw进程和父进程bash:




关闭终端,在另一个终端查看进程:




bash进程已经退出,但hw进程还在,符合预期!!而且hw进程的ppid变成了1,说明hw在父进程bash退出后变成孤儿进程被init进程收养。

新建session&setsid
为了使用户进程和bash不在同一个session,需要调用setsid方法,该方法的作用是新建一个新的session,并使自己成为leader。

// 先fork
int pid = fork();
if(pid > 0){
    // 父进程, 直接退出
    return 1;
}else if(pid == 0){
    // 子进程
    // 创建新的session
    setsid();
    //
    printf("Hello World!\n");
    printf("sleeping...\n");
    while(1){
        sleep(1);
    }
}
1234567891011121314151617
调用setsid前先fork,因为若不fork,hw作为进程组的leader,是不允许重建session的,原因留给读者自己思考。

编译并执行hw,查看进程:




可以看到,相比之前,有几个不同的地方:

程序启动完,返回终端,hw切换到后台运行。hw进程的父进程不再是bash,而是init进程。hw没有关联的终端设备(pts/2)。[/ol]
关闭终端,看到bash已经消失,但对hw进程没有任何影响:




更简单的方法
setsid命令,用setsid来启动程序,这样就不用修改任何代码也可以做到使启动的进程在新的session中,并且终端关闭时,进程不退出。
setsid ./hw
  nohup命令,被nohup启动的程序会忽略SIGHUP信号。
nohup ./hw
[/ol]
其他
命令行中&的作用:

./hw &
12
&的作用是使程序在后台运行,输入fg命令又可以使程序切换到前台。虽然在后台运行,但并不能保证进程在终端关闭时不退出。

总结
简而言之,终端在关闭时会发送SIGHUP给对应的bash进程,bash进程收到这个信号后首先将它发给session下面的进程,如果你的程序没有对SIGHUP信号做特殊处理,那么进程就会随着终端关闭而退出。
               
        
        
  
   
      
        

      
      
        
          创作挑战赛
         

        
        新人创作奖励来咯,坚持创作打卡瓜分现金大奖
      
   
  
   
            
        
        
回复

使用道具 举报

全部回复0 显示全部楼层

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

楼主

审核员

热门推荐

联系客服 关注微信 下载APP 返回顶部 返回列表