您的位置:首页 > 其它

创建守护进程为什仫要fork两次

2017-06-16 11:21 309 查看
要解决这个问题,首先需要了解什仫是守护进程?

守护进程的基本概念:

守护进程也叫精灵进程,它是大多数服务器的载体。守护进程是一种运行在后台的一种特殊的进程,它独立于控制终端并且周期性的执行某种任务或是等待处理某些发生的时间。如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程。

守护进程的特点:

1).自成进程组,自成会话,与控制终端脱关联;

2).守护进程的父进程是1号进程;

3).守护进程的命令一般以字符d结尾;

4).守护进程的生命周期是7*24小时不掉线;

5).一般的网络服务器都以守护进程的形式在后台运行。比如常见的http,ftp等等服务器都是以守护进程的形式在后台运行;

ps axj | grep -E 'd$'   //查看系统中所有以d结尾的进程
参数a表示不仅列当前用户的进程,也列出所有其他用户的进程;
参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程;
参数j表示列出与作业控制相关的信息。




如何创建一个守护进程:

方式一:用户自定义

1).调用umask将文件模式创建屏蔽字设置为0;

2).调用fork,父进程退出(exit);

原因:如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕;保证子进程不是一个进程组的组长进程;

3).调用setsid创建一个新会话。

原因:setsid会导致:调用进程成为新会话的首进程;调用进程成为一个进程组的组长进程;调用进程没有控制终端。(再次fork一次,保证daemon进程,之后不会打开tty设备);

4). 将当前工作目录更改为根目录;

原因:使用fork()创建的子进程是继承了父进程的当前工作目录,由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后使用会造成很多的麻烦。因此通常的做法是让“/”作为守护进程的当前目录,当然也可以指定其他的别的目录来作为守护进程的工作目录。

5). 关闭不在需要的文件描述符;

原因:用fork()函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,如果不进行关闭的话将会浪费系统的资源,造成进程所在的文件系统无法卸下以及引起预料的错误;

6). 忽略SIGCHLD信号;

原因:因为父进程已经终止,所以不需要子进程再给父进程发送SIGCHLD信号;

代码创建一个守护进程:

//fork一次
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/stat.h>

void mydaemon()
{
//1.将文件模式创建屏蔽字设置为0
umask(0);
//2.fork子进程,父进程退出
if(fork() > 0)//father
{
exit(0);
}
//3.创建一个新会话
setsid();
//4.更改当前的工作目录
chdir("/");
//5.关闭不需要的文件描述符
close(0);
//close(1);
close(2);
//6.忽略SIGCHLD信号
signal(SIGCHLD,SIG_IGN);
}

int main()
{
mydaemon();
//daemon(0,0);   //默认更改工作目录并且更改文件描述符为空,使其指向/dev/null
//daemon(1,0);     //不更改工作目录也不更改文件描述符
while(1)
{
sleep(1);
}
return 0;
}




由上图可知通过运行自己编写的mydaemon在Linux后台创建了一个守护进程,它的父进程是1号进程,它自己是自成进程组,自成会话的,因为该守护进程的PID,PGID,SID是一样的。

在Linux的根目录下有一个目录proc,/proc目录中包含许多以数字命名的子目录,这些数字表示系统当前正在运行进程的进程号,里面包含对应进程相关的多个信息文件。例如 cd /proc/3092,进入到刚才创建的守护进程的id里面,并查看它进程id所对应的文件描述符的情况。



可以发现当1号文件描述符不关闭的时候,此时的1号文件描述符指向的是自己的特殊的设备文件/dev/pts/1。所以在创建守护进程的时候就是有一个步骤要关闭不必要的文件描述符。

//fork两次
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

void mydaemon()
{
umask(0);

pid_t id=fork(); //第一次fork,终止父进程
if(id < 0){
perror("fork1");
return;
}
else if(id > 0){
exit(0);
}

setsid();

id=fork();
if(id < 0){
perror("fork2");
return;
}
else if(id > 0){ //father,第二次fork,保证daemon进程之后不会打开tty设备
exit(0);
}

chdir("/");

close(0);
close(1);
close(2);

signal(SIGCHLD,SIG_IGN);  //注册子进程忽略信号
}

int main()
{
mydaemon();
while(1)
{
sleep(1);
}
return 0;
}




fork两次可以发现它的PGID,SID是一致的,不同与PID。

方式二:创建守护进程也可以调用daemon函数:
int daemon(int nochdir, int noclose);


一般调用daemon有两种方式:

1).daemon(0,0); //默认更改工作目录且更改文件描述符0,1,2为空,使其指向/dev/null。这个/dev/null就类似黑洞,不管哪个文件描述符对其进行写操作,它都会直接将数据丢弃;

2).daemon(1,0); //不更改工作目录也不更改文件描述符;

守护进程要fork两次的原因:

fork一次:这里第一次fork的作用就是让shell认为这条命令已经终止,不用挂在终端输入上;另一个原因就是为了后面的setsid服务,因为调用setsid函数的进程不能是进程组组长,如果不fork子进程,那么此时的父进程是进程组组长,无法调用setsid。当子进程调用完setsid函数之后,子进程是会话组长也是进程组组长,并且脱离了控制终端。此时要杀死该守护进程只能通过
kill -9 +id
的形式向该守护进程发送9号信号。

fork两次:第二次fork是为了避免后期进程误操作而再次打开终端tty设备。只有会话首进程才可以打开终端设备,也就是说fork第二次,退出父进程,再次fork的子进程作为守护进程继续运行,保证了该守护进程不再是会话的首进程。



第二次fork不是必须的,是可选的。

在这里就分享结束了~~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  守护进程 fork两次