【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,一经查实,立即删除!

相关文章

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追…

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…

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].…

游戏引擎学习第25天

Git: https://gitee.com/mrxiao_com/2d_game 今天的计划 总结和复述&#xff1a; 这段时间的工作已经接近尾声&#xff0c;虽然每次编程的时间只有一个小时&#xff0c;但每一天的进展都带来不少收获。尽管看起来似乎花费了很多时间&#xff0c;实际上这些日积月累的时间并未…

《Python基础》之Pandas库

目录 一、简介 二、Pandas的核心数据结构 1、Series 2、DataFrame 三、数据读取与写入 1、数据读取 2、数据写入 四、数据清洗与处理 1、处理缺失值 2、处理重复值 3、数据转换 五、数据分析与可视化 1、统计描述 2、分组聚合 3、数据可视化 六、高级技巧 1、时…

设计模式 更新ing

设计模式 1、六大原则1.1 单一设计原则 SRP1.2 开闭原则1.3 里氏替换原则1.4 迪米特法则1.5 接口隔离原则1.6 依赖倒置原则 2、工厂模式 1、六大原则 1.1 单一设计原则 SRP 一个类应该只有一个变化的原因 比如一个视频软件&#xff0c;区分不同的用户级别 包括访客&#xff0…

c++预编译头文件

文章目录 c预编译头文件1.使用g编译预编译头文件2.使用visual studio进行预编译头文件2.1visual studio如何设置输出预处理文件&#xff08;.i文件&#xff09;2.2visual studio 如何设置预编译&#xff08;初始创建空项目的情况下&#xff09;2.3 visual studio打开输出编译时…

SeggisV1.0 遥感影像分割软件【源代码】讲解

在此基础上进行二次开发&#xff0c;开发自己的软件&#xff0c;例如&#xff1a;【1】无人机及个人私有影像识别【2】离线使用【3】变化监测模型集成【4】个人私有分割模型集成等等&#xff0c;不管是您用来个人学习还是公司研发需求&#xff0c;都相当合适&#xff0c;包您满…

echarts地图立体效果,echarts地图点击事件,echarts地图自定义自定义tooltip

一.地图立体效果 方法1:两层地图叠加 实现原理:geo数组中放入两个地图对象,通过修改zlevel属性以及top,left,right,bottom形成视觉差 配置项参考如下代码: geo: [{zlevel: 2,top: 96,map: map,itemStyle: {color: #091A51ee,opacity: 1,borderWidth: 2,borderColor: #16BAFA…

HTML 快速上手

目录 一. HTML概念 二. HTML标签 1. 标题标签 2. 段落标签 3. 换行标签 4. 图片标签 5. 超链接标签 6. 表格标签 7. 表单标签 7.1 form 标签 7.2 input 标签 (1) 文本框 (2) 单选框 (3) 密码框 (4) 复选框 (5) 普通按钮 (6) 提交按钮 8. select标签 9. 无语义…

Linux 各个目录作用

刚毕业的时候学习Linux基础知识&#xff0c;发现了一份特别好的文档快乐的 Linux 命令行&#xff0c;翻译者是happypeter&#xff0c;作者当年也在慕课录制了react等前端相关的视频&#xff0c;通俗易懂&#xff0c;十分推荐 关于Linux的目录&#xff0c;多数博客已有详细介绍…

Fastapi + vue3 自动化测试平台---移动端App自动化篇

概述 好久写文章了&#xff0c;专注于新框架&#xff0c;新UI界面的实践&#xff0c;废话不多说&#xff0c;开搞 技术架构 后端&#xff1a; Fastapi Airtest multiprocessing 前端&#xff1a; 基于 Vue3、Vite、TypeScript、Pinia、Pinia持久化插件、Unocss 和 Elemen…