【Linux】进程间关系与守护进程

🌎进程间关系与守护进程


文章目录:

进程间关系与守护进程

    进程组
    会话
      认识会话
      会话ID
      创建会话

    控制终端
    作业控制
      作业(job)和作业控制(Job Control)
      作业号及作业过程

    守护进程


🚀进程组

  之前我们提到了进程的概念, 其实每一个进程除了有一个进程 ID(PID)之外 还属于一个进程组。进程组是一个或者多个进程的集合, 一个进程组可以包含多个进程。 每一个进程组也有一个唯一的进程组 ID(PGID), 并且这个 PGID 类似于进程 ID, 同样是一个正整数, 可以存放在 pid_t 数据类型中。我们使用如下命令可以查看进程组:

ps-eopid,pgid,ppid,comm|greptest
  • -e 选项: 表示every的意思,表示输出每一个进程信息
  • -o选项: 以逗号操作符(,)作为定界符,可以指定要输出的列

在这里插入图片描述

  我们使用sleep命令通过管道创建了 3个进程,虽然这并没有什么意义,我们发现,这三个进程的 ppid 是相同的,也就是说,这三个进程是兄弟进程,那么他们的父进程是谁呢?就是我们常说的 bash 进程。

  如果你仔细观察上图,会发现有一列名为 PGID 的数值,它们三个进程都是一样的,而 PGID 表示的就是进程组id,如果你再仔细观察,Sleep 10000 进程的进程id实际上就是他们三个的进程组id,这就表示,-每一个进程组都有一个组长进程。组长进程的ID等于其进程ID

  如果只有一个进程会是什么情况呢?还会有进程组出现吗?我们不妨做个简单的实验,实验代码如下所示:

#include <iostream>
#include <string>
#include <unistd.h>int main()
{while(true){std::cout << "I'm a process, pid is: " << getpid() << std::endl;sleep(1);}return 0;
}

在这里插入图片描述

  从结果我们可以得出结论:无论进程是一个还是多个,都会有自己的进程组,如果是多个进程,会以第一个创建的进程的pid为进程组id,如果为单个进程,自己的pid就是进程组id。还有一种情况,我们没考虑到,如果是父子进程之间呢?会有什么样的关系?我们继续做实验,下面是实验代码:

#include <iostream>
#include <string>
#include <unistd.h>int main()
{pid_t id = fork();if(id == 0){while(true){std::cout << "I'm a sub process, pid is: " << getpid() << std::endl;sleep(1);}}sleep(3);std::cout << "I'm a father process, pid is: " << getpid() << std::endl;sleep(100);return 0;
}

在这里插入图片描述

  我们发现,进程组的组id就是父进程的进程pid,其实也不难解释,因为需要先创建父进程才会创建子进程,而父子进程工作实际上就是在同一个进程组当中执行任务。

  • 进程组组长的作用进程组组长可以创建一个进程组或者创建该组中的进程
  • 进程组的生命周期从进程组创建开始到其中最后一个进程离开为止

注意主要某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否已经终止无关


🚀 会话

✈️认识会话

  刚刚我们谈到了进程组的概念,那么会话又是什么呢?会话其实和进程组息息相关,会话可以看成是一个或多个进程组的集合,一个会话可以包含多个进程组每一个会话也有一个 会话ID(SID)

  当我们在使用远程登录Xshell的时候,远端服务器会给我们做鉴权,我们登录成功之后系统会分配给我们一个终端文件,如下所示:

在这里插入图片描述

  最开始我们只连接了一个客户端,此时在 /dev/pts 目录下就是我们的终端文件,当时只有0号文件这一个文件,但是当我们开启第二个终端,我们再次查看这个目录,就会发现终端文件就会多出一号。

  除了会给每个用户分配一个终端文件以外,每个用户也会启动自己的bash进程,每当有新的用户连接时,机会分配一个新的bash:

在这里插入图片描述
  

在这里插入图片描述

  我们前面说了,一个会话是包含多个进程组的,为了验证这个事实,我们创建两个进程组,一个是 sleep 10000 | sleep 20000 | sleep 30000 一个是我们前面写的程序,同时我们使用如下命令对他们进行监测:

ps ajx | head -1 && ps ajx | grep -E 'sleep|process_task' # -E选项表示''中的内容只要有匹配的就显示出来

在这里插入图片描述

  可以看到,系统中存在五个进程,并且这些进程被分为了两组,但是我们观察SID这一栏,我们不难发现,它们的SID都是相同的,尽管是两个不同的进程组。而前面我们说了,SID就是会话ID,这也就证明了,一个会话中可以存在多个进程组。


