当前位置: 首页 > article >正文

Linux网络:守护进程

Linux网络:守护进程

    • 会话
      • 进程组
      • 会话
      • 终端
    • 守护进程
      • setsid
      • daemon


在创建一个网络服务后,往往这个服务进程是一直运行的。但是对于大部分进程来说,如果退出终端,这个终端上创建的所有进程都会退出,这就导致进程的生命周期与终端绑定,如果终端退出,服务就无法继续运行了。

为此,创建一个服务进程后,需要让其不在受到终端的影响,就算终端退出,进程依然执行,这种进程称为守护进程

在讲解如何创建一个守护进程之前,先了解一些Linux中与会话相关的知识。

会话

进程组

进程组是一个或者多个进程的集合,这些进程都有同样的进程组ID(PGID),这个ID和PID类似,都是一个正整数,可以放在pid_t类型中。

往往一个进程组内部的所有进程,要共同完成一个功能,这些进程之间可以进行通信。而当在Linux中通过管道命令创建多个进程,此时Linux认为多个进程是共同完成一个功能的,就会把它们放到同一个进程组中。

通过管道创建三个sleep进程:

在这里插入图片描述

创建进程后,通过ps -ajx查看进行信息,可以看到三个经常的PGID都是54844,也就是sleep 100这条命令的PID。这说明这三个由管道串联起来的进程,属于同一个进程组。而sleep 100这个进程是进程组长

进程组长往往是进程组中的第一个进程,其可以在当前进程组中创建新的进程,也可以创建一个新的进程组。

但是如果进程组长退出了,不代表进程组退出了,就算进程组长退出了,只要进程组中还有进程,那么这个进程组依然存在。

在这里插入图片描述

通过kill杀掉组长进程后,剩余的两个进程依然存在,并且PGID依然是54844


会话

一个Linux系统,可以有多个会话,每个会话都是一个或多个进程组的集合。

创建一个新的会话,并在会话中执行三个sleep进程:

在这里插入图片描述

在原先的会话中,查看bash进程和sleep进程的信息:

在这里插入图片描述

此处-E "bash|sleep"表示查找bash或者sleep进程。

其中SID表示会话ID,可以看到第一个bashSID = 53792,第二个bashSID = 54989

这两个bash本身也是进程,它们的PID == PGID == SID。这种PID == SID的进程,称为话首进程,它是会话中的第一个进程。一般而言,话首进程都是bash

而创建的三个sleep进程,它们的SID = 54989,与第二个bash相同,说明它们属于同一个会话,而54989会话中,包含四个进程以及两个进程组。而53792会话中只有一个进程和一个进程组。

这说明一个会话是一个或多个进程组的集合。当一个话首进程退出,这个会话内部的所有进程都会退出

在一个会话中创建三个sleep进程:

在这里插入图片描述

随后把bash话首进程杀掉:

在这里插入图片描述

可以看到,不仅仅话首进程退出了,三个sleep进程一起退出了。


终端

一个会话不仅仅包含多个进程组,它还需要一个控制终端,来接受用户的指令。

当创建一个会话,会执行以下两步:

  1. 创建一个终端
  2. 启动一个bash进程(组)

终端本质上是一个Linux的文件,存在于/dev/pts目录下。

在这里插入图片描述

当前我创建了两个会话,那么就有两个终端,这两个终端分别对应01这两个文件。

如果尝试删除这些文件会被拒绝,哪怕你是root用户,因为这些文件是由内核管理的。

除去两个终端文件,还有一个ptmx,这个文件是用于创建终端的文件,如果这个文件丢失,可能就无法创建新的终端了。

在一个会话中,包含多个进程组,这些进程组分为两类:

  1. 前台进程组:一个会话只有一个,这个进程组独占终端的输入输出流
  2. 后台进程组:一个会话可以有多个,无法接收到来自终端的数据,在后台运行

在这里插入图片描述

比如向终端输入ctrl + c,就是一个中断信号,此时这个信号会发送给前台进程,导致前台进程退出。

但是并不是所有的信号都直接发送给前台进程组,因为有一个特殊的bash进程,如果某些信号的功能是直接挂断整个会话,那么这个信号会发送给bash,哪怕bash不是当前的前台进程组。

至此,可以这样理解Linux中的会话机制:

  1. 一个进程组管理多个进程,这些进程往往共同完成一个功能
  2. 一个会话管理多个进程组,当会话退出,该会话下的所有进程组都退出
  3. 终端是会话的对外表现,一个会话只有一个进程组可以占用终端

刚才提到,一个终端本质就是/dev/pts下的一个文件,其实与终端交互,就是进程在读写这个文件。那么一个进程如何知道要读取/dev/pts下的哪一个文件?会话又是如何保证其下的所有进程都使用同一个终端的?

终端的信息存储在进程的PCB中,先前说过会话创建包括一个终端和一个bash,而这个过程中,会把这个终端的信息存储到bashPCB

而在一个会话中创建的所有进程,都是bash的后代进程,会继承来自bashPCB,也就会继承到PCB中的终端信息,从而保证同一会话的所有进程最后都操控同一个终端


守护进程

了解Linux的会话机制后,就可以谈一谈守护进程的问题了。

先前说过,当一个会话的话首进程退出,那么该会话的所有进程都会退出,因此一个守护进程一定不能属于其它会话,而是自己创建一个会话,自己就是话首进程

setsid

setsid可以创建一个新会话并把自己变成话首进程,需要头文件<sys/types.h><unistd.h>

函数声明:

