Linux操作系统:进程间关系

发布时间:2026/5/19 0:55:01

Linux操作系统:进程间关系 大家平时遇见的服务端比如微信的服务端难道就是要保证24小时的终端打开吗显然不是的所以今天我们就为大家拓展介绍一下进程间的各种关系与守护进程的概念。1、前台后台进程我们之前已经初步接触到了前台与后台进程的概念也曾提到过我们在执行程序或命令时默认都是在前台执行的这个时候如果我们想取消进程可以直接输入ctrl c发送信号。要想让其在后台执行可以在后面加一个表示让其在后台执行。2、进程组我们之前在学Linux系统部分的时候说过进程的概念其实每一个进程除了有一个进程 ID(PID)之外它还属于一个进程组。进程组是一个或者多个进程的集合用于管理一组相关联的进程一个进程组可以包含多个进程。 每一个进程组也有一个唯一的进程组 ID(PGID) 并且这个 PGID 类似于进程 ID 同样是一个正整数 可以存放在 pid_t 数据类型中。之前我们说过每一个命令在Shell中其实都是转化为一个进程。那么如果我利用管道连续执行三个sleep 10000命令会发生什么我们这里返回了一行信息这个的中括号中的1我们后面会说到这是作业号。而后面的数字1195338是返回的最后一个指令进程的PID。我们可以使用ps查看当前Shell中运行的进程可以看见我们应该是先创建了1195386这个进程随后依次创建另外两个sleep进程。这里输出的信息有一列叫做PGID这个就是我们进程组的ID一个进程组的ID等于它的组长的PID。而组长通常是第一个创建的进程也就是我们的1195386这个进程。值得注意的是 即使组长进程终止只要组内还有其他进程存在进程组仍然存在直到组内所有进程退出或加入其他进程组。组长退出进程组的PGID也不会改变。在Linux系统中使用管道连接的命令通常会被分配到一个进程组里这也就是我们三个sleep命令的关联之处。3、会话刚刚我们谈到了进程组的概念 那么会话又是什么呢 会话其实和进程组息息相关会话可以看成是一个或多个进程组的集合 它是 Unix/Linux 操作系统中的一个进程管理单元用于组织一组相关的进程组Process Groups。一个会话可以包含多个进程组。每一个会话也有一个会话 ID(SID)那么会话 ID 是什么呢 我们可以先说一下会话首进程 会话首进程是具有唯一进程 ID 的单个进程 那么我们可以将会话首进程的进程 ID 当做是会话 ID。注意会话 ID 在有些地方也被称为 会话首进程的进程组 ID 因为会话首进程总是一个进程组的组长进程 所以两者是等价的。4、终端说完会话就不得不提一下终端的概念。终端Terminal是用户与计算机系统交互的入口其核心功能是输入指令并显示输出结果。我们的云服务使用的是伪终端的形式我们通过在终端中运行的Shell程序实现了解析用户命令并调用操作系统内核执行的作用。我们的一个会话通常会与一个终端进行绑定。用户通过 SSH 或终端模拟器登录 → 系统分配一个伪终端如/dev/pts/0随后启动 Shell如bash→ Shell 成为会话首进程Session Leader并绑定该终端。在我们的云服务器中/dev/pts/目录下存储的就是我们的终端文件在这里插入图片描述当我们新建几个终端后可以看见终端文件又多了几个在这里插入图片描述我们可以通过echo My terminal:可以看见终端文件的名字与所绑定的会话号都已经打印了出来。5、终端与会话在 UNIX / Linux 系统中用户通过终端登录系统后得到一个 Shell 进程这个终端成为 Shell进程的控制终端。控制终端是保存在 PCB 中的信息我们知道 fork 进程会复制 PCB中的信息因此由 Shell 进程启动的其它进程的控制终端也是这个终端。默认情况下没有重定向每个进程的标准输入、标准输出和标准错误都指向控制终端进程从标准输入读也就是读用户的键盘输入进程往标准输出或标准错误输出写也就是输出到显示器上。另外会话、进程组以及控制终端还有一些其他的关系我们在下边详细介绍一下一个会话可以有一个控制终端通常会话首进程打开一个终端终端设备或伪终端设备后该终端就成为该会话的控制终端。这一点我们刚刚提到过建立与控制终端连接的会话首进程被称为控制进程。当 Bash 作为登录 Shell 启动时它自己创建一个新会话SIDBash_PID随后成为该会话的首进程Session Leader并将自己设为前台进程组PGIDBash_PID一个会话中的几个进程组可被分成一个前台进程组以及一个或者多个后台进程组。这个什么意思呢就是说我的前台进程组同时只能存在一个。在平时我们的前台程序组是bash所在的进程组我们的标准输入是被定向到了这个前台进程组中。但是当我们执行了一个程序以前台方式执行我们的前台进程组就会替换为该程序bash将把自己替换为后台进程组里。这也就是为什么在执行sleep以及其他程序时我们无法通过输入输出流给bash发送命令的原因。同样的当一个进程在后台执行时我们不能直接ctrl c终止掉它因为此时的输入流是在前台进程组中我们发送的信号到达不了后台进程组。如果终端接口检测到调制解调器或网络已经断开则将挂断信号发送给控制进程会话首进程在这里插入图片描述6、作业控制作业job是针对用户来讲用户完成某项任务而启动的进程一个作业既可以只包含一个进程也可以包含多个进程进程之间互相协作完成任务 通常是一个进程管道。Shell 分前后台来控制的不是进程而是作业或者进程组。一个前台作业可以由多个进程组成一个后台作业也可以由多个进程组成Shell 可以同时运⾏一个前台作业和任意多个后台作业这称为作业控制。我们之前说过放在后台执⾏的程序或命令称为后台命令可以在命令的后面加上符号从而让Shell 识别这是一个后台命令后台命令不用等待该命令执⾏完成就可立即接收新的命令另外后台进程执行完后会返回一个作业号以及一个进程号PID在这里插入图片描述作业号是 Shell如 Bash用来跟踪和管理后台任务或挂起任务的标识符。通过作业号我们可以对执行当前作业的进程组进行管理这是因为一个任务号与该进程组的PGID关联起来了。和-符号是 Shell如 Bash在作业控制Job Control中用来标识作业优先级的标记它们帮助用户快速识别和管理作业.默认作业()是 Shell 中最近被操作或最近被放入后台的作业。当用户执行fg、bg或kill等命令不指定作业号时默认操作的就是这个作业。候选默认作业-是下一个可能成为默认作业的作业。当当前默认作业终止或被移除后-作业会自动升级为。我们可以过以下命令来控制一个作业场景命令组合启动后台作业cmd 挂起当前前台作业CtrlZ恢复挂起的作业到前台fg 或 fg %1恢复挂起的作业到后台bg 或 bg %2终止作业kill %1查看所有作业含 PIDjobs -l让后台作业忽略 SIGHUPdisown %1以这组命令为例在这里插入图片描述我们首先创建一个后台进程组得到他的任务号为1。我们使用fg 命令指定任务号为1的任务使其变成一个前台进程组。此时打印出了我们的执行的命令sleep 10000。随后我们可以使用 ctrl z使其被挂起暂停此时打印出的[1] Stopped sleep 100001表示任务号表示是默认任务stopped表示任务状态sleep 10000表示任务执行的命令。在这里插入图片描述我们可以新开一个终端获取当前进行信息发现其的状态为T。随后我们执行bg 1将其又恢复到后台。在这里插入图片描述我们可以通过jobs命令令查看本用户当前后台执⾏或挂起的作业。上面我们提到了键入 Ctrl Z 可以将前台作业挂起实际上是将 STGTSTP 信号发送至前台进程组作业中的所有进程 后台进程组中的作业不受影响。 在 unix系统中 存在 3 个特殊字符可以使得终端驱动程序产生信号 并将信号发送至前台进程组作业 它们分别是Ctrl C 中断字符 会产生 SIGINT 信号Ctrl \ 退出字符 会产生 SIGQUIT 信号Ctrl Z挂起字符 会产生 STGTSTP 信号终端的 I/O(即标准输入和标准输出)和终端产生的信号总是从前台进程组作业连接打破实际终端。在我们的Windows系统中呢其实也是这样的。我们每一个用户都是一个会话在电脑很卡时点击注销回到登录页面这个注销等于在Xshell中直接关闭链接会话中的所有的进程都会被收到影响。那么能不能创建一个子进程独立形成一个会话。使得这个进程不受用户登录注销的影响守护进程就是这样的存在。7、守护进程守护进程是 Linux/Unix 系统中的一种长期运行的后台服务进程独立于终端会话通常在系统启动时自动运行持续提供某种功能如 Web 服务、日志管理等。不同于后台进程依旧属于当前会话守护进程是属于一个独立的会话他会脱离于当前终端比如服务器它的io是从网络中读取日志写到磁盘文件里。想要创建一个守护进程有两种方式第一种就是直接使用系统调用在这里插入图片描述第二种方式就是手动的创建需要注意的是我们想要创建的守护进程不能是组长也就是一个进程组里的第一个进程。所以我们一般会先fork出一个进程让父进程销毁随后子进程变成一个孤儿进程虽然与之前的父进程在同一个进程组但是子进程不会是组长。具体实现1、首先守护进程一般会屏蔽特定的异常信号代码语言javascriptAI代码解释void Daemon() { //1、屏蔽特定异常信号 signal(SIGCHLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); }其中屏蔽第一个信号是为了防止守护进程因未处理的子进程退出而变成僵尸进程屏蔽第二个信号是为了防止因向已关闭的管道或套接字写入数据导致进程意外终止。第二步保证为非组长这里就要涉及之前说的使用子进程了//2、设置不为组长 if(fork()0) { exit(0);//使父进程退出子进程变成孤儿满足不是组长的条件 }第三步建立新会话我们可以使用系统调用在这里插入图片描述直接给当前执行函数的进程变为一个新的会话:setsid();第四步每一个进程都有自己的CWD我们需要根据用户意愿选择是否将当前进程的CWD更改成为 / 根目录这是为了在使用文件时将其路径转化为了绝对路径方便管理与使用。//4、每一个进程都有自己的CWD是否将当前进程的CWD更改成为 / 根目录 if(ischdir) { chdir(ROOT);//设置为根目录 }第五步此时该进程已经变成守护进程啦不需要和用户的输入输出错误进行关联了但是我们要根据用户意愿选择是否将其取消关联。如果不需要就直接关闭0,1,2文件描述符。如果需要输入输出就把这些结果重定向到一个黑洞文件中/dev/null //5、已经变成守护进程啦不需要和用户的输入输出错误进行关联了代码语言javascriptAI代码解释if (isclose) { ::close(0); ::close(1); ::close(2); } else { int fd ::open(devnull, O_WRONLY); if (fd 0) { // 各种重定向 dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } }其中/dev/null是 Linux 系统中的空设备文件其核心特性写入黑洞所有写入它的数据会被直接丢弃返回成功但数据消失。读取空值从它读取会立即返回 EOF文件结束符。:代码语言javascriptAI代码解释#pragma once #include iostream #include cstdlib #include signal.h #include unistd.h #include fcntl.h #include sys/types.h #include sys/stat.h #define ROOT / #define devnull /dev/null void Daemon(bool ischdir, bool isclose) { //1、屏蔽特定异常信号 signal(SIGCHLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); //2、设置不为组长 if(fork()0) { exit(0);//使父进程退出子进程变成孤儿满足不是组长的条件 } //3、建立新会话 setsid(); //4、每一个进程都有自己的CWD是否将当前进程的CWD更改成为 / 根目录 if(ischdir) { chdir(ROOT);//设置为根目录 } //5、已经变成守护进程啦不需要和用户的输入输出错误进行关联了 if (isclose) { ::close(0); ::close(1); ::close(2); } else { int fd ::open(devnull, O_WRONLY); if (fd 0) { // 各种重定向 dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); } } }当我们完成这个代码后就可以把我们之前的网络版计算机的服务端变成一个守护进程这样一旦我们启动服务端就算把终端关闭服务端进程也不会关闭因为他已经在我们的服务器中运行起来了。我们只需要添加该头文件并在服务端main函数开始时调用Daemon函数并把日志改成写入文件模式就可以开启我们的守护进程模式的服务端在这里插入图片描述运行代码可以看见我们的服务端已经不在前台运行了此时我们把终端关闭不会影响它的运行。如果我们想关闭就只能通过kill命令了

相关新闻