✈️会话ID

  上边我们提到了会话 ID, 那么会话 ID 是什么呢? 我们可以先说一下会话首进程, 会话首进程是具有唯一进程 ID 的单个进程, 那么我们可以将会话首进程的进程 ID 当做是会话 ID

  注意会话 ID 在有些地方也被称为 会话首进程的进程组 ID, 因为会话首进程总是一个进程组的组长进程, 所以两者是等价的

在这里插入图片描述

  在上面的例子中,我们除了看到这两个进程组是属于同一个会话以外,如果你仔细观察,会发现他们5个进程的ppid都是bash,而bash又作为第一个终端下启动的进程,所以正常情况下一个会话的会话id实际上就是bash进程的pid。


✈️创建会话

  我们知道什么是会话了以后,我们该如何手动创建一个会话呢?实际上OS给我们提供系统调用的接口 setsid() ,可以调用 setseid 函数来创建一个会话, 前提是 调用进程不能是一个进程组的组长

在这里插入图片描述

  • 返回值成功返回创建会话的sid,失败返回-1

该接口调用之后会发生

  • 调用进程会变成新会话的会话首进程。 此时, 新会话中只有唯一的一个进程
  • 调用进程会变成进程组组长。 新进程组 ID 就是当前调用进程 ID
  • 该进程没有控制终端。 如果在调用 setsid 之前该进程存在控制终端, 则调用之后会切断联系

注意
  这个接口如果调用进程原来是进程组组长, 则会报错, 为了避免这种情况, 我们通常的使用方法是先调用 fork 创建子进程父进程终止, 子进程继续执行, 因为子进程会继承父进程的进程组 ID, 而进程 ID 则是新分配的, 就不会出现错误的情况


🚀控制终端

  在上面的例子中,如果你仔细观察,当我在测试不同进程组属于同一个会话的时候,我们把 sleep 10000 | sleep 20000 | sleep 30000 & 变成了后台进程,这是因为 在同一个会话中,可以运行同时存在的多个进程,但是在任何时刻,只允许有一个前台进程(进程组),可以允许有多个后台进程并且只有前台进程才能获取从键盘得到的数据以及指令。这也就是为什么我们无法使用 Ctrl C 来杀死后台进程。

  在 UNIX 系统中,用户通过终端登录系统后得到一个 Shell 进程,这个终端成为 Shell进程的控制终端。控制终端是保存在 PCB 中的信息,我们知道 fork 进程会复制 PCB中的信息,因此由 Shell 进程启动的其它进程的控制终端也是这个终端。默认情况下没有重定向,每个进程的标准输入、标准输出和标准错误都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。另外会话、进程组以及控制终端还有一些其他的关系,我们在下边详细介绍一下:

  •  一个会话可以有一个控制终端,通 常会话首进程打开一个终端(终端设备或伪终端设备)后,该终端就成为该会话的控制终端
  •  建立与控制终端连接的会话首进程被称为 控制进程
  •  一个会话中的 几个进程组可被分成一个前台进程组以及一个或者多个后台进程组
  • 如果一个会话有一个控制终端,则它有一个前台进程组,会话中的其他进程组则为后台进程组
  •  无论何时进入终端的中断键(ctrl+c)或退出键(ctrl+\),就会将中断信号发送给前台进程组的所有进程
  •  如果终端接口检测到调制解调器(或网络)已经断开,则将挂断信号发送给控制进程(会话首进程)。

它们的关系如下图所示:

在这里插入图片描述

  那么当用户退出的时候,会话中的进程组虽然不一定都终止(不同OS处理方式不同),但是这些进程组一定会受到影响。


🚀作业控制

✈️作业(job)和作业控制(Job Control)

  作业 是针对用户来讲,用户完成某项任务而启动的进程,一个作业既可以只包含一个进程,也可以包含多个进程进程之间互相协作完成任务, 通常是一个 进程管道

  Shell 分前后台来控制的不是进程而是 作业 或者进程组一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell 可以同时运⾏一个前台作业和任意多个后台作业,这称为 作业控制

  例如下列命令就是一个作业,它包括两个命令,在执⾏时 Shell 将在前台启动由两个进程组成的作业:

cat process.cc | head -n 5

在这里插入图片描述


