守护进程:也称精灵进程(Daemon),是运行在后台的一种特殊进程。也是一种很有用的进程。因为Linux的大多数服务器就是用守护进程实现的(比如:Internet服务器inetd,Web服务器httpd),而且它可以完成许多系统任务(比如:作业规划进程crond)。
其特点是独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
创建守护进程的步骤:
(1)调用umask将文件模式创建屏蔽字设置为0.
(2)调用fork,父进程退出(exit)。原因:1)如果该守护进程是作为一条简单的shell命令启动的,那么父进程终⽌止使得shell认为该命令已经执行完毕。2)保证子进程不是一个进程组的组长进程。
(3)调用setsid创建一个新会话。
(4)将当前工作目录更改为根目录。
(5)关闭不在需要的文件描述符。
(6)其他:忽略SIGCHLD信号。
创建守护进程最关键的一步是调用setsid函数创建一个新Session,并成为Session Leader。
该函数调用成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。注意,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进程组的Leader也很容易,只要先fork再调⽤用setsid就行了。fork创建的子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsid就不会有问题了。
成功调用该函数的结果是:
1. 创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
2. 创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
3. 如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端了。
下面是一个“粗糙”的创建守护进程代码(只创建一个子进程):
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/stat.h>
void Mydemon()
{
umask(0);
if(fork() > 0)
exit(0);
setsid();
chdir("/");
close(0);
close(1);
close(2);
signal(SIGCHLD,SIG_IGN);
}
int main()
{
Mydemon();
while(1)
{
sleep(1);
}
return 0;
}
想要看到结果,可以在终端下输入 ps axj|grep Demcon
1 4720 4720 4720 ? -1 Ss 500 0:00 ./Demcon
2460 4722 4721 2372 pts/0 4721 S+ 500 0:00 grep Demcon
在这里我们要注意fork 两次比一次要好。
因为调用setsid函数会导致:1)调用进程成为新会话的首进程。 2)调用进程成为一个进程组的组长进程 。3)调用进程没有控制终端。
所以再次fork一次,保证daemon进程,之后不会打开tty设备
以下是简单的实现代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>
void Mydemcon()
{
int fd0;
pid_t pid;
struct sigaction sa;
umask(0);
if((pid = fork()) != 0)
exit(0);
setsid();
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if( sigaction(SIGCHLD, &sa, NULL ) < 0 )
{
return ;
}
if( (pid = fork())<0)
{
printf("fork error!\n");
return ;
}
else if( pid != 0)
{
exit(0);
}
if( chdir("/") < 0 )
{
printf("child dir error\n");
return ;
}
close(0);
fd0 = open("/dev/null", O_RDWR);
dup2(fd0, 1);
dup2(fd0, 2);
}
int main()
{
Mydemcon();
while(1)
{
sleep(1);
}
return 0;
}
同样在终端输入 ps axj|grep Demcon 看到结果
1 4720 4720 4720 ? -1 Ss 500 0:00 ./Demcon
1 4815 4814 4814 ? -1 S 500 0:00 ./Demcon
2460 4817 4816 2372 pts/0 4816 S+ 500 0:00 grep Demcon
上述程序中调用一次fork的作用:
第一次fork的作用:是让shell认为这条命令已经终止,不用挂在终端输入上,还有就是为了后面的setsid服务,因为调用setsid函数的进程不能是进程组组长,如果不fork出子进程,则此时的父进程是进程组组长,就无法调用setsid。当子进程调用完setsid函数之后,子进程是会话组长也是进程组组长,并且脱离了控制终端,此时,不管控制终端如何操作,新的进程都不会收到一些信号使得进程退出。
第二次fork的作用:虽然当前关闭了和终端的联系,但是后期可能会误操作打开了终端。只有会话首进程能打开终端设备,也就是再fork一次,再把父进程退出,再次fork的子进程作为守护进程继续运行,保证了该精灵进程不是对话期的首进程。
当然第二次fork不是必须的,因为市面上有些开源项目也是fork一次的。