创建守护进程为何fork两次

我们先来了解一下什么是守护进程?

守护进程

守护进程也称精灵进程(Daemon)
它是运⾏在后台的⼀种特殊进程。它独⽴于控制终端并且周期性地执⾏某种任务或等待处理某些发⽣的事件。守护进程是⼀种很有⽤的进程。Linux的⼤多数服务器就是⽤守护进程实现的。⽐如, Internet服务器inetd, Web服务器httpd等。同时,守护进程完成许多系统任务。⽐如,作业规划进程crond等。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和⽤户交互。其它进程都是在⽤户登录或运⾏程序时创建,在运⾏结束或⽤户注销时终⽌,但系统服务进程不受⽤户登录注销的影响,它们⼀直在运⾏着。

守护进程的特点

  1. 守护进程7*24小时不间断运行,即时刻运行着,不受用户登录注销的影响。
  2. 守护进程自成进程组。
  3. 守护进程的父进程为序号为1,是孤儿进程。
  4. 守护进程自成会话。关于会话,新启一个终端即新建一个会话,且bash作为该会话的首进程;关闭一个终端即关闭一个会话。
  5. 守护进程没有控制终端,不能直接和用户交互
  6. 守护进程命名上以d结尾

查看守护进程的命令

ps axj

这里写图片描述

凡是TPGID⼀栏写着-1的都是没有控制终端的进程,也就是守护进程。
在COMMAND⼀列⽤[]括起来的 名字表⽰内核线程,这些线程在内核⾥创建,没有⽤户空间代码,因此没有程序⽂件名和命令⾏。
init进程我们已经很熟悉了
udevd负责维护/dev⽬录下的 设备⽂件
acpid负责电源管理
syslogd负责维护/var/log下的⽇志⽂件
可以看出,守护进程通 常采⽤以d结尾的名字,表⽰Daemon。

下面来看一下详细的参数信息

ps axj | grep -E "d$"

这里写图片描述

守护进程存在的原因

daemon函数存在的原因是因为控制终端由于某些原因(如断开终端链接)会发送一些信号的原因。而接收处理这些信号的缺省动作会让进程退出。这些信号会由于终端上敲一些特殊按键而产生。

守护进程和后台进程的区别

  • 守护进程是后台进程,后台进程不一定是守护进程
  • 守护进程运行是与终端无关的,是不能往终端上打消息的
  • 守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行

setsid函数

创建守护进程最关键的⼀步是调⽤setsid函数创建⼀个新的Session,并成为Session Leader。

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

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

  1. 创建⼀个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。
  2. 创建⼀个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。
  3. 如果当前进程原本有⼀个控制终端,则它失去这个控制终端,成为⼀个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是⼀个普通的打开⽂件⽽不是控制终端了

注意,调⽤这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。要保证当前进程不是进程组的Leader也很容易,只要先fork再调⽤setsid就⾏了。 fork创建的⼦进程和⽗进程在同⼀个进程组中,进程组的Leader必然是该组的第⼀个进程,所以⼦进程不可能是该组的第⼀个进程,在⼦进程中调⽤setsid就不会有问题了。

守护进程的创建

  • 代码如下:
  1 #include <stdio.h>
  2 #include <signal.h>
  3 #include <unistd.h>
  4 #include <stdlib.h>
  5 #include <fcntl.h>
  6 #include <sys/stat.h>
  7 
  8 void mydaemon()
  9 {
 10     int i = 0;
 11     int fd0;
 12     struct sigaction sa;
 13     pid_t id;
 14     umask(0);
 15     if((id = fork()) < 0)
 16     {
 17         perror("fork");
 18         return;
 19     }
 20     else if(id != 0) // father
 21     {
 22         exit(0);
 23     }
 24     setsid();
 25 
 26     sa.sa_handler = SIG_IGN;
 27     sigemptyset(&sa.sa_mask);
 28     sa.sa_flags = 0;
 29     if(sigaction(SIGCHLD, &sa, NULL) < 0)
 30     {
 31         return;
 32     }
 33     if((id = fork()) < 0)
 34     {
 35         perror("fork");
 36         return;
 37     }
 38     else if(id != 0)
 39     {
 40         exit(0);
 41     }
 42     if(chdir("/") < 0)
 43     {
 44         perror("chdir");
 45         return;
 46     }
 47     close(0);
 48     fd0 = open("/dev/null", O_RDWR);
 49     dup2(fd0, 1);
 50     dup2(fd0, 2);
 51 }
 52 
 53 int main()
 54 {
 55     mydaemon();
 56     while(1);
 57     return 0;
 58 }
  • 步骤
    1. 调⽤umask将⽂件模式创建屏蔽字设置为0.
    2. 调⽤fork,⽗进程退出( exit) 。
      (1)如果该守护进程是作为⼀条简单的shell命令 启动的,那么⽗进程终⽌使得shell认为该命令已经执⾏完毕。
      (2)保证⼦进程不是⼀个进程组的组长进程。
    3. 调⽤setsid创建⼀个新会话。
      (1)调⽤进程成为新会话的⾸进程。
      (2)调⽤ 进程成为⼀个进程组的组长进程 。
      (3)调⽤进程没有控制终端。(再次fork⼀次,保证 daemon进程,之后不会打开tty设备)
    4. 将当前⼯作⽬录更改为根⽬录。
    5. 关闭不在需要的⽂件描述符。
    6. 其他:忽略SIGCHLD信号。

创建过程中fork几次?

在上述代码中,我fork了两次。也有人在创建过程中fork一次的,但是结果相同。下面来看看有何区别。

  • 要知道在创建守护进程的时候fork一次和fork两次两者有什么区别,就要先知道第一次fork和第二次fork都起到了什么作用:
  • (1)调用一次fork的作用:
    • 第一次fork的作用是让shell认为这条命令已经终止,不用挂在终端输入上,还有就是为了后面的setsid服务,因为调用setsid函数的进程不能是进程组组长,如果不fork出子进程,则此时的父进程是进程组组长,就无法调用setsid。当子进程调用完setsid函数之后,子进程是会话组长也是进程组组长,并且脱离了控制终端,此时,不管控制终端如何操作,新的进程都不会收到一些信号使得进程退出。
  • (2)第二次fork的作用:
    • 虽然当前关闭了和终端的联系,但是后期可能会误操作打开了终端。只有会话首进程能打开终端设备,也就是再fork一次,再把父进程退出,再次fork的子进程作为守护进程继续运行,保证了该精灵进程不是对话期的首进程。

由此可见,第二次不是必须的,而是可选的,市面上有些开源项目也是fork一次。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值