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

文章目录

  • [1. 进程组](#1. 进程组)
  • [2. 会话](#2. 会话)
    • [2.1 什么是会话](#2.1 什么是会话)
    • [2.2 如何创建会话](#2.2 如何创建会话)
    • [2.3 守护进程](#2.3 守护进程)
  • [3. 作业控制](#3. 作业控制)

1. 进程组

我们运行下面的命令

bash 复制代码
sleep 10000 | sleep 20000 | sleep 30000

然后查看进程的信息:

可以看到,其实每一个进程除了有进程PID、PPID之外,还属于一个进程组(PGID)。 进程组是一个或者多个进程的集合,一个进程组可以包含多个进程,每一个进程组也有一个唯一的进程组 ID(PGID)。

进程组的ID一般是组长的ID,即组中最先创建的进程的PID。

  • 进程组组长的作用: 进程组组长可以创建一个进程组或者创建该组中的进程

  • 进程组的生命周期: 从进程组创建开始到其中最后一个进程离开为止。 注意:只要某个进程组中有一个进程存在, 则该进程组就存在, 这与其组长进程是否已经终止无关

2. 会话

2.1 什么是会话

系统登录时,会创建终端文件 + bash进程;会话 = bash进程 + 终端文件 (每一个新的登录,都会形成一个-bash进程)

终端文件:/dev/pts/

向不同的终端文件中输出,就会在其会话页面中显示

  • 会话其实和进程组息息相关,会话可以看成是一个或多个进程组的集合,一个会话可以包含多个进程组。
  • 每一个会话也有一个会话 ID(SID,一般是会话中的第一个进程,即bash )

下面是我在同一个会话中运行了两个程序查询出来的结果

2.2 如何创建会话

那如何创建一个会话呢?

可以调用 setsid 函数来创建一个会话, 前提是调用进程不能是一个进程组的组长。

c 复制代码
#
include <unistd.h>
/*
*功能: 创建会话
*返回值: 创建成功返回 SID, 失败返回-1
*/
pid_t setsid(void);

该接口调用之后会发生:

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

需要注意的是:

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

2.3 守护进程

守护进程(Daemon Process)是一种在后台运行的特殊进程,通常用于执行特定的任务或服务,而不需要用户直接交互。

守护进程的特点

  • 后台运行:守护进程在后台运行,不会在前台显示任何界面或交互窗口。它独立于用户终端,即使用户注销,守护进程仍然可以继续运行
  • 独立于控制终端:守护进程通常与控制终端分离,这意味着它不会因终端的关闭而终止。例如,一些网络服务(如 Web服务器)会以守护进程的形式运行,确保服务的持续可用性。

那如何创建守护进程呢?

  1. 创建子进程并退出父进程:通过 fork() 创建子进程,父进程退出。这样可以确保子进程不会被终端挂起。
  2. 创建新会话:通过 setsid() 创建一个新的会话,使子进程成为会话的领导者,从而脱离控制终端
  3. 改变工作目录:通常将工作目录更改为根目录(/),以避免守护进程因当前工作目录被卸载而导致问题。
  4. 关闭文件描述符:关闭所有打开的文件描述符,避免资源泄漏。
cpp 复制代码
const char *root = "/";
const char *dev_null = "/dev/null";     
void Daemon(bool ischdir,bool isclose)
{
    //1. 忽略可能引起程序异常退出的信号
    signal(SIGCHLD,SIG_IGN);
    signal(SIGPIPE,SIG_IGN);

    int rid = fork();   //2.创建子进程
    if(rid > 0)
    {
        exit(1);    //父进程直接退出
    }

    int id = setsid();  //3.子进程调用,子进程独立成立一个会话

    // 4. 每一个进程都有自己的 CWD, 是否将当前进程的 CWD 更改成为 /根目录
    //以避免守护进程因当前工作目录被卸载而导致问题
    if(ischdir)
        chdir(root);

    // 5. 已经变成守护进程啦,不需要和用户的输入输出,错误进行关联了
    if(isclose)
    {
        close(0);
        close(1);
        close(2);
    }
    else
    {
        //避免后面有向0,1,2中操作的,为了防止失败
        //一般将0、1、2重定向到"黑洞"(任何写入到 /dev/null 的数据都会被永久丢弃,不会保存在任何地方)
        int fd = open(dev_null,O_RDWR);
        if(fd > 0)
        {
            dup2(fd,0);
            dup2(fd,1);
            dup2(fd,2);
            close(fd);  //避免fd泄漏,重定向后关闭
        }
    }
}

如何将一个服务器守护进程化呢?特别简单,直接调用Daemon即可

cpp 复制代码
// ./server port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage : " << argv[0] << " port" << std::endl;
        return 0;
    }
    uint16_t localport = std::stoi(argv[1]);
    Daemon(false, false);
    std::unique_ptr<TcpServer> svr(new TcpServer(localport,HandlerRequest));
    svr->Loop();
    return 0;
}

即使当前会话关闭了,守护进程依旧还在。它是一个独立的会话

3. 作业控制

  1. 什么是作业?

作业是针对用户来讲的,一个进程组要完成的任务,一般叫做作业。

一个作业既可以只包含一个进程,也可以包含多个进程,进程之间互相协作完成任务(通常是通过进程管道)。

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

例如:下面通过管道协作的sleep命令就是一个作业

此时无论执行什么(只要会话不退出),都不会影响后台作业。

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

  1. 作业控制

将作业放回前台:fd + 作业号

如果再想将作业放回后台,要先暂停作业(Ctrl + z),然后再bg + 作业号,才可将作业切回到后台运行。

我们可以直接通过输入 jobs 命令查看本用户当前后台执⾏或挂起的作业

  • 参数-l 则显示作业的详细信息
  • 参数-p 则只显示作业的 PID

关于作业号后面的+、-号

对于一个用户来说,只能有一个默认作业(+号),同时也只能有一个即将成为默认作业的作业(-号),当默认作业退出后,该作业会成为默认作业。

  • +:表示该作业号是默认作业
  • -:表示该作业即将成为默认作业
  • 无符号:表示其他作业
  1. 作业状态

常见的作业状态如下表所示:

  1. 作业控制相关的信号

上面我们提到了键入 Ctrl + Z 可以将前台作业挂起,实际上是将 STGTSTP 信号发送至前台进程组作业中的所有进程, 后台进程组中的作业不受影响。

在 unix系统中, 存在 3 个特殊字符可以使得终端驱动程序产生信号,并将信号发送至前台进程组作业,它们分别是:

  • Ctrl + C: 中断字符, 会产生 SIGINT 信号
  • Ctrl + \: 退出字符, 会产生 SIGQUIT 信号
  • Ctrl + Z: 挂起字符, 会产生 STGTSTP 信号
相关推荐
多多*4 小时前
Java设计模式 简单工厂模式 工厂方法模式 抽象工厂模式 模版工厂模式 模式对比
java·linux·运维·服务器·stm32·单片机·嵌入式硬件
南鸳6106 小时前
Linux常见操作命令(2)
linux·运维·服务器
fengwuJ6 小时前
Linux安装Idea
linux·ubuntu·debian·intellij-idea
西北大程序猿7 小时前
linux进程信号 ─── linux第27课
linux·运维·服务器·信号处理
涛ing7 小时前
【Git “fetch“ 命令详解】
linux·c语言·c++·人工智能·git·vscode·svn
想躺在地上晒成地瓜干7 小时前
树莓派超全系列文档--(18)树莓派配置音频
linux·音视频·树莓派·raspberrypi·树莓派教程
宅小海8 小时前
14 配置Hadoop集群-配置历史和日志服务
linux·服务器·hadoop
孤客网络科技工作室8 小时前
每天学一个 Linux 命令(7):cd
java·linux·前端
hanpfei9 小时前
PipeWire 音频设计与实现分析一——介绍
linux·音视频
想躺在地上晒成地瓜干9 小时前
树莓派超全系列文档--(17)树莓派配置显示器
linux·树莓派·raspberrypi·raspi-config