Linux 守护进程

简介: Linux 守护进程

Linux 守护进程又称为 Daemon 进程,为 Linux 的后台服务进程(独立于控制终端)。该进程通常周期性地执行某种任务或等待处理某些发生的事件。其生命周期较长,通常在系统启动时开始执行,在系统关闭时终止。 Linux 中很多系统服务都是通过守护进程实现的。


Linux 中,每一个从终端开始运行的进程都会依附于一个终端(系统与用户进行交互的界面),这个终端为进程的控制终端。当控制终端关闭时,这些进程就会自动结束,但守护进程不受终端关闭的影响。


如何将一个进程变成一个守护进程,只需要遵循一些特定的流程,下面通过 5 个步骤来讲解。


Step1. 创建子进程(子进程不退出,父进程退出)

很明显,由于父进程先于子进程退出,造成子进程成为孤儿进程。此时子进程的父进程变成 init/systemd 进程。


Step2. 在子进程中创建新会话

这个步骤在进程组与会话组中已经有所介绍,使用的函数是 setsid() 。该函数将会创建一个新会话,并使进程担任该会话组的组长。同时,在会话组中创建新的进程组,该进程依然也是进程组的组长。该进程成为新会话组和进程组中唯一的进程。最后使该进程脱离终端的控制,运行在后台。


之所以需要这样处理,是因为子进程在被创建时,复制了父进程的会话、进程组和终端控制等。虽然父进程退出,但原先的会话、进程组和控制终端等并没有改变。因此,子进程并没有实现真正意义上的独立。


Step3. 改变当前的工作目录

使用 fork() 函数创建的子进程继承了父进程的当前工作目录。系统通常的做法是让根目录成为守护进程的当前工作目录。改变工作目录的函数是 chdir()

#include <unistd.h>
int chdir(const char *path);点击复制复制失败已复制


Step4. 重设文件权限掩码

文件权限掩码的作用是屏蔽文件权限中的对应位。在文件的打开和关闭中有涉及该问题。文件被创建后,其用户操作权限 mode ,将会被执行 mode&~umask ( umask 为文件权限掩码,通常用八进制数表示)。例如,文件的权限为 0666umask 值为 0002 ,那么将 umask 取反,再与文件权限相与,则文件权限值变为 0664 。由于创建的子进程继承父进程的文件权限掩码,这给子进程(守护进程)操作文件带来一定影响。因此,通常把文件权限掩码设置为 0 ,这样可以增强守护进程的灵活性。此时,文件权限掩码取反全为 1 ,与任何文件权限相与,都可保持文件最原始的状态值。


使用函数 umask() ,改变文件权限掩码,参数即为要修改的掩码值。

#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);点击复制复制失败已复制


Step5. 关闭文件描述符

新创建的子进程会从父进程继承一些已经打开的文件描述符。这些描述符可能永远不会被守护进程访问,但它们却占有一定的资源。特别需要注意的是,守护进程脱离了终端的控制,所以与终端相关的标准输入、输出、错误输出的文件描述符 012 ,已经没有了任何价值,应对关闭。具体如下所示:

int num;
num = getdtablesize();  // 获取当前进程文件描述符表大小
for (i = 0; i < num; i++){
  close(i);
}点击复制复制失败已复制


其中, getdtablesize() 函数的功能为获取文件描述符表的大小,也可以理解为获取进程打开的文件描述符的最大数量。


实现

通过以上 5 步,可以实现创建守护进程,其流程如下:

微信截图_20221209152630.png


守护进程的代码具体如下所示,守护进程每隔 3 秒向日志文件 "/tmp/daemon.log" 中写入字符串。

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, const char *argv[]) {
  pid_t pid;
  int i, fd;
  char *buf = "This is a Daemon\n";
  pid = fork(); // 第一步
  if (pid < 0) {
    perror("fork error");
    return -1;
  } else if (pid > 0) {
    exit(0); // 父进程退出
  } else {
    setsid();      // 第二步
    chdir("/tmp"); // 第三步
    umask(0);      // 第四步
    for (i = 0; i < getdtablesize(); i++) {
      close(i); // 第五步
    }
    if ((fd = open("daemon.log", O_CREAT | O_WRONLY | O_TRUNC, 0600)) < 0) {
      perror("open error");
      return -1;
    }
    while (1) {
      write(fd, buf, strlen(buf));
      sleep(3);
    }
    close(fd);
  }
  return 0;
}点击复制复制失败已复制


编译并运行,可以看到终端直接退出,接下来查看 /tmp/daemon.log 文件,可以看到日志内容:

This is a Daemon
This is a Daemon
This is a Daemon
This is a Daemon
This is a Daemon
……点击复制复制失败已复制


终端查看进程信息如下:

$ ps axj | grep ./a.out
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
   1202  496227  496227  496227 ?             -1 Ss    1000   0:00 ./a.out
$ ps axj | grep systemd
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
      1    1202    1202    1202 ?             -1 Ss    1000   0:00 /lib/systemd/systemd --user点击复制复制失败已复制


可以看出进程的 ID 与组 ID 、会话 ID 保持一致,说明当前守护进程为组长。其父进程为 systemd 进程, TTY 选项为 "?" ,表示其为后台进程

目录
相关文章
|
2月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
139 2
|
2月前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
47 2
|
5天前
|
Linux Shell
6-9|linux查询现在运行的进程
6-9|linux查询现在运行的进程
|
2月前
|
消息中间件 Linux 开发者
Linux进程间通信秘籍:管道、消息队列、信号量,一文让你彻底解锁!
【8月更文挑战第25天】本文概述了Linux系统中常用的五种进程间通信(IPC)模式:管道、消息队列、信号量、共享内存与套接字。通过示例代码展示了每种模式的应用场景。了解这些IPC机制及其特点有助于开发者根据具体需求选择合适的通信方式,促进多进程间的高效协作。
78 3
|
2月前
|
消息中间件 Linux
Linux进程间通信
Linux进程间通信
35 1
|
2月前
|
C语言
Linux0.11 系统调用进程创建与执行(九)(下)
Linux0.11 系统调用进程创建与执行(九)
25 1
|
2月前
|
存储 Linux 索引
Linux0.11 系统调用进程创建与执行(九)(上)
Linux0.11 系统调用进程创建与执行(九)
48 1
|
2月前
|
Web App开发 Linux
在Linux中,如何杀死一个进程?
在Linux中,如何杀死一个进程?
|
2月前
|
域名解析 监控 安全
在Linux中,什么是守护进程,它们是如何工作的?
在Linux中,什么是守护进程,它们是如何工作的?
|
2月前
|
消息中间件 Linux
在Linux中,进程间通信方式有哪些?
在Linux中,进程间通信方式有哪些?