云计算百科
云计算领域专业知识百科平台

永不休眠:Linux 守护进程的工作原理

个人主页:chian-ocean

文章专栏-NET

永不休眠:Linux 守护进程的工作原理

    • 个人主页:chian-ocean
    • 文章专栏-NET
  • 前言
  • 进程信息字段含义
    • 进程组ID(`PGID`)
    • 会话ID(`SID`)
  • 前后台进程
    • 定义
      • 后台进程的特点
    • 前后台进程的操作
      • `jobs`显示后台作业
      • `fg`将后台作业带回前台
      • `bg`将后台作业带回前台
  • 守护进程
    • 守护进程的特点
    • 编写守护进程

前言

在 Linux 系统中,守护进程(Daemon)是指那些在后台运行的进程,通常不与用户直接交互,而是提供某种服务或者完成系统任务。守护进程通常在系统启动时启动,并在系统运行时持续存在。

在这里插入图片描述

进程信息字段含义

image-20250613220101763

字段含义用途示例
PPID 父进程 ID 表示当前进程的父进程的进程 ID。帮助追踪进程的父子关系。 23449
PID 进程 ID 唯一标识当前进程。通过 PID 可以管理进程。 24284
PGID 进程组 ID 标识当前进程所在的进程组。用于管理相关进程。 24284
SID 会话 ID 标识当前进程所属的会话 ID。会话是共享同一个控制终端的进程集合。 23449
TTY 终端类型 指当前进程所使用的终端设备。 pts/0
TPGID 控制终端的进程组 ID 标识该进程所属的控制终端的进程组。 24284
STAT 进程状态 表示进程当前的状态。常见状态包括休眠、运行、停止等。 S+
UID 用户 ID 表示当前运行该进程的用户的标识符。 1000
TIME 进程占用的 CPU 时间 显示该进程使用的 CPU 时间,格式为“分钟:秒”。 0:00
COMMAND 执行的命令 显示当前进程正在执行的命令或程序。 sleep 100

进程组ID(PGID)

image-20250613220115253

  • PGID(进程组 ID) 表示进程所在的进程组的标识符。在该输出中,所有进程的 PGID 都是 25091,说明这些进程都属于同一个进程组。

  • 这个进程组的 leader(进程组长) 是 PID 25091(它的 PGID 值是它自己的 PID),也就是说,这个进程(PID 25091)是该进程组的领导者。该进程组中的其他进程(例如 PID 25092、25093)都是由该进程创建的,它们与进程组共享同一个 PGID。

为什么 PGID 相同?

  • 进程组是一个将多个相关进程组织在一起的机制。在这个例子中,sleep 100 进程的多个实例(PID 25091、25092、25093)都属于同一个进程组,它们的 PGID 是相同的。
  • 这种设计允许操作系统同时管理多个进程,比如向整个进程组发送信号,进行进程组控制等。

会话ID(SID)

image-20250613220119253

  • 在这里面我们创建了三个会话,SID 分别为 22449、22403、25410分别控制着不同的终端( TTY)

在此会话中我们执行

sleep 100 | sleep 100 | sleep 100

image-20250613220122467

观察进程 sleep 的 SID = 24003:

  • 所有显示的 sleep 进程的 SID 都是 24003。这表明所有这些 sleep 进程属于同一个会话,即它们共享相同的会话 ID。
  • SID 表示这些进程是同一个会话中的进程,会话中的所有进程都具有相同的 SID。

bash : 会话的领导者:

  • 会话的 SID 通常等于会话的 leader(会话领导进程)的 PID。因此,SID = 24003 表明会话中的领导进程的 PID 是 24003。这个进程的 PID 会作为会话的 SID。
  • 这些 sleep 进程的 SID 为 24003,表明它们是由 PID = 24003 启动的进程所创建,并且它们共享该会话。

前后台进程

定义

  • 前台进程 是那些直接与用户交互并占用当前终端的进程,通常它们会接收用户输入并显示输出。

#include <iostream>
#include <unistd.h>

using namespace std;

int main ()
{
while(1)
{
cout << "hello proc "<<endl;
sleep(1);
}
return 0;
}

  • 执行代码在前台进程,执行指令(./文件名):

image-20250613220127778

  • 后台进程 则是在后台运行的进程,它们不直接与用户交互,不占用终端,通常用于执行长时间运行的任务,如守护进程或定时任务。

  • 如果在后台进程的执行的指令就是:

./文件名 &

image-20250613220132041

后台进程的特点

  • 不占用终端:后台进程不会占用当前的控制终端(即用户的输入输出设备)。当你将一个进程放入后台运行时,终端仍然可以用来执行其他命令。

  • 不与用户交互:后台进程不直接与用户交互,因此它不会阻塞终端的使用。例如,你可以在后台运行一个长时间的下载任务,而继续在前台执行其他任务(如编辑文件、查看文件等)。

  • 异步执行:后台进程通常是异步执行的,即它们独立于当前正在运行的进程执行,不会干扰前台进程的执行

