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

现象

经常在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)。

这里写图片描述

图片:

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

下面通过strace命令来验证以上结论:

  1. 跟踪hw进程(命令意为跟踪pid为6367的进程上与signal有关的系统调用):

    strace -e trace=signal -p 6367

  2. 跟踪bash进程(命令意为跟踪pid为5933的进程上与signal有关的系统调用):

    strace -e trace=signal -p 5933

  3. 关闭启动hw程序的终端,观察strace输出.

hwd的strace如下,si_pid=5933说明是5933这个进程发了SIGHUP给它,也就是bash进程:

这里写图片描述

bash的strace略微复杂:

这里写图片描述

  1. kill(4294960929, SIGHUP)

    kill第一个参数是32位有符号整数,转换成int就是-6367,当参数为负时表示发送给这个数绝对值的进程组,即pgrp=6367的所有进程,在上面的图片中可以看到hw进程正好属于该进程组。

  2. kill(5933, SIGHUP)

    5933是自己的pid,bash在第一次收到SIGHUP时先把信号发给session内其他进程,然后再次发送SIGHUP命令给自己,将自己杀死,后面的si_pid=5933也证实了这一点。

如何让终端关闭时进程不退出

根据上面的结论,要使终端关闭时进程不退出,有以下几种情况:

  1. 用户进程拦截SIGHUP信号。
  2. 用户进程和bash进程不在一个session。

下面依次验证这两种情况

拦截SIGHUP

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

signal(SIGHUP, SIG_IGN);
  • 1
  • 2