✈️作业号及作业过程

  放在后台执⾏的程序或命令称为后台命令,可以在命令的后面加上&符号从而让Shell 识别这是一个后台命令,后台命令不用等待该命令执⾏完成,就可立即接收新的命令,另外后台进程执行完后会返回一个作业号以及一个进程号(PID)

  例如下面的命令在后台启动了一个作业, 该作业由三个进程组成, 三个进程都在后台运⾏:

在这里插入图片描述
  我们将 sleep 1000 | sleep 2000 | sleep 3000 & 三个进程放到了后台运行,执行的那一刻,我们就得到了作业号,并且 该作业号通常是进程组当中最进入的进程pid

  当然,我们有更简单的命令来查看后台启动的作业,jobs 命令:

在这里插入图片描述
  如果我们想要把一个后台作业放到前台去运行,我们可以使用 fg 作业序号

在这里插入图片描述
在这里插入图片描述

  如果这个时候,你需要将放置到前台的作业再次放回到后台去运行,那么首先你需要先将作业暂停,使用 Ctrl Z 快捷键进行暂停作业,而暂停的进程就会自动的变成后台进程,这时候我们使用 bg 作业序号 就可以继续运行任务了:

在这里插入图片描述

  我们仔细观察这些后台进程,在序号后面会跟着加号或者减号,这些是什么东西?实际上这与默认作业是有关系的。关于默认作业对于一个用户来说,只能有一个默认作业(+),同时也只能有一个即将成为默认作业的作业(-),当默认作业退出后,该作业会成为默认作业。而作业也会有自己的状态,一般分为以下几种:

在这里插入图片描述


🚀守护进程

  假设一个会话中存在4个进程组,并且第四个进程组只有一个进程,如果我们将第四个进程(进程组)独立出来,形成一个新的会话,这个进程就叫做 守护进程

在这里插入图片描述
  不同的程序员对创建守护进程的方式不同,这里我们用上面提到的 setsid() 来创建一个新的会话,这样就可以形成一个守护进程了,但是调用该接口的不能是进程组的组长,所以我们可以通过fork创建子进程,并将父进程退出,让子进程执行该接口即可。而父进程退出之后子进程就变成了孤儿进程,所以守护进程是孤儿进程的一种特殊情况。

  如果我们直接调用setsid()是行不通的,必须得首先创建子进程,并且退出父进程,这样很费力,所以Linux给我们提供了一个一劳永逸的接口,不需要你创建子进程,因为其函数内部就已经做了处理 Daemon()

在这里插入图片描述

  • nochdir 参数是否更改当前进程的工作目录。如果更改,守护进程的目录就会切换为根目录,如果不更改,则在启动时的路径下
  • nocliose参数是否需要进行输入输出的处理

  Linux每个终端下都会存在一个null文件:/dev/null如果去读取这个文件,文件内是没有任何内容的,如果对该文件进行写,同样也不会保存任何信息,而是立刻丢弃。我们知道,当我们创建了守护进程,也就意味着脱离了原本的会话,所以也就没有原本的终端文件了,守护进程内可能存在大量的IO操作,为了避免因为没有对应的终端文件进行IO而出错,我们可以将 0,1,2三个文件描述符全部重定向到 /dev/null 当中。

#include <iostream>
#include <unistd.h>int main()
{std::cout << "Pid is: " << getpid() << std::endl;sleep(1);daemon(0, 0);while(true){std::cout << "hello test" << std::endl;sleep(1);}return 0;
}

  以上是一个简单的测试样例,daemon内部会自动的fork并且退出父进程:

在这里插入图片描述

  经过测试我们可以看到,hello.exe 的TTY,也就是终端文件变成了 “?”, 也就表示已经不属于当前的会话了,而SID同样与当前进程的SID不同,并且SID为守护进程的pid。如果我们查看守护进程的工作目录:

在这里插入图片描述
  可以看到,守护进程当前工作目录实际上就是在根目录,如果我们同时查看该守护进程的文件fd就会发现:

在这里插入图片描述

  由此可见,daemon接口的两个参数实际上是bool值类型的,第一个参数表示是否更改工作目录,第二个参数表示是否更改重定向,如果我们把daemon参数设置为daemon(0, 0):

在这里插入图片描述

在这里插入图片描述

  将daemon参数设置为(1, 1)就会导致我们输出的内容还是在上一个会话下,并且Ctrl C 也无法终止进程(可使用 kill -9 process_pid 杀死进程),当我们查询进程工作目录时,也能发现其在当前的工作目录下,而fd也指向了第一个终端文件。

  那么这里,我们来模拟一下daemon接口的行为:

