守护进程(Daemon Process),是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件,一般采用以 d 结尾的名字。
进程组
多个进程的集合就是进程组,这个组中必须有一个组长,组长就是进程组中的第一个进程,组长以外的都是普通的成员。
每个进程组都有一个唯一的组 ID,进程组的 ID 和组长的 PID 相同。
进程组中的成员是可以转移的,如果当前进程组中的成员被转移到了其他的组(或者所有进程都退出),那么这个进程组就不存在了。如果进程组中组长死了,但是当前进程组中有其他进程,这个进程组还是继续存在的。下面介绍几个常用的进程组函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| pid_t getpgrp(void);
pid_t getpgid(pid_t pid);
int setpgid(pid_t pid, pid_t pgid);
|
会话
会话(session)是由一个或多个进程组组成的,一个会话可以对应一个控制终端,也可以没有。一个普通的进程可以调用 setsid()
函数使自己成为新 session 的领头进程(会长),并且这个 session 领头进程还会被放入到一个新的进程组中。setsid()
函数的原型如下:
1 2 3 4 5 6 7 8
| #include <unistd.h>
pid_t getsid(pid_t pid);
pid_t setsid(void);
|
使用这个函数的注意事项:
(1)调用这个函数的进程不能是组长进程,如果是该函数调用失败。如何保证这个函数能调用成功?
先 fork ()
创建子进程,终止父进程,让子进程调用这个函数
(2)如果调用这个函数的进程不是进程组长,会话创建成功。
这个进程会变成当前会话中的第一个进程,同时也会变成新的进程组的组长。该函数调用成功之后,当前进程就脱离了控制终端,因此不会阻塞终端。
创建守护进程
创建一个守护进程,标准步骤如下(部分操作可以根据实际需求进行取舍):
(1)创建子进程,让父进程退出
因为父进程有可能是组长进程,不符合条件,退出即可。
子进程没有任何职务,目的是让子进程最终变成一个会话,最终就会得到守护进程。
(2)通过子进程创建新的会话
调用函数 setsid()
,脱离控制终端,变成守护进程。
(3)改变当前进程的工作目录(可选项)
某些文件系统(U 盘、移动硬盘)可以被卸载。进程如果在这些目录中运行,运行期间这些设备被卸载了,运行的进程就不能正常工作。
修改当前进程的工作目录需要调用函数 chdir()
(4)重新设置文件的掩码(可选项)
掩码 umask,在创建新文件的时候需要和这个掩码进行运算,去掉文件的某些权限
设置掩码需要使用函数 umask()
(5)关闭/重定向文件描述符(不做也可以,但是建议做)
启动一个进程,文件描述符表中默认有三个被打开了,对应的都是当前的终端文件。
因为进程通过调用 setsid()
已经脱离了当前终端,因此关联的文件描述符就没用了,可以关闭。
1 2 3
| close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO);
|
重定向文件描述符(和关闭二选一):改变文件描述符关联的默认文件,让它们指向一个特殊的文件 /dev/null
,只要把数据扔到这个特殊的设备文件中,数据被销毁了。
1 2 3 4 5
| int fd = open("/dev/null", O_RDWR);
dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO);
|
(6)根据实际需求在守护进程中执行某些特定的操作
守护进程的应用
写一个守护进程,每隔 2s 获取一次系统时间,并将得到的时间写入到磁盘文件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <sys/time.h> #include <time.h>
void writeFile(int num) { time_t seconds = time(NULL); struct tm* loc = localtime(&seconds); char* curtime = asctime(loc); int fd = open("./time.log", O_WRONLY | O_CREAT | O_APPEND, 0664); write(fd, curtime, strlen(curtime)); close(fd); }
int main() { pid_t pid = fork(); if (pid > 0) { exit(0); }
setsid();
chdir("/home/xxx");
umask(022);
int fd = open("/dev/null", O_RDWR); dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO);
struct sigaction act; act.sa_flags = 0; act.sa_handler = writeFile; sigemptyset(&act.sa_mask); sigaction(SIGALRM, &act, NULL);
struct itimerval val; val.it_value.tv_sec = 2; val.it_value.tv_usec = 0; val.it_interval.tv_sec = 2; val.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &val, NULL);
while(1) { sleep(100); }
return 0; }
|
参考资料
https://subingwen.cn/linux/deamon