什么是守护进程

  • 守护进程通常在系统引导装载时启动,并且在系统关闭之前一直运行。

  • 守护进程的名称通常以 "d" 结尾,以便于区分。例如,sshd 是 Secure Shell 守护进程,httpd 是 HTTP 守护进程。

  • 这些进程在后台运行,提供各种服务,例如处理网络请求(如 web 服务器)、处理系统日志、处理电子邮件和其他各种任务。

  • 守护进程通常不直接与用户交互,但它们工作起来非常重要,为其他程序和用户提供关键服务。

  • 守护进程在创建时通常会进行“孤儿化”操作,使其成为 init 进程(进程ID为1)的子进程。这样,它们就可以在后台运行,不受任何特定用户或会话的影响。此外,守护进程通常会更改其工作目录到根目录 (“/”),关闭所有已打开的文件描述符(包括输入、输出和错误输出),并重新打开标准输入、标准输出和标准错误到/dev/null,这样就可以防止它们不小心读取或写入任何用户文件或终端。

总的来说,守护进程是一种在后台运行,为系统或其他程序提供服务的进程。

如何创建一个守护进程

下面我来解释一下每个步骤的含义:

  1. 执行一个 fork(),之后父进程退出,子进程继续执行。

    • 这是创建守护进程的第一步,目的是让守护进程在后台运行。在fork()之后,父进程退出,子进程成为孤儿进程,并被 init 进程(进程ID为1)接管。
  2. 子进程调用 setsid() 开启一个新会话。

    • setsid()函数会创建一个新的会话,并且让子进程成为这个新会话的首进程。这样子进程就与其原来的会话、进程组和控制终端脱离,成为一个新的会话的首进程,可以独立运行。
  3. 清除进程的 umask 以确保当守护进程创建文件和目录时拥有所需的权限。

    • umask是一个权限掩码,它决定了新建文件或目录的默认权限。清除umask是为了让守护进程能够有更大的权限控制它创建的文件或目录。
  4. 修改进程的当前工作目录,通常会改为根目录(/)。

    • 这是为了防止守护进程阻止文件系统被卸载。如果守护进程的工作目录在一个挂载的文件系统中,那么这个文件系统就不能被卸载。
  5. 关闭守护进程从其父进程继承而来的所有打开着的文件描述符。

    • 这是为了避免守护进程继续使用这些文件描述符,可能会导致不可预料的问题。
  6. 在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null 并使用dup2() 使所有这些描述符指向这个设备。

    • 这是为了让守护进程的标准输入、输出和错误输出被重定向到/dev/null,避免它们产生任何输出,因为守护进程通常不需要和用户交互。

核心业务逻辑 在完成了以上所有的步骤后,守护进程开始执行其核心的业务逻辑,为系统或其他程序提供服务。

示例:创建一个守护进程

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
void daemonize()
{
    // 执行一个 fork(),之后父进程退出,子进程继续执行。
    pid_t pid = fork();
    if (pid > 0)
        exit(0);
    else if (pid < 0)
    {
        perror("fork");
        exit(0);
    }
    // 子进程调用 setsid() 开启一个新会话。
    if (setsid() == -1)
    {
        perror("setsid");
        exit(0);
    }

    // 清除进程的 umask 以确保当守护进程创建文件和目录时拥有所需的权限。
    umask(0);

    // 修改进程的当前工作目录,通常会改为根目录(/)。
    chdir("/home/nowcoder/Linux/lession28");

    // 关闭守护进程从其父进程继承而来的所有打开着的文件描述符。
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    // 在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null 并使用dup2() 使所有这些描述符指向这个设备。
}

// 代码主体逻辑部分
int main()
{
    daemonize();
    FILE *file = fopen("/home/nowcoder/Linux/lession28/time.txt", "a+");
    if (file == NULL)
    {
        perror("fopen");
        exit(0);
    }


    while (1)
    {
        time_t tt = time(NULL);
        struct tm t = *localtime(&tt);

        fprintf(file, "现在是北京时间: %d-%d-%d %d:%d:%d\n", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
        fflush(file); // 保证数据立即写入文件
        sleep(2);
    }

    return 0;
}
  • 编译和运行这个程序之后,你可以使用ps -ef | grep a.out(假设你的程序名是a.out)来查看守护进程。你会看到它没有控制终端,而且会话ID是1,这都是守护进程的特征。
  • 你可以用 kill - 9 守护进程的进程号 去杀死一个守护进程

注意,这是一个基础的守护进程,一个真实的守护进程可能需要处理更多的细节,对于实际环境中的守护进程,需要处理的问题可能包括但不限于:

  • 日志记录:当守护进程运行时,可能需要记录或报告它的行为。通常,它将这些信息写入日志文件或系统日志。

  • 配置文件:守护进程通常有一些可配置的设置,这些设置通常存储在配置文件中。当守护进程启动时,它会读取这个配置文件。

  • 信号处理:守护进程需要响应系统发送给它的信号。例如,当系统关闭时,它可能会收到一个SIGTERM信号,通知它应当进行清理并优雅地终止。

  • 错误处理:守护进程需要处理可能在运行时发生的各种错误,例如文件读取失败、网络连接中断等。

  • 权限管理:守护进程可能需要以特定的用户身份运行,以限制其对系统资源的访问。

  • 资源管理:如果守护进程创建了子进程,那么它可能需要跟踪这些进程,确保它们被正确地关闭和清理。

以上就是在设计守护进程时,可能需要考虑的一些问题。这些问题的处理方式会因应用的具体需求而异。

总结

  • 守护进程(Daemon)是一类在 Unix 和 Unix-like 操作系统中运行的后台进程。这些进程通常在系统启动时开始,然后在系统关闭时结束。守护进程通常不与用户直接交互,它们为其他程序和用户提供服务,例如网络服务、系统日志服务、打印服务等。

  • 守护进程的名称通常以 “d” 结尾,这表示它是一个守护进程。例如,httpd 是一个处理 HTTP 请求的守护进程,sshd 是一个提供安全 shell 服务的守护进程。

总的来说,守护进程就是在后台运行,为系统或用户提供各种服务的进程。与普通的进程一样,只要知道进程号,就可以用kill信号去杀死它。

最后的最后,如果你觉得我的这篇文章写的不错的话,请给我一个赞与收藏,关注我,我会继续给大家带来更多更优质的干货内容

05-16 20:36