前后台进程的操作

jobs显示后台作业

image-20250613220135254

  • 当前证明有11个任务在同时跑。

fg将后台作业带回前台

fg -进程编号

image-20250613220343590

分析:

  • 后台运行程序:执行了 ./a.out &,该命令将程序 a.out 在后台运行,并返回了进程号 [1] 27998,表示后台程序正在运行。
  • 查看输出:输入了 hello proc,终端多次显示了这个字符串,可能是 a.out 程序的输出内容。
  • 调到前台:使用 fg 1 将后台运行的程序(作业号 1)调到前台,使其开始在前台运行。此时可以看到程序输出持续显示。
  • 终止程序:按下 CTRL + C(中断信号),终止了在前台运行的程序。

bg将后台作业带回前台

bg -进程编号

image-20250613220347268

分析:

  • ./a.out:执行这个命令来运行已编译的程序 a.out。程序打印了三次 hello proc 到终端。

  • ^Z:这是一个键盘快捷键,用来暂停(停止)正在运行的程序。程序被挂起,并显示消息 [1]+ Stopped,表示该进程已暂停。

  • bg 1:bg 命令用于将停止的进程(此处为作业号 1)恢复到后台继续运行。这样程序就可以继续执行,而不会占用终端。

  • ./a.out &:程序现在在后台运行,输出继续显示 hello proc。

守护进程

守护进程(Daemon)是计算机中在后台运行的一个长期存在的进程,通常在操作系统启动时自动启动,并在系统关闭时停止。它不与用户直接交互,而是负责执行一些后台任务,如定时任务、系统监控、服务提供等。

守护进程的特点

  • 后台运行:守护进程不与终端直接交互,它们在后台运行,并且不会因用户登出而终止。、
  • 自启动和长期运行:大多数守护进程在系统启动时自动启动,并且通常会一直运行直到系统关闭或守护进程被手动停止。
  • 与终端分离:守护进程通常会与终端或控制台分离,这意味着它们不依赖于任何终端会话,甚至在用户退出登录后也会继续运行。
  • 编写守护进程

    #pragma once // 防止该头文件被多次包含

    #include <iostream> // 引入输入输出流库
    #include <unistd.h> // 引入Unix标准函数库(比如fork, setsid等)
    #include <string> // 引入C++标准字符串库
    #include <signal.h> // 引入信号处理库(signal相关函数)
    #include <sys/types.h> // 包含系统数据类型(pid_t等)
    #include <sys/stat.h> // 引入文件状态和权限库
    #include <fcntl.h> // 引入文件控制库

    const std::string nullfile = "/dev/null"; // 定义一个常量 nullfile,表示 Unix 系统中的空设备文件 /dev/null

    void daemon(const std::string chdir = "")
    {
    // 忽略一些常见的信号
    signal(SIGCHLD, SIG_IGN); // 忽略子进程结束信号(不需要等待子进程)
    signal(SIGPIPE, SIG_IGN); // 忽略管道破裂信号
    signal(SIGSTOP, SIG_IGN); // 忽略停止信号(通常用户发送的 Ctrl+Z)

    // 将当前进程变为独立会话的领导者
    if (fork() > 0)
    {
    // 父进程退出,子进程继续
    exit(1);
    }
    setsid(); // 创建新的会话,脱离终端,成为会话领导者

    // 如果传入了工作目录路径,尝试更改目录权限
    if (!chdir.empty())
    chmod(chdir.c_str(), 0777); // chmod 需要传递权限值参数,这里假设为 0777

    // 打开 /dev/null 设备文件,以只读方式打开
    int fd = open(nullfile.c_str(), O_RDONLY);
    if (fd > 0)
    {
    // 将标准输入、输出和错误输出重定向到 /dev/null
    dup2(0, fd); // 将标准输入(fd 0)重定向到 /dev/null
    dup2(1, fd); // 将标准输出(fd 1)重定向到 /dev/null
    dup2(2, fd); // 将标准错误输出(fd 2)重定向到 /dev/null
    close(fd); // 关闭文件描述符
    }
    }

    • 忽略异常信号:守护进程通常不会干扰终端信号,因此忽略了与子进程、管道破裂和停止信号相关的信号。
    • 脱离终端:通过 fork() 和 setsid() 创建一个新的会话,使得守护进程不再受控制终端影响。
    • 修改工作目录:尝试修改工作目录(但代码存在错误,应改为 chdir())。
    • 重定向输入输出:将标准输入、输出和错误输出重定向到 /dev/null,避免进程与终端交互。
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 永不休眠:Linux 守护进程的工作原理
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!