守护进程(精灵进程)

1.守护进程的概念及相关的概念

守护进程也叫精灵进程,指的是脱离终端,独立运行在后台,长期运行,用来提供服务的进程。

守护进程的特点:

1.不依赖任何终端(关闭终端守护进程而不会退出)。

2.父进程通常是init或者是systemd;

3.用于提供服务,网络服务,定时服务等。

1. 进程组(Process Group)
  • 定义:一个或多个进程的集合,用于统一管理(如发送信号)。
  • 特点:
    • 每个进程组有唯一的进程组 ID(PGID)
    • 进程组的生命周期从创建开始,到最后一个进程离开(终止或加入其他组)结束。
2. 组长进程(Process Group Leader)
  • 定义:进程组的创建者,其PID = PGID
  • 特点:组长进程可以创建进程组和组内进程,但组长进程终止不会影响进程组(只要还有其他进程存在)。
3. 会话(Session)
  • 定义:一个或多个进程组的集合,通常与一个终端关联(控制终端)。
  • 特点:
    • 每个会话有唯一的会话 ID(SID)
    • 一个会话最多有一个控制终端(用于输入输出);
    • 会话中的进程组分为前台进程组 (与终端交互)和后台进程组(不交互)。
4. 会话首进程(Session Leader)
  • 定义:会话的创建者,其PID = SID
  • 特点:只有会话首进程能重新获取控制终端(这也是守护进程需要二次 fork 的原因)。

二、详细解析守护进程实现步骤

1. 第一次 fork()
  • 作用:让父进程退出,子进程继续运行。
  • 原因
    • 父进程通常是 shell 进程,退出后子进程变成孤儿进程,由init/systemd收养;
    • 确保子进程不是进程组组长(为后续setsid创造条件,因为组长进程不能创建新会话)。
2. setsid()
  • 作用:创建一个新会话,使子进程成为新会话的首进程和新进程组的组长,并脱离原控制终端。
  • 底层逻辑
    • 新会话的 SID = 子进程 PID;
    • 新进程组的 PGID = 子进程 PID;
    • 子进程不再关联任何控制终端。
3. 第二次 fork()
  • 作用:让子进程(会话首进程)退出,孙进程继续运行。
  • 原因
    • 会话首进程可以重新获取控制终端,二次 fork 后孙进程不再是会话首进程,彻底避免重新关联终端;
    • 进一步确保守护进程的独立性。
4. chdir("/")
  • 作用:将工作目录切换到根目录。
  • 原因
    • 守护进程长期运行,若工作目录在其他文件系统(如 U 盘),会导致该文件系统无法卸载;
    • 根目录始终存在,安全性高。
5. umask(0)
  • 作用:将文件权限掩码设为 0。
  • 原因
    • 守护进程可能需要创建文件,继承的权限掩码(如 022)会限制文件权限;
    • 设为 0 后,创建文件时权限完全由open/creat的参数决定(如0644)。
6. close() 关闭文件描述符
  • 作用:关闭从父进程继承的所有文件描述符(尤其是 0、1、2,对应标准输入、输出、错误)。
  • 原因
    • 守护进程脱离终端,这些文件描述符不再使用,浪费资源;
    • 避免后续操作(如open)意外使用这些描述符导致错误。
  • 优化 :通常关闭0-2后,将它们重定向到/dev/null(空设备),防止程序因读写标准 IO 出错。

三、守护进程代码演示(C 语言)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <syslog.h>

void create_daemon() {
    pid_t pid;

    // 1. 第一次fork
    pid = fork();
    if (pid < 0) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    }
    if (pid > 0) {
        exit(EXIT_SUCCESS); // 父进程退出
    }

    // 2. 创建新会话
    if (setsid() < 0) {
        perror("setsid failed");
        exit(EXIT_FAILURE);
    }

    // 3. 忽略SIGHUP信号(防止会话首进程退出时孙进程被终止)
    signal(SIGHUP, SIG_IGN);

    // 4. 第二次fork
    pid = fork();
    if (pid < 0) {
        perror("second fork failed");
        exit(EXIT_FAILURE);
    }
    if (pid > 0) {
        exit(EXIT_SUCCESS); // 会话首进程退出
    }

    // 5. 切换工作目录到根目录
    if (chdir("/") < 0) {
        perror("chdir failed");
        exit(EXIT_FAILURE);
    }

    // 6. 设置文件权限掩码为0
    umask(0);

    // 7. 关闭所有继承的文件描述符
    // 先获取最大文件描述符数
    int max_fd = sysconf(_SC_OPEN_MAX);
    if (max_fd == -1) {
        max_fd = 1024; // 默认值
    }
    for (int i = 0; i < max_fd; i++) {
        close(i);
    }

    // 8. 重定向0、1、2到/dev/null
    open("/dev/null", O_RDWR); // 0
    dup(0); // 1
    dup(0); // 2

    // 9. 初始化syslog(用于守护进程日志输出)
    openlog("mydaemon", LOG_PID, LOG_DAEMON);
}

int main() {
    create_daemon();

    // 守护进程的核心逻辑:每隔10秒写一条日志
    while (1) {
        syslog(LOG_INFO, "Daemon is running...");
        sleep(10);
    }

    closelog();
    return 0;
}
代码解析
  1. 第一次 fork:父进程退出,子进程成为孤儿进程。
  2. setsid:子进程成为新会话和新进程组的组长,脱离终端。
  3. 忽略 SIGHUP:防止第二次 fork 后,会话首进程退出时孙进程收到 SIGHUP 信号被终止。
  4. 第二次 fork:孙进程不再是会话首进程,彻底避免重新关联终端。
  5. chdir("/"):切换到安全的根目录。
  6. umask(0):确保文件创建权限不受限制。
  7. 关闭文件描述符:释放资源,避免意外操作。
  8. 重定向到 /dev/null:让标准 IO 操作 "空转",防止程序出错。
  9. syslog :守护进程无法使用 printf,通过 syslog 将日志写入系统日志(/var/log/syslog/var/log/messages)。

四、补充:守护进程的管理

  1. 查看守护进程

    复制代码
    ps aux | grep mydaemon
  2. 查看日志

    复制代码
    tail -f /var/log/syslog
  3. 终止守护进程

    复制代码
    kill <daemon_pid>
相关推荐
Suhan421 小时前
新版本Docker Desktop 自定义安装路径和下载镜像地址路径修改(附must be owned by an elevated account问题解决)
运维·docker·容器·eureka
拓朗工控1 小时前
工控机上电开机:工业自动化的脉搏启动瞬间
运维·自动化·工控机
|_⊙1 小时前
Linux 进程地址空间
linux·运维·服务器
RisunJan1 小时前
Linux命令-nm(列出目标文件(可执行文件、对象文件、库文件)中的符号)
linux·运维
c++逐梦人2 小时前
⽹络基础概念
linux·网络
UrSpecial2 小时前
TCP服务器并发模型:单线程、多线程与Select实现
服务器·网络·网络协议·tcp/ip
杨云龙UP2 小时前
Oracle / ODA环境TRACE、alert日志定位与ADRCI清理 SOP_20260423
linux·运维·服务器·数据库·oracle
@土豆2 小时前
Jenkins CI_CD流水线案例
运维·ci/cd·jenkins
深色風信子2 小时前
Docker sub2api
运维·docker·容器·sub2api