pid_t setsid(void)

调用该函数有一个前提,该进程不能是进程组长

但是对于一个新创建的进程,它自成一个进程组,自己就是进程组长,如何才能让它不是进程组长?

尝试以下代码:

#include <unistd.h>

int main()
{
	fork();
    while (true)
    {}

    return 0;
}   

这个代码中,通过fork创建了一个子进程,随后父子进程同时陷入死循环。

运行代码:

在这里插入图片描述

此时终端被阻塞,切换到另一个终端,查看test.exe这个进程的信息。

在这里插入图片描述

此时查询到了两个进程,一个是父进程,另一个是子进程,重点在于它们的GPID都是52746,这说明通过fork创建的父子进程属于同一个进程组

frok创建的子进程会继承来自父进程的所有代码,PCB等信息。因此如果想要让一个进程调用setsid创建一个新会话,只需要通过fork创建一个子进程,子进程继承父进程的所有信息,但又不是进程组长,可以调用setsid

因此setsid的常见写法如下:

pid_t id = fork();
if (id > 0)
    exit(0);

setsid();

这段代码中,通过fork创建一个子进程,此时父子进程属于同一个进程组,父进程是组长。如果id > 0说明是父进程,父进程直接退出,一个进程组中,进程组长退出,进程组其他进程继续运行,不会退出。因此子进程不会退出,继承了父进程的所有信息,并且还不是进程组长。

于是子进程调用setsid,自己创建一个会话,自己做话首进程,至此子进程就是一个守护进程了!

运行以下代码:

#include <unistd.h>
#include <sys/types.h>

int main()
{
    pid_t id = fork();
    if (id > 0)
        exit(0);

    setsid();
    while (true)
    {}

    return 0;
}

这个代码在子进程变为守护进程后,执行一个死循环。

在这里插入图片描述

执行./test.exe后,查看bashtest进程的信息,发现test.exe这个进程PID=PGID=SID=58278,并且不和任何一个bash重复。这说明test.exe已经自己创建一个会话,自己做话首进程,成为了一个守护进程了。

但是还有一个问题,那就是它的终端没有切断:

在这里插入图片描述

可以看到它的三个标准流,都指向/dev/pts/0,这就是当前的终端文件。如果当前终端退出,那么如果这个守护进程还向终端输出内容,就会导致错误,因此还要改变它的输出流:

// 关闭标准输入、输出和错误
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

// 打开/dev/null作为标准输入、输出和错误
open("/dev/null", O_RDONLY);
open("/dev/null", O_WRONLY);
open("/dev/null", O_WRONLY);

此处的/dev/null是一个Linux提供的文件,它可以接收任何输入,但是不论输入什么都会被系统丢弃。因此如果一个程序有输出,但是又不希望接收到它的输出时,就可以把输出重定向到/dev/null下。


daemon

setsid的用法还是有点复杂了,为此Linux提供了另一个系统调用daemon,它封装了上述所有过程,可以快速创建一个守护进程。需要头文件<unistd.h>

函数声明:

int daemon(int nochdir, int noclose)

参数:

  • nochdir:改变工作目录
    • 传入0:改变工作目录为根目录
    • 传入1:保持当前工作目录
  • nclose:改变输入输出流
    • 传入0:输入输出流重定向到/dev/null
    • 传入1:不改变输入输出流

原先的代码就可以变成:

#include <unistd.h>
#include <sys/types.h>

int main()
{
	daemon(0, 0);
	while(true)
	{}
	
    return 0;
}

这就是一个功能全面的守护进程了。



http://www.kler.cn/a/400710.html

相关文章:

  • 【网络云计算】2024第48周-技能大赛-初赛篇
  • HTTP 1.0、HTTP 1.1 和 HTTP 2.0 区别
  • FastAPI 中间件详解:实现高性能 Web 应用的完整指南和实际案例
  • 多线程4:线程池、并发、并行、综合案例-抢红包游戏
  • 基于普中51单片机开发板的电子门铃设计( proteus仿真+程序+设计报告+讲解视频)
  • Debezium-MySqlConnectorTask
  • 路由器基本原理与配置
  • easyExcel - 导出合并单元格
  • 深入理解VUE对象生命周期——从创建到销毁的完整流程
  • leetcode面试 150题之 三数之和 复刷日记
  • android 如何获取当前 Activity 的类名和包名
  • 论文阅读《Neural Map Prior for Autonomous Driving》
  • 【深度学习目标检测|YOLO算法6-27】YOLO家族进化史:从YOLOv1到YOLOv11的架构创新、性能优化与行业应用全解析...
  • 基于yolov8、yolov5的植物类别识别系统(含UI界面、训练好的模型、Python代码、数据集)
  • 2024 CCF中国开源大会“开源科学计算与系统建模openSCS”分论坛成功举办
  • 跨平台WPF框架Avalonia教程 八
  • 如何在项目中用elementui实现分页器功能
  • OceanBase 分区表详解
  • Vue监视属性变化watch
  • 25-Elasticsearch 数据建模实例
  • 模型的评估指标——IoU、混淆矩阵、Precision、Recall、P-R曲线、F1-score、mAP、AP、AUC-ROC
  • C++设计模式:抽象工厂模式(风格切换案例)
  • IDEA如何设置编码格式,字符编码,全局编码和项目编码格式
  • 静默绑定推广人方法修复
  • 微信小程序内嵌h5页面(uniapp写的),使用uni.openLocation无法打开页面问题
  • 计算机网络-理论部分(二):应用层