守护进程(Daemon Process)是在 后台运行 的一种特殊类型的进程。它独立于终端会话,并且在系统启动时启动,并持续运行以提供特定的服务或执行特定的任务。守护进程通常以系统级别的服务方式运行,提供后台任务的管理和执行。

特点

  1. 后台运行:守护进程在后台运行,没有与用户终端相关联,不会向终端输出信息,也不会从终端接收输入。它们通常在系统启动时自动启动,并持续运行,直到系统关闭或手动停止。

  2. 无终端关联:守护进程与终端无关,它们不会受到终端关闭或用户退出的影响。它们通常在系统级别启动,不依赖于用户登录或终端会话。

  3. 独立于终端会话:守护进程不会与任何特定的终端会话相关联。它们不会收到终端信号(如Ctrl+C)并且不会受到用户登录或注销的影响。

  4. 任务服务:守护进程通常用于提供特定的服务或执行特定的任务。它们可以是网络服务、系统监控、定时任务、日志记录等。守护进程可以在后台运行,持续监控和处理任务,而不会影响其他用户进程或终端会话。

  5. 无交互性:守护进程通常是无交互性的,它们不会与用户进行直接的交互。它们的配置和控制通常通过配置文件、命令行选项或特定的管理工具进行。

在实现守护进程时,通常需要注意以下几个方面:

  • 重定向标准输入、输出和错误流:守护进程通常需要将标准输入、输出和错误流重定向到文件或设备,以避免与终端相关联。
  • 脱离终端会话:守护进程需要通过调用fork()函数创建子进程,并使子进程脱离终端会话,通过setsid()函数创建一个新的会话。
  • 重设文件权限掩码:守护进程需要调用umask()函数重设文件权限掩码,以确保创建的文件具有适当的权限。
  • 处理信号:守护进程通常需要处理一些重要的信号,如终止信号(SIGTERM)、重新加载配置信号(SIGHUP)等。

setsid

在Linux系统中,setsid()函数是一个系统调用,用于创建一个新的会话并设置新的进程组ID。它是守护进程创建过程中常用的一步。

具体来说,setsid()函数的作用是:

  1. 创建新的会话:调用setsid()函数会创建一个新的会话,使得当前进程成为该会话的首领进程(session leader)。该会话成为一个独立的会话,与原有的终端会话完全分离。

  2. 设置新的进程组ID:在新的会话中,setsid()函数将创建一个新的进程组,并将当前进程设置为该进程组的组长进程(group leader)。新的进程组ID与会话ID相同。

setsid()函数通常用于将守护进程从终端会话中脱离出来,使其成为一个独立的进程实体,独立于任何终端或终端会话。这样可以确保守护进程不受终端会话的影响,并能够在后台持续运行。

chdir

chdir()是一个系统调用,用于改变当前进程的工作目录。它是"change directory"的缩写。

函数原型如下:

int chdir(const char *path);

参数path是一个字符串,表示要改变到的目标目录的路径。

chdir()函数的作用是将当前进程的工作目录更改为指定的目录。它的使用方式 类似于在命令行中使用cd命令 改变目录。

使用chdir()函数时需要注意以下几点:

  1. 目录路径必须是有效的:传递给chdir()函数的目录路径必须是一个有效的目录路径,否则函数调用将失败并返回-1。

  2. 目录权限:在尝试改变工作目录之前,应该 确保当前进程对目标目录具有足够的权限 ,以便能够读取和写入该目录。

  3. 目录路径的格式:目录路径可以是相对路径或绝对路径。相对路径是相对于当前工作目录的路径,而绝对路径是从根目录开始的完整路径。

  4. 错误处理:chdir()函数在失败时会返回-1,并设置errno变量以指示具体的错误类型。可以使用perror()或strerror()函数来输出错误信息。

umask

umask是一个系统调用,用于设置文件创建时的默认权限掩码。它决定了文件和目录的默认权限。

函数原型如下:

mode_t umask(mode_t mask);

参数mask是一个权限掩码,用于指定要屏蔽的权限位。它是一个八进制数,表示要屏蔽的权限组合。通常使用八进制表示法来指定umask值,如0777表示屏蔽所有权限。

umask()函数的作用是设置当前进程的文件创建掩码,并返回之前的文件创建掩码。

文件创建掩码是一个权限位掩码,用于确定在创建新文件或目录时应该屏蔽哪些权限位。umask函数通过设置掩码来影响新文件和目录的默认权限。具体来说,umask值与新文件或目录的权限进行按位与操作,将屏蔽的权限位从新文件或目录的权限中去除。

例如,如果umask值设置为0022,表示屏蔽其他用户的写权限和组用户的写权限,那么创建新文件时,它的默认权限将是0644(所有者可读写,其他用户可读)。

需要注意以下几点:

  1. umask值的默认值通常是022,它使得新文件的默认权限为0644,新目录的默认权限为0755。这样设置的目的是为了确保文件和目录对所有者可读写,对其他用户只有读的权限。

  2. umask值可以在程序中通过调用umask函数来进行更改。通常在程序开始的时候可以设置合适的umask值,以确保文件和目录的默认权限符合需求。

  3. umask值是进程级别的,它 会影响到该进程及其所有子进程创建的文件和目录。它 不会影响已经存在的文件和目录的权限。

  4. umask值是进程的属性,不会被继承或传递给其他进程。

SIGHUP 信号

SIGHUP是一个UNIX和类UNIX系统中的信号,它代表着"挂起"(Hangup)信号。它通常由终端控制进程(如登录会话)发送给与终端设备连接的进程,用于通知该进程终端连接的状态发生了变化。

默认情况下,当进程接收到SIGHUP信号时,它的默认操作是终止(Terminate)。这意味着进程会立即退出并终止其执行。

对于守护进程来说,收到 SIGHUP 信号,通常意味着终端链接断开,我们当然不希望守护进程因此而退出,所以需要指定 SIGHUP 信号的处理方式

实例

#include <iostream>
#include <fstream>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

class Daemonize
{
public:
    // 执行守护进程的初始化
    Daemonize()
    {
        pid_t pid = fork();
        if(pid < 0)
        {
            throw std::runtime_error("Fork error!\n");
            exit(1);
        }
        else if(pid > 0) // 第一步:退出父进程
            exit(0);        
        
        // 第二步,在子进程中创建新会话,成为首领进程
        if(setsid() < 0)
        {
            throw std::runtime_error("Setsid error!\n");
            exit(1);
        }

        // 第三步,屏蔽 SIGHUP 信号,也可以自定义处理方式
        signal(SIGHUP, SIG_IGN);

        // 第四步,关闭标准输入、输出、错误,脱去与原终端的所有关联
        for(int fd = STDIN_FILENO; fd <= STDERR_FILENO; ++fd)
            close(fd);

        // 第五步,修改工作目录为工作目录,可以根据需求调整
        chdir("/Users/Sky_Lee/Documents/Linux/studyNotes/Linux operating system/Linux 守护进程");

        // 第六步,修改文件掩码,可以根据需求调整
        umask(0000); // 均不屏蔽
    }

    void run(void)
    {
        // 执行核心逻辑

        std::fstream fs;
        fs.open("log.txt");
        if(!fs.is_open())
            exit(1);
        fs << "------------------Daemonize------------------" << std::endl;
        fs << "[note]: pid: " << getpid() << std::endl;
        fs.close();

        while(1) // 模拟其它过程
        {
            sleep(1);
        }
    }
};

int main(void)
{
    Daemonize daemonize;
    daemonize.run();
}