执行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){// 子进程// 创建新的sessionsetsid();//printf("Hello World!\n");printf("sleeping...\n");while(1){sleep(1);}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

调用setsid前先fork,因为若不fork,hw作为进程组的leader,是不允许重建session的,原因留给读者自己思考。

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

这里写图片描述

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

  1. 程序启动完,返回终端,hw切换到后台运行。
  2. hw进程的父进程不再是bash,而是init进程。
  3. hw没有关联的终端设备(pts/2)。

关闭终端,看到bash已经消失,但对hw进程没有任何影响:

这里写图片描述

更简单的方法

  1. setsid命令,用setsid来启动程序,这样就不用修改任何代码也可以做到使启动的进程在新的session中,并且终端关闭时,进程不退出。

    setsid ./hw

  2. nohup命令,被nohup启动的程序会忽略SIGHUP信号。

    nohup ./hw

其他

命令行中&的作用:

./hw &
  • 1
  • 2

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

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/385040.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

libevent源码深度剖析

第一章 1,前言 Libevent是一个轻量级的开源高性能网络库,使用者众多,研究者更甚,相关文章也不少。写这一系列文章的用意在于,一则分享心得;二则对libevent代码和设计思想做系统的、更深层次的分析&#xff…

浅谈auto_ptr智能指针

引入智能指针:智能指针的实现原理: 资源分配即初始化RAII(Resource Acquisition Is Initialization): 定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可…

多重继承之虚继承(主要是为了解决产生的数据冗余问题)

虚继承 是面向对象编程中的一种技术,是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。形式:在继承定义中包含了virtual关键字的继承关系,如下图中,类A就…

/proc 虚拟文件系统(实例)

Linux下有一个神奇的目录/proc,经常会运行 cat /proc/cpuinfo 命令查看cpu信息,/proc下的确有cpuinfo文件,但是这个文件不是物理存在的,是软件虚拟出来的,与普通文件不同,该文件是动态的。通过/proc可以实现…

malloc,calloc,realloc

与堆操作相关的两个函数 malloc #include<stdio.h> #include<stdlib.h> #include<string.h>int main() {char *p malloc(10); //内存随机&#xff0c;未做处理int i;for(i 0; i < 10: i){printf(“%d “,p[i]);} free(p);return 0; } 运行结果&…

Linux内核线程kernel thread详解--Linux进程的管理与调度

内核线程为什么需要内核线程Linux内核可以看作一个服务进程(管理软硬件资源&#xff0c;响应用户进程的种种合理以及不合理的请求)。 内核需要多个执行流并行&#xff0c;为了防止可能的阻塞&#xff0c;支持多线程是必要的。 内核线程就是内核的分身&#xff0c;一个分身可以处…

Linux 内核网络协议栈 ------sk_buff 结构体 以及 完全解释 (2.6.16)

在2.6.24之后这个结构体有了较大的变化&#xff0c;此处先说一说2.6.16版本的sk_buff&#xff0c;以及解释一些问题。一、先直观的看一下这个结构体~~~~~~~~~~~~~~~~~~~~~~在下面解释每个字段的意义~~~~~~~~~~~[cpp] view plaincopyprint?struct sk_buff { /* These…

最常用的设计模式---适配器模式(C++实现)

适配器模式属于结构型的设计模式&#xff0c;它是结构型设计模式之首&#xff08;用的最多的结构型设计模式&#xff09;。 适配器设计模式也并不复杂&#xff0c;适配器它是主要作用是将一个类的接口转换成客户希望的另外一个接口这样使得原本由于接口不兼容而不能一起工作的那…

c++简单线程池实现

线程池&#xff0c;简单来说就是有一堆已经创建好的线程&#xff08;最大数目一定&#xff09;&#xff0c;初始时他们都处于空闲状态&#xff0c;当有新的任务进来&#xff0c;从线程池中取出一个空闲的线程处理任务&#xff0c;然后当任务处理完成之后&#xff0c;该线程被重…

Linux 打印可变参数日志

实现了传输进去的字符串所在的文档&#xff0c;函数和行数显示功能。 实现了将传入的可变参数打印到日志功能。 #include<stdio.h> #include<stdarg.h> #include<string.h>const char * g_path "/home/exbot/wangqinghe/log.txt"; #define LOG(fm…

C++强化之路之线程池开发整体框架(二)

一.线程池开发框架 我所开发的线程池由以下几部分组成&#xff1a; 1.工作中的线程。也就是线程池中的线程&#xff0c;主要是执行分发来的task。 2.管理线程池的监督线程。这个线程的创建独立于线程池的创建&#xff0c;按照既定的管理方法进行管理线程池中的所有线程&#xf…

vfprintf()函数

函数声明&#xff1a;int vfprintf(FILE *stream, const char *format, va_list arg) 函数参数&#xff1a; stream—这是指向了FILE对象的指针&#xff0c;该FILE对象标识了流。 format—c语言字符串&#xff0c;包含了要被写入到流stream中的文本。它可以包含嵌入的format标签…

TCP粘包问题分析和解决(全)

TCP通信粘包问题分析和解决&#xff08;全&#xff09;在socket网络程序中&#xff0c;TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程&#xff0c;收发两端&#xff08;客户端和服务器端&#xff09;都要有成对的socket&#xff0c;因此&#xff0c;发送端为了将…

UML类图符号 各种关系说明以及举例

UML中描述对象和类之间相互关系的方式包括&#xff1a;依赖&#xff0c;关联&#xff0c;聚合&#xff0c;组合&#xff0c;泛化&#xff0c;实现等。表示关系的强弱&#xff1a;组合>聚合>关联>依赖 相互间关系 聚合是表明对象之间的整体与部分关系的关联&#xff0c…

ESP传输模式拆解包流程

一、 ESP简介ESP&#xff0c;封装安全载荷协议(Encapsulating SecurityPayloads)&#xff0c;是一种Ipsec协议&#xff0c;用于对IP协议在传输过程中进行数据完整性度量、来源认证、加密以及防回放攻击。可以单独使用&#xff0c;也可以和AH一起使用。在ESP头部之前的IPV4…

linux内核netfilter模块分析之:HOOKs点的注册及调用

1: 为什么要写这个东西?最近在找工作,之前netfilter 这一块的代码也认真地研究过&#xff0c;应该每个人都是这样的你懂 不一定你能很准确的表达出来。 故一定要化些时间把这相关的东西总结一下。 0&#xff1a;相关文档linux 下 nf_conntrack_tuple 跟踪记录 其中可以根据内…

网络抓包工具 wireshark 入门教程

Wireshark&#xff08;前称Ethereal&#xff09;是一个网络数据包分析软件。网络数据包分析软件的功能是截取网络数据包&#xff0c;并尽可能显示出最为详细的网络数据包数据。Wireshark使用WinPCAP作为接口&#xff0c;直接与网卡进行数据报文交换。网络管理员使用Wireshark来…

结构体中指针

结构体中带有指针的情况 #include<stdio.h>struct man {char *name;int age; };int main() {struct man m {"tom",20};printf("name %s, age %d\n",m.name,m.age);return 0; } 运行结果&#xff1a; exbotubuntu:~/wangqinghe/C/20190714$ gcc st…

python使用opencv提取视频中的每一帧、最后一帧,并存储成图片

提取视频每一帧存储图片 最近在搞视频检测问题&#xff0c;在用到将视频分帧保存为图片时&#xff0c;图片可以保存&#xff0c;但是会出现(-215:Assertion failed) !_img.empty() in function cv::imwrite问题而不能正常运行&#xff0c;在检查代码、检查路径等措施均无果后&…

线程间通信之eventfd

线程间通信之eventfd man手册中的解释&#xff1a; eventfd()创建了一个“eventfd对象”&#xff0c; 通过它能够实现用户态程序间(我觉得这里主要指线程而非进程)的等待/通知机制&#xff0c;以及内核态向用户态通知的机制&#xff08;未考证&#xff09;。 此对象包含了一个…