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

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


举个例子:在现实生活中, 许多大型的软件或服务器必须保证7*24小时(一周7天,一天24小时)无障碍的运行,例如淘宝网、百度搜索引擎、支付宝等等,那么像这样一种要一直运行的程序怎么实现呢?究其本质其实就是我们的守护进程。


二、守护进程的特点:
1).自成进程组,自成会话,与控制终端脱关联;
2).守护进程的父进程是1号进程;
3).守护进程的命令一般以字符d结尾;
4).守护进程的生命周期是7*24小时不掉线;
5).一般的网络服务器都以守护进程的形式在后台运行,比如常见的http,ftp等等服务器都是以守护进程的形式在后台运行;



三、创建守护进程

1、查看系统中的进程

ps axj
 
  • 1

参数a表示不仅列当前用户的进程,也列出所有其他用户的进程, 
参数x表示不仅列有控制终端的进程,也列出所无控制终端的进程, 
参数j表示列出与作业控制相关的信息。



2、setsid函数 
(1)创建守护进程最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。

#include<unistd.h>
pid_t setsid(void);
 
  • 1
  • 2

返回值:该函数调用成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。


(2)需要注意的是,,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。 
解决办法:先fork再调用setsid,fork创建的子进程和父进程在同一个进 程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子 进程中调用setsid就不会有问题了。


(3)成功调用该函数的结果是: 
1. 创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。 
2. 创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。 
3. 如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端了。


3、创建守护进程的步骤 

方式一:

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

umask(0);//umask必须清0,否则创建文件受系统默认权限的影响
 
  • 1

文件权限掩码是屏蔽掉文件权限中的对应位。由于使用fork()函数新创建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带了很多的麻烦(比如父进程中的文件没有执行文件的权限,然而在子进程中希望执行相应的文件这个时候就会出问题)。因此在子进程中要把文件的权限掩码设置成为0,即在此时有最大的权限,这样可以大大增强该守护进程的灵活性。


(2)调用fork,父进程退出(exit)。 
原因: 
1)如果该守护进程是作为一条简单的shell命令启动的,那么⽗父进程终止使得shell认为该命令已经执行完毕。
2)保证子进程不是一个进程组的组长进程。


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

调用setsid的原因: 
由于创建守护进程的第一步是调用fork()函数来创建子进程,再将父进程退出。由于在调用了fork()函数的时候,子进程拷贝了父进程的会话期、进程组、控制终端等资源、虽然父进程退出了,但是会话期、进程组、控制终端等并没有改变,因此,需要用setsid()韩式来时该子进程完全独立出来,从而摆脱其他进程的控制。


(4)将当前工作目录更改为根目录。 
防止当前目录有一个目录被删除,导致守护进程无效。 
使用fork()创建的子进程是继承了父进程的当前工作目录,由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后使用会造成很多的麻烦。因此通常的做法是让“/”作为守护进程的当前目录,当然也可以指定其他的别的目录来作为守护进程的工作目录。


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

如:关闭标准输入流、标准输出流、标准错误流。

close(0)close(1)close(2)
 
  • 1
  • 2
  • 3

(6)其他:忽略SIGCHLD信号

signal(SIGCHLD,SIG_IGN);

#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
int creat_daemon()
{  
    //(1)调用umask将文件模式创建屏蔽字设置为0.
    umask(0);    
   //(2)调用fork,父进程退出(exit)。  
    pid_t id = fork();
    if(id > 0)
    {
        exit(1);
    }
    else if(id == 0)
    {
        setsid();         //(3)创建一个新的会话
        if(chdir("/")<0)  //(4)将新建会话的工作目录改成根目录
        {
           perror("chdir");
           return;
        }
       //(5)关闭从父进程继承来的文件
        close(0);
        //close(1); 假如这里没有关闭会出现什么情况,看下面结果
        close(2);
       //(6)忽略SIGCHLD信号。
        signal(SIGCHLD, SIG_IGN);
 
    }
}

int main()
{
    creat_daemon();
    while(1);
    return 0;
}


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

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


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


方式二:创建守护进程也可以调用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。所以到这里子进程便成为了一个新会话组的组长。


第二次fork:第2次fork不是必须的。也看到很多开源服务没有fork第二次。fork第二次主要目的是。防止进程再次打开一个控制终端。因为打开一个控制终端的前台条件是该进程必须是会话组长。再fork一次,子进程ID != sid(sid是进程父进程的sid)。所以也无法打开新的控制终端。

daemon目的就是防止终端产生的一些信号让进程退出。上面函数并没有直接调用signal函数去处理它。而是间接通过fork和setsid函数使用更少代码优雅处理。而被有些人误以为是防止父进程没有正常退出而导致的僵死进程的原因需要这样处理。

  

评论 1 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页

打赏作者

TerryZjl

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值