守护进程也称精灵进程,是运行在后他的一种特殊进程。它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。守护进程是一种很有的进程,linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond等。
linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其他进程都是在用户登录或程序运行的时候创建,在运行结束或用户注销时终止,但系统服务进程不受用户登录注销影响,他们一直在运行着。这种进程有一个名称叫守护进程。
1.创建守护进程
创建守护进程最关键的一步是调用setsid函数创建一个新的Session(会话),并成为Session Leader。
# include< unistd.h>
pid_t setsid(void);
该函数调用成功是返回新创建的session的id(其实就是当前进程的id),出错返回-1.注意,调用这个函数之前,当前进程不允许是进程组的leader,否则该函数返回-1.要保证当前进程不是leader也很容易,只要先fork再调用setsid就行了。fork创建的子进程和父进程在同一个进程组中,进程组的leader当然是改组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsit就不会有问题了。
2.步骤说明
(1)调用umask函数将文件模式创建屏蔽字设置为0.
(2)调用fork(),退出父进程(exit)。①如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕。②保证子进程不是该进程组的组长进程。
(3)调用setsid创建一个新会话。setsid会导致:①调用进程会成为新会话的首进程。②调用进程会成为一个进程组的组长进程。③调用进程没有控制终端。
(4)将当前目录更改为根目录。
(6)其他:忽略SIGCHID信号。
3.代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
void my_daemon()
{ pid_t id;
umask(0);//将文件模式创建屏蔽字设置为0.
if((id = fork()) < 0)
{
perror("fork");
}
else if(id > 0)
{
exit(0);//终止父进程
}
printf("pid:%d\n",setsid());//设置新会话
signal(17, SIG_IGN);//屏蔽SIGCHLD信号
chdir("/");//更改当前目录到根目录
//屏蔽不必要的文件描述符
close(0);
close(1);
close(2);
}
int main()
{
my_daemon();
while(1)
{
sleep(1);
}
return 0;
}
结果:
我们用 $ ps axj 查看当前进程。凡是TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程。如上图中,我们已经创建出了守护进程。
4.调用unix标准:
# include < stdio.h>
# include < unistd.h>
int main()
{
daemon(0,0);
while(1);
}
以上程序可以直接创建守护进程。
daemon原型:
#include < unistd.h>
int daemon(int nochdir, int noclose);
5.为什么创建守护进程时有人fork两次?
两次fork的代码如下:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
void creat_daemon(void)
{
int i;
int fd0;
pid_t pid;
struct sigaction sa;
umask(0); //设置⽂文件掩码为0
if( (pid = fork()) < 0 ){
}else if (pid != 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)//再次fork,终⽌止⽗父进程,保持⼦子进程不是话⾸首进程,从⽽而保证后续不会在和其他终端关联
{
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); // 关闭标准输⼊入,重定向所有标准(输⼊入输出错误)到/dev/null
dup2(fd0, 1);
dup2(fd0, 2);
}
int main()
{
creat_daemon();
while(1)
{
sleep(1);
}
}
因为在第一次fork后,当前子进程为该会话的首进程。因为打开一个控制终端的前提条件是该进程必须是会话组长。防止进程再次打开一个控制终端,再fork一次,使该进程不是该会话的首进程。(守护进程是没有控制终端的)。