Linux守护进程编程规则

写这篇blog的原因在于,工作中经常需要写一些守护进程(daemon)。

而我们创建守护进程的方法,往往是简单粗暴,不按照守护进程的编程来。

创建守护进程的一般方法是:

pid = fork();
if (pid < 0) {  // fork failed
    printf("fork error\n");
    exit(1);
} else if (pid > 0) { // parent process
    return;
} else { // child process
    // daemon begin to execute
}
上面创建守护进程的方法虽然也能实现大体上的功能,但不够规范。上述的创建方法,可能会导致不必要的交互作用。

因此,有必要搞清楚标准守护进程的创建方法,也就是守护进程的编程规则。


1.守护进程是什么?

守护进程(daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

2.守护进程的特征?

守护进程最重要的特性是后台运行。在这一点上DOS下的常驻内存程序TSR与之相似。

其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩码等。

这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。

最后,守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(通常是shell)执行。

总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。

3.守护进程的编程规则?

编写守护进程需要遵循一些基本规则,以便防止产生不必要的交互作用。

(1)  首先要做的是调用umask,重设文件权限掩码。

文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。
由于使用fork函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。
因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。
设置文件权限掩码的函数是umask。在这里,通常的使用方法为umask(0)。

(2) 调用fork,然后将父进程退出(exit)。

这是编写守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成一程序已经运行完毕的假象。
之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。
在Linux中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程是,就会自动由1号进程(init)收养它。
这样,原先的子进程就会变成init进程的子进程。

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

这个步骤是创建守护进程中最重要的一步,虽然它的实现非常简单,但它的意义却非常重大。

在这里使用的是系统函数setsid,在具体介绍setsid之前,首先要了解两个概念:进程组和会话期
  进程组:是一个或多个进程的集合。进程组有进程组ID来唯一标识。除了进程号(PID)之外,进程组ID也是一个进程的必备属性。每个进程组都有一个组长进程,其组长         进程的进程号等于进程组ID。且该进程组ID不会因组长进程的退出而受到影响。
  会话周期:会话期是一个或多个进程组的集合。通常,一个会话开始与用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。

接下来就可以具体介绍setsid的相关内容:
  setsid函数作用:
  setsid函数用于创建一个新的会话,并担任该会话组的组长。调用setsid有下面的3个作用:
  让进程摆脱原会话的控制
  让进程摆脱原进程组的控制
  让进程摆脱原控制终端的控制 

那么,在创建守护进程时为什么要调用setsid函数呢?
由于创建守护进程的第一步调用了fork函数来创建子进程,再将父进程退出。由于在调用了fork函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等。
虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,还还不是真正意义上的独立开来,而setsid函数能够使进程完全独立出来,从而摆脱其他进程的控制。

(4) 将当前工作目录更改为根目录。

从父进程继承过来的当前工作目录可能在一个装配文件系统中。因为守护进程可能会在系统再引导之前就一直存在,所以如果守护进程的当前工作目录

在一个装配文件系统中,那么该文件系统就不可拆卸。这与装配文件系统的原意不符。

(5) 关闭不再需要的文件描述符。

fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。 

(6) 某些守护进程打开/dev/null*,使其具有文件描述符0、1和2.

因为守护进程并不与终端设备相关联,所以不能再终端设备上显示其输出,也无处从交互式用户那里接收输入。


*在类Unix系统中,/dev/null,或称空设备,是一个特殊的设备文件,它丢弃一切写入其中的数据(但报告写入操作成功),读取它则会立即得到一个EOF。

发布了33 篇原创文章 · 获赞 6 · 访问量 9万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章