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

个人主页:chian-ocean

文章专栏-NET

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

前言

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

进程信息字段含义

字段 含义 用途 示例
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

  • 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

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

在此会话中我们执行

bash 复制代码
sleep 100 | sleep 100 | sleep 100 

观察进程 sleep SID = 24003

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

bash : 会话的领导者

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

前后台进程

定义

  • 前台进程 是那些直接与用户交互并占用当前终端的进程,通常它们会接收用户输入并显示输出。
cpp 复制代码
#include <iostream>                                                                     
#include <unistd.h>    
    
using namespace std;    
    
int main ()    
{    
    while(1)    
    {    
        cout << "hello proc "<<endl;    
        sleep(1);    
    }    
    return 0;    
}
  • 执行代码在前台进程,执行指令(./文件名):
  • 后台进程 则是在后台运行的进程,它们不直接与用户交互,不占用终端,通常用于执行长时间运行的任务,如守护进程或定时任务。

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

bash 复制代码
./文件名 &

后台进程的特点

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

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

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

前后台进程的操作

jobs显示后台作业

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

fg将后台作业带回前台

bash 复制代码
fg -进程编号

分析

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

bg将后台作业带回前台

bash 复制代码
bg -进程编号

分析:

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

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

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

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

守护进程

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

守护进程的特点

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

编写守护进程

cpp 复制代码
#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,避免进程与终端交互。
相关推荐
小慧102419 分钟前
2.1话题发布
linux·ros
夜影风1 小时前
Linux系统中自签名HTTPS证书
linux·运维·https
wb1891 小时前
流编辑器sed
运维·笔记·ubuntu·云计算
成工小白2 小时前
【Linux】C语言模拟实现shell命令行(程序替换原理)
linux·运维·服务器
西装没钱买3 小时前
C语言多进程TCP服务器与客户端
服务器·c语言·tcp/ip·进程
福理原乡大王4 小时前
Linux信号详解
linux·运维·服务器·c++·ubuntu·信号处理
锅锅是锅锅4 小时前
ubuntu调整硬盘大小-使用gparted
linux·ubuntu·硬盘·gparted
ldq_sd5 小时前
centos 8.3(阿里云服务器)mariadb由系统自带版本(10.3)升级到10.6
服务器·阿里云·centos
孙克旭_5 小时前
day031-Shell自动化编程-数组与案例
linux·运维·自动化
喜欢踢足球的老罗5 小时前
自动化模型管理:MediaPipe Android SDK 中的模型文件下载与加载机制
android·运维·自动化