插件窝 干货文章 linux守护进程实例代码分析

linux守护进程实例代码分析

进程 守护 文件 include 507    来源:    2025-04-21

Linux守护进程实例代码分析

守护进程(Daemon)是在后台运行的特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。下面我将分析一个典型的Linux守护进程实现代码。

基本守护进程实现代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>

void daemonize()
{
    pid_t pid;

    /* 1. 创建子进程,父进程退出 */
    pid = fork();
    if (pid < 0) {
        perror("fork failed");
        exit(1);
    }
    if (pid > 0) {  // 父进程
        exit(0);     // 父进程退出
    }

    /* 2. 在子进程中创建新会话 */
    if (setsid() < 0) {
        perror("setsid failed");
        exit(1);
    }

    /* 3. 改变当前工作目录到根目录 */
    if (chdir("/") < 0) {
        perror("chdir failed");
        exit(1);
    }

    /* 4. 重设文件权限掩码 */
    umask(0);

    /* 5. 关闭文件描述符 */
    for (int x = sysconf(_SC_OPEN_MAX); x >= 0; x--) {
        close(x);
    }

    /* 6. 重定向标准输入、输出、错误到/dev/null */
    int fd = open("/dev/null", O_RDWR);
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    close(fd);
}

void sig_handler(int signo)
{
    if (signo == SIGTERM) {
        // 处理终止信号
        syslog(LOG_INFO, "received SIGTERM, daemon exiting");
        closelog();
        exit(0);
    }
}

int main()
{
    // 守护进程化
    daemonize();

    // 设置信号处理
    signal(SIGTERM, sig_handler);

    // 守护进程主循环
    while (1) {
        // 守护进程的主要工作代码

        sleep(10); // 示例:每10秒执行一次
    }

    return 0;
}

代码分析

1. 创建子进程,父进程退出

pid = fork();
if (pid > 0) {
    exit(0);  // 父进程退出
}
  • 使子进程成为孤儿进程,被init进程接管
  • 确保守护进程不是进程组首进程,为后续setsid调用做准备

2. 创建新会话

if (setsid() < 0) {
    perror("setsid failed");
    exit(1);
}
  • setsid()创建新会话,使进程成为新会话的首进程
  • 脱离原控制终端,成为独立进程

3. 改变工作目录

if (chdir("/") < 0) {
    perror("chdir failed");
    exit(1);
}
  • 将工作目录改为根目录,避免占用可卸载的文件系统

4. 重设文件权限掩码

umask(0);
  • 设置文件权限掩码为0,确保守护进程创建的文件有预期的权限

5. 关闭文件描述符

for (int x = sysconf(_SC_OPEN_MAX); x >= 0; x--) {
    close(x);
}
  • 关闭所有从父进程继承的文件描述符
  • 防止资源泄漏

6. 重定向标准I/O

int fd = open("/dev/null", O_RDWR);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
  • 将标准输入、输出、错误重定向到/dev/null
  • 防止守护进程在终端上产生输出

高级守护进程实现建议

  1. 使用syslog记录日志

    • 守护进程通常使用syslog记录运行状态和错误信息
    • 示例: c #include <syslog.h> openlog("mydaemon", LOG_PID, LOG_DAEMON); syslog(LOG_INFO, "Daemon started");
  2. 实现可靠的信号处理

    • 正确处理SIGTERM等信号
    • 使用sigaction替代signal以获得更可靠的行为
  3. 单实例保证

    • 通过锁文件确保只有一个守护进程实例运行
    • 示例: c int lockfile = open("/var/run/mydaemon.pid", O_RDWR|O_CREAT, 0640); if (lockfile < 0) exit(1); if (lockf(lockfile, F_TLOCK, 0) < 0) exit(0);
  4. 资源限制检查

    • 检查并设置适当的资源限制
    • 示例: c struct rlimit rl; getrlimit(RLIMIT_NOFILE, &rl);

现代替代方案

对于现代Linux系统,可以考虑使用以下更简单的方式创建守护进程:

  1. 使用daemon()函数

    if (daemon(0, 0) < 0) {
       perror("daemon failed");
       exit(1);
    }
    
  2. 使用systemd等init系统

    • 编写.service单元文件
    • 由init系统管理守护进程的生命周期

总结

一个健壮的Linux守护进程需要正确处理以下方面: 1. 进程关系(fork和setsid) 2. 文件系统和权限(工作目录和umask) 3. 输入输出重定向 4. 信号处理 5. 日志记录 6. 单实例保证

以上代码和分析提供了一个基本的守护进程实现框架,实际应用中需要根据具体需求进行调整和完善。