个人主页: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
分别为22449
、22403
、25410
分别控制着不同的终端(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 进程的 SID 为 24003 ,表明它们是由 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 1
:bg
命令用于将停止的进程(此处为作业号1
)恢复到后台继续运行。这样程序就可以继续执行,而不会占用终端。 -
./a.out &
:程序现在在后台运行,输出继续显示hello proc
。
守护进程
守护进程(Daemon)是计算机中在后台运行的一个长期存在的进程,通常在操作系统启动时自动启动,并在系统关闭时停止。它不与用户直接交互,而是负责执行一些后台任务,如定时任务、系统监控、服务提供等。
守护进程的特点
- 后台运行:守护进程不与终端直接交互,它们在后台运行,并且不会因用户登出而终止。、
- 自启动和长期运行:大多数守护进程在系统启动时自动启动,并且通常会一直运行直到系统关闭或守护进程被手动停止。
- 与终端分离:守护进程通常会与终端或控制台分离,这意味着它们不依赖于任何终端会话,甚至在用户退出登录后也会继续运行。
编写守护进程
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
,避免进程与终端交互。