Linux守护进程和setsid()函数

在 Unix/Linux 系统编程中,守护进程(daemon)、进程组(process group)、会话(session)是系统编程中重要的概念,它们通常在创建和管理后台进程时扮演着关键角色。setsid() 是一个系统调用,用于创建一个新的会话(session)并将调用进程设置为新会话的领头进程(session leader)。它的声明通常在 <unistd.h> 头文件中。

#include <unistd.h>
pid_t setsid(void);

setsid()主要功能:

  • 创建新会话:调用 setsid() 会创建一个新的会话,包括一个新的进程组。
  • 设置进程为会话领头进程:调用进程成为新会话的领头进程,同时成为新进程组的组长进程(process group leader)。
  • 断开与控制终端的关联:新的会话不再与任何控制终端关联,这样可以避免后续进程中断信号(SIGHUP)的影响。这对于守护进程(daemon)特别重要,因为它们通常需要在后台运行而不受终端的影响。

守护进程(Daemon)
守护进程是在后台运行的一种特殊类型的进程,通常独立于控制终端并且没有用户交互界面。它们通常用于执行系统任务或服务,例如网络服务、日志记录等。守护进程的特点包括:

  • 脱离终端控制:守护进程通常通过 fork() 创建子进程,并在子进程中调用 setsid() 来创建新会话,从而脱离与任何终端的关联。
  • 自身的生命周期管理:守护进程通常需要自己处理信号、日志、配置文件等,而不依赖于用户的交互操作。

进程组(Process Group)
进程组是一组相关进程的集合,它们可以作为一个单元接收信号。进程组的特点包括:

  • 由一个进程组ID标识:每个进程组都有一个唯一的进程组ID(PGID),用于区分不同的进程组。
  • 方便信号处理:进程组可以方便地批量发送信号给组内的所有进程,例如 kill 命令可以发送信号给指定进程组。

会话(Session)
会话是一个或多个进程组的集合,通常与一个控制终端关联。会话的特点包括:

  • 由一个会话ID标识:每个会话都有一个唯一的会话ID(SID),用于标识该会话。
  • 包含一个控制终端:每个会话通常与一个控制终端(tty)关联,这允许用户与会话中的进程进行交互。
  • 会话领头进程:每个会话有一个会话领头进程(session leader),它是会话中第一个创建的进程,通常是调用 setsid() 后的进程。

关系和使用场景:

  • 守护进程和会话:守护进程通过调用 fork() 和 setsid() 来创建新会话并成为会话领头进程,从而脱离终端的控制,保证后台运行。
  • 信号处理和进程组:进程组的使用可以方便地向一组相关进程发送信号,这在实现进程间通信和协作时非常有用。
  • 多用户环境下的会话管理:在多用户系统中,每个用户登录会创建一个新的会话,用户的所有进程都会成为该会话的成员,便于管理和控制。

守护进程示例代码

#include <unistd.h>
#include <cstdlib>
#include <cstdio>
#include <sys/stat.h>
#include <sys/types.h>
#include <cerrno>
#include <cstring>

void daemonize() {
    // 1. Fork to create a child process
    pid_t pid = fork();
    if (pid < 0) {
        std::perror("fork");
        std::exit(EXIT_FAILURE);
    }
    if (pid > 0) {
        // Parent process: Exit
        std::exit(EXIT_SUCCESS);
    }

    // 2. Create a new session and become the session leader
    if (setsid() == -1) {
        std::perror("setsid");
        std::exit(EXIT_FAILURE);
    }

    // 3. Fork again to ensure we are not session leader
    pid = fork();
    if (pid < 0) {
        std::perror("fork");
        std::exit(EXIT_FAILURE);
    }
    if (pid > 0) {
        // Parent process: Exit
        std::exit(EXIT_SUCCESS);
    }

    // 4. Change the current working directory to root
    if (chdir("/") == -1) {
        std::perror("chdir");
        std::exit(EXIT_FAILURE);
    }

    // 5. Close all open file descriptors
    for (int fd = sysconf(_SC_OPEN_MAX); fd > 0; fd--) {
        close(fd);
    }

    // 6. Redirect standard I/O file descriptors to /dev/null
    freopen("/dev/null", "r", stdin);
    freopen("/dev/null", "w", stdout);
    freopen("/dev/null", "w", stderr);
}

int main() {
    // Create the daemon
    daemonize();

    // Now the process is running as a daemon
    // You can perform your daemon-specific tasks here

    // Example: Write to a log file
    FILE* logFile = fopen("/var/log/mydaemon.log", "a");
    if (logFile == nullptr) {
        perror("fopen");
        return EXIT_FAILURE;
    }
    fprintf(logFile, "Daemon started\n");
    fclose(logFile);

    // Simulate daemon running
    while (true) {
        // Daemon logic here...

        sleep(10);  // Example: Sleep for 10 seconds
    }

    return EXIT_SUCCESS;
}

daemonize 函数:

  • 第一次 fork:创建子进程,父进程退出。子进程成为新会话的领头进程,但仍可能成为会话组长。
  • setsid:调用 setsid() 创建新的会话,并确保子进程不是任何进程组的组长。
  • 第二次 fork:为了确保守护进程不会再次获得控制终端,再次创建子进程。父进程退出,子进程继续。
  • chdir:将当前工作目录更改为根目录,确保守护进程不占用任何挂载点。
  • 关闭文件描述符:关闭所有打开的文件描述符,防止从父进程继承打开的文件描述符影响守护进程。
  • 重定向标准 I/O:将标准输入、标准输出、标准错误重定向到 /dev/null,这样可以防止任何输入或输出影响守护进程。

main 函数:

  • 调用 daemonize() 函数,将当前进程转变为守护进程。
  • 在守护进程中,可以执行特定于守护进程的任务。例如,打开日志文件并写入启动信息。
  • 在示例中,守护进程简单地循环执行一些逻辑(例如休眠一段时间),模拟后台运行的情况。

注意事项
守护进程的编写需要考虑到各种情况下的错误处理,尤其是与系统调用相关的错误处理,以确保守护进程能够稳定运行。
守护进程通常会在后台默默运行,不会输出任何信息到控制台,所有的输出通常会被重定向到日志文件或者 /dev/null。
上述示例中的路径和文件名(如日志文件路径)应根据实际情况进行调整和修改,以符合具体的部署环境和安全策略。

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值