守护进程是脱离于终端并且在后台运行的进程。
守护进程通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事情。守护进程常常在系统引导装入时启动,在系统关闭时终止。Linux系统有很多守护进程,大多数服务都是通过守护进程实现的,同时,守护进程还能完成许多系统任务。
创建一个简单的守护进程的步骤如下:
(1) 创建子进程,父进程退出
这一步完成后,在shell终端里造成一程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。在Linux中,如果父进程先于子进程退出会造成子进程称为孤儿进程,而每当系统发现一个孤儿进程时,就会自动由1号进程(init)收养它,这样,原先的子进程就会变成init进程的子进程。
(2) 使用setsid()系统函数,在子进程中创建新会话
setsid()用于创建一个新的会话,并担任该会话组的组长。
其具体作用是:
1)让进程摆脱原会话的控制
2)让进程摆脱原进程组的控制
3)让进程摆脱原控制终端的控制
由于创建守护进程的第一步是调用fork函数,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,因此还不是真正意义上的独立。而setsid函数能够使进程完全独立出来,从而摆脱其他进程的控制。
(3) 改变当前目录为根目录
子进程继承了父进程的工作目录,在进程运行中,当前目录所在的文件系统是不能卸载的,这会对以后的使用造成麻烦。因此,通常的做法是让根目录作为守护进程的当前工作目录。
(4) 重新设置文件权限掩码
子进程继承了父进程的文件权限掩码(指屏蔽掉文件权限中的对应位),这可能导致子进程无法读或写文件,因此设置文件权限掩码为0,可增强该守护进程的灵活性。
(5)关闭文件描述符
子进程会从父进程中继承的一些已经打开的文件,而这些被打开的文件可能永远不会被守护进程读写,但会消耗系统资源,而且可能导致所在的文件系统无法结束。因此关闭掉子进程中的文件描述符也是有必要的。
具体代码实现如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#define MAXFILE 65535
int main()
{
// 创建子进程
pid_t pc;
pc = fork();
if(pc < 0)
{
printf("fork error \n");
exit(1);
}
else if(pc > 0)
{
// 父进程退出
exit(0);
}
// 子进程与父进程分离
setsid();
// 设置当前目录为根目录
chdir("/");
// 重新设置文件权限掩码
umask(0);
// 关闭文件描述符
for(int i = 0; i < MAXFILE; ++i)
close(i);
char *buf = "this is a demo\n";
int len = strlen(buf);
int fd;
// 每隔5s向/tmp/demo.log中写入一串字符
while(1)
{
if((fd = open("/tmp/demo.log",O_CREAT|O_WRONLY|O_APPEND,0600)) < 0)
{
perror("open");
exit(1);
}
write(fd,buf,len+1);
close(fd);
sleep(10);
}
return 0;
}
上述代码在Linux下使用gcc或g++进行编译:
g++ guard.cpp -o guard
查看字符串写入的结果:
由上图可以分析到,守护进程执行后,终端中并没有打印信息。查找进程发现其在后台继续执行。查找/tmp/demo.log,可以看到每隔一段时间,就会向文件中写入一串字符。
谢谢阅读
参考书籍 《后台开发 核心技术与应用实践》