Linux 守护进程的原理与实现
目录
1 守护进程
守护进程:也称为精灵进程,守护进程是一个在后台运行并且不受任何终端控制的进程。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。
2 创建守护进程流程
(1)创建子进程,退出父进程:为了脱离控制终端需要退出父进程,之后的工作都由子进程完成。在Linux中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程时,就会自动由1号进程(init)收养它,
这样,原先的子进程就会变成init进程的子进程。
(2)在子进程中创建新的会话:使用setsid函数创建一个新的会话,并担任该会话组的组长,主要作用如下:
- 让进程摆脱原会话的控制;
- 让进程摆脱原进程组的控制;
- 让进程摆脱原控制终端的控制;
(3)改变当前目录为根目:使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前目录所在的文件系统(如“/mnt/usb”)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。因此,通常的做法是让"/"作为守护进程的当前工作目录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数式chdir。
(4)重设文件权限掩码:由于fork函数创建的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0(即,不屏蔽任何权限),可以增强该守护进程的灵活性。设置文件权限掩码的函数是umask。通常的使用方法为umask(0)。文件权限掩码:是指屏蔽掉文件权限中的对应位。
(5)关闭文件描述符:用fork创建的子进程也会从父进程那里继承一些已经打开了的文件。在使用setsid调用之后,守护进程已经与所属的控制终端失去了联系,因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1、2(即,标准输入、标准输出、标准错误输出)的三个文件已经失去了存在的价值,也应该关闭。
(6)守护进程退出处理(可选):当用户需要外部停止守护进程运行时,往往会使用 kill命令停止该守护进程。所以,守护进程中需要编码来实现kill发出的signal信号处理,达到进程的正常退出。
3 代码示例
本演示代码每隔10s在/tmp/dameon.log中写入一个“open”。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include <signal.h>
#define MAXFILE 65535
volatile sig_atomic_t _running = 1;
void sigterm_handler(int arg)
{
_running = 0;
}
int main()
{
pid_t pc, pid;
int i, fd, len;
char *buf = "this is a Dameon\n";
len = strlen(buf);
//第一步
pc = fork();
if(pc < 0)
{
printf("error fork\n");
exit(1);
}
else if(pc > 0)
{
exit(0);
}
//第二步
setsid();
pid = fork();//与终端完全脱离[1]
if (pid < 0)
{
perror("fork error");
}
if (pid > 0)
{
exit(0);
}
//第三步
chdir("/");
//第四步
umask(0);
//第五步
for(i = 0;i < MAXFILE; i++)
{
close(i);
}
signal(SIGTERM, sigterm_handler);
while( _running )
{
if((fd = open("/tmp/dameon.log", O_CREAT | O_WRONLY | O_APPEND, 0600)) < 0)
{
perror("open");
exit(1);
}
write(fd, buf, len);
close(fd);
usleep(10 * 1000);//10毫秒
}
}