#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdlib>const std::string defaultpath = "/";
const std::string defaultdev = "/dev/null";void Daemon(bool ischdir, bool isclose)
{//1. 忽略掉不要的信号signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);// forkif(fork() > 0) exit(0);//setsidsetsid();// 确认是否要更改工作目录if(ischdir)chdir(defaultpath.c_str());// 对012进行重定向if(!isclose){::close(0);::close(1);::close(2);}else{int fd = open(defaultdev.c_str(), O_RDWR);if(fd > 0){dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);::close(fd);}}
}

  这样我们的daemon接口就模拟完成了,接着在main函数中我们就可以调用该接口实现会话分离:

#include <iostream>
#include <string>
#include <unistd.h>
#include "Daemon.hpp"int main()
{Daemon(false, false);while(true){sleep(1);// 模拟任务}return 0;
}

在这里插入图片描述

  该进程就变成了守护进程,可以看到ppid变为了1,pid与pgid, sid都相同也验证了我们所说的部分,并且TTY,我们找不到对应的终端文件了,更可以证明这个进程已经是一个守护进程了。要杀死守护进程也很简单,使用kill命令即可。


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

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

相关文章

RuoYi-Vue部署到Linux服务器(Jar+Nginx)

一、本地环境准备 源码下载、本地Jdk及Node.js环境安装,参考以下文章。 附:RuoYi-Vue下载与运行 二、服务器环境准备 1.安装Jdk 附:JDK8下载安装与配置环境变量(linux) 2.安装MySQL 附:MySQL8免安装版下载安装与配置(linux) 3.安装Redis 附:Redis下载安装与配置(…

QT5.14 QML串口助手

基于 QML的 串口调试助手 这个代码有缺失&#xff0c;补了部分代码 ASCII HEX 工程共享&#xff0c; Qt版本 5.14.1 COM_QML 通过百度网盘分享的文件&#xff1a;COM_QML.zip 链接&#xff1a;https://pan.baidu.com/s/1MH2d6gIPDSoaX-syVWZsww?pwd5tge 提取码&#xff1a;…

IOS ARKit进行图像识别

先讲一下基础控涧&#xff0c;资源的话可以留言&#xff0c;抽空我把它传到GitHub上&#xff0c;这里没写收积分&#xff0c;竟然充值才能下载&#xff0c;我下载也要充值&#xff0c;牛&#xff01; ARSCNView 可以理解画布或者场景 1 配置 ARWorldTrackingConfiguration AR追…

常用排查工具使用

1.spy++ Microsoft Spy++是一个非常好的查看Windows操作系统的窗口、消息、进程、线程信息的工具,简单易用,功能强大。 在vs的工具中默认安装,还可以监控到隐层窗口,通过查看窗口的属性可以获得更多信息,包括规格、窗口、类、进程等信息,可以帮助排查相关窗口的问题。 2…

“Encrypt”属性设置为“true”且 “trustServerCertificate”属性设置为“false”,但驱动程序无法使用安全套接字层 (SSL) 加密与 SQL Server 建立安全

com.microsoft.sqlserver.jdbc.SQLServerException: “Encrypt”属性设置为“true”且 “trustServerCertificate”属性设置为“false”&#xff0c;但驱动程序无法使用安全套接字层 (SSL) 加密与 SQL Server 建立安全连接:错误:PKIX path building failed: sun.security.provi…

【RK3588 Linux 5.x 内核编程】-内核高分辨率定时器

内核高分辨率定时器 文章目录 内核高分辨率定时器1、高分辨率定时器介绍2、高分辨率定时器API2.1 初始化定时器2.2 启动定时器2.3 停止定时器2.4 改变定时器超时时间2.5 定时器状态检查3、驱动实现4、驱动验证在前面的文章中,我们知道了如果在Linux内核中使用定时器。本文将详…

C语言第十五周课——课堂练习

目录 1.输出特定图形 2.求三个数的最小值 3.思考题 1.输出特定图形 要求&#xff1a;输出下面形状在控制台 * * * * * * * * * * * * * * * #include <stdio.h> int main() {int i, j;// 外层循环控制行数for (i 1; i < 5; i){// 内层循环控制每行的星号个数for (…

数据结构 (20)二叉树的遍历与线索化

一、二叉树的遍历 遍历是对树的一种最基本的运算&#xff0c;所谓遍历二叉树&#xff0c;就是按一定的规则和顺序走遍二叉树的所有节点&#xff0c;使每一个节点都被访问一次&#xff0c;而且只被访问一次。二叉树的遍历方式主要有四种&#xff1a;前序遍历、中序遍历、后序遍历…

sscanf与sprintf函数

本期介绍&#x1f356; 主要介绍&#xff1a;sscanf()、sprintf()这对输入/输出函数&#xff0c;并详细讲解了这两个函数的应用场景。 概述&#x1f356; 在C语言的输出和输入库中&#xff0c;有三对及其相似的库函数&#xff1a;printf()、scanf()、fprintf()、fscanf()、spri…

基于链表的基础笔试/面试题

1. 反转链表 问题描述&#xff1a;反转一个单向链表。 示例&#xff1a; 输入&#xff1a;1 → 2 → 3 → 4 → 5 输出&#xff1a;5 → 4 → 3 → 2 → 1 class ListNode {int val;ListNode next;ListNode(int x) {val x;} }public class LinkedList {public ListNode …

[高等数学学习记录] 泰勒公式

1 知识点 1.1 要求 为简化计算, 通常用多项式近似表达复杂函数: 设函数 f ( x ) f(x) f(x) 在含有 x 0 x_0 x0​ 的开区间内具有 ( n 1 ) (n1) (n1) 阶导数, 试找出一个关于 ( x − x 0 ) (x-x_0) (x−x0​) 的 n n n 次多项式 p n ( x ) p_n(x) pn​(x) 近似表达 f…

Linux条件变量线程池详解

一、条件变量 【互斥量】解决了线程间同步的问题&#xff0c;避免了多线程对同一块临界资源访问产生的冲突&#xff0c;但同一时刻对临界资源的访问&#xff0c;不论是生产者还是消费者&#xff0c;都需要竞争互斥锁&#xff0c;由此也带来了竞争的问题。即生产者和消费者、消费…

【错误记录】jupyter notebook打开后服务器错误Forbidden问题

如题&#xff0c;在Anaconda Prompt里输入jupyter notebook后可以打开浏览器&#xff0c;但打开具体项目后就会显示“服务器错误&#xff1a;Forbidden”&#xff0c;终端出现&#xff1a; tornado.web.HTTPError: HTTP 403: Forbidden 查看jupyter-server和jupyter notebook版…

shodan2-批量查找CVE-2019-0708漏洞

声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…

PostgreSQL实现透视表查询

PostgreSQL 8.3版本发布时&#xff0c;引入了一个名为tablefunc的新扩展。这个扩展提供了一组非常有趣的函数。其中之一是交叉表函数&#xff0c;用于创建数据透视表。这就是我们将在本文中讨论的内容。 需求说明 解释此函数如何工作的最简单方法是使用带有数据透视表的示例…

使用Tauri创建桌面应用

当前是在 Windows 环境下 1.准备 系统依赖项 Microsoft C 构建工具WebView2 (Windows10 v1803 以上版本不用下载&#xff0c;已经默认安装了) 下载安装 Rust下载安装 Rust 需要重启终端或者系统 重新打开cmd&#xff0c;键入rustc --version&#xff0c;出现 rust 版本号&…

【掩体计划——DFS+缩点】

题目 代码 #include <bits/stdc.h> using namespace std; const int N 1e5 10; vector<vector<int>> g; bool st[N]; int ans 1e9; bool dfs(int f, int u, int dis) {bool is 1;for (auto j : g[u]){if (j f)continue;is & dfs(u, j, dis (g[u].…

第四十四篇 EfficientNetV1、V2模型详解

摘要 EfficientNetV1 详解 简要介绍 EfficientNet是Google提出的一种高效的神经网络架构,其核心思想是通过比例缩放网络的宽度(通道数)、高度和深度(层数)来平衡计算资源和准确性。EfficientNetV1是该系列的首个版本,在提出时便在效果、参数量、速度方面均大幅超越了之…

微信小程序踩坑指南(二)<template>和<block>

<template> 小程序里的和Vue里的表达的不是一种含义。小程序的template是一种模板&#xff0c;不能用于直接显示代码。它正常情况下不显示&#xff0c;需加载使用。 <block> 并不是一个组件&#xff0c;它仅仅是一个包装元素&#xff0c;不会在页面中做任何渲染…

【Spring】注解开发

为了提高开发效率&#xff0c;从 Spring 2.0 开始引入了多种注解&#xff0c;而在 Spring 3.0 中则实现了纯注解的开发方式。 一、注解的使用 在 Spring 2.0 之后&#xff0c;使用注解进行开发主要分为两个步骤&#xff1a; 定义 Bean&#xff1a;使用 Component 注解来定义…