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
为文件权限掩码,通常用八进制数表示)。例如,文件的权限为 0666
, umask
值为 0002
,那么将 umask
取反,再与文件权限相与,则文件权限值变为 0664
。由于创建的子进程继承父进程的文件权限掩码,这给子进程(守护进程)操作文件带来一定影响。因此,通常把文件权限掩码设置为 0
,这样可以增强守护进程的灵活性。此时,文件权限掩码取反全为 1
,与任何文件权限相与,都可保持文件最原始的状态值。
使用函数 umask()
,改变文件权限掩码,参数即为要修改的掩码值。
#include <sys/types.h> #include <sys/stat.h> mode_t umask(mode_t mask);点击复制复制失败已复制
Step5. 关闭文件描述符
新创建的子进程会从父进程继承一些已经打开的文件描述符。这些描述符可能永远不会被守护进程访问,但它们却占有一定的资源。特别需要注意的是,守护进程脱离了终端的控制,所以与终端相关的标准输入、输出、错误输出的文件描述符 0
、 1
、 2
,已经没有了任何价值,应对关闭。具体如下所示:
int num; num = getdtablesize(); // 获取当前进程文件描述符表大小 for (i = 0; i < num; i++){ close(i); }点击复制复制失败已复制
其中, getdtablesize()
函数的功能为获取文件描述符表的大小,也可以理解为获取进程打开的文件描述符的最大数量。
实现
通过以上 5
步,可以实现创建守护进程,其流程如下:
守护进程的代码具体如下所示,守护进程每隔 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
选项为 "?"
,表示其为后台进程。