#include <syslog.h>
void syslog(int priority, const char *message, ...);
#include <syslog.h>
void openlog(const char *ident, int options, int facility);
void closelog(void);
1 创建子进程,父进程退出
这是创建守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成一程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。在Linux中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程是,就会自动由1号进程(init)收养它,这样,原先的子进程就会变成init进程的子进程。
2 在子进程中创建新会话
Linux是一个多用户多任务系统,每个进程都有一个进程ID,同时每个进程还都属于某一个进程组,而每个进程组都有一个组长进程,组长进程的标识ID等于进程组的ID,且该进程组ID不会因组长进程的退出而受到影响。会话期是一个或多个进程组的集合,通常,一个会话开始与用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。一个会话期可以有一个单独的控制终端,只有其前台进程才可以拥有控制终端,实现与用户的交互。从shell中启动的每一个进程将继承一个与之相结合的终端,以便进程与用户交互,但是守护进程不需要这些,子进程继承父进程的会话期和进程组ID,子进程会受到发送给该会话期的信号的影响,所以守护进程应该创建一个新的会话期,这个步骤是创建守护进程中最重要的一步,虽然它的实现非常简单,但它的意义却非常重大。在这里使用的是系统函数setsid来实现的。
setsid函数用于创建一个新的会话,并担任该会话组的组长。调用setsid有下面的3个作用:
让进程摆脱原会话的控制
让进程摆脱原进程组的控制
让进程摆脱原控制终端的控制
由于创建守护进程的第一步调用了fork函数来创建子进程,再将父进程退出。在调用fork函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,还还不是真正意义上的独立开来,而setsid函数能够使进程完全独立出来,从而摆脱其他进程的控制。
3 改变当前目录为根目录
使用fork创建的子进程继承了父进程的当前工作目录。守护进程不应当使用父进程的工作目录,应该设置自己的工作目录,通常可以通过chdir()来完成,一般可以将其设置为根目录,不过有些守护进程需要将它设置到自己特定的工作目录,但此时必须保证所设置的工作目录处于一个不能卸载的文件系统中,因为守护进程通常在系统引导后是一直存在的。
4 重设文件权限掩码
守护进程从父进程继承来的文件创建方式掩码可能会拒绝设置某些许可权限,文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用fork函数新建的子进程继承了父进程的文件权限掩码,可能使守护进程的执行出现问题,因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask。在这里,通常的使用方法为umask(0)。
5 关闭文件描述符
一般情况下,进程启动时都会自动打开终端文件,但是守护进程已经与终端脱离,所以终端描述符应该关闭。用fork函数新建的子进程也会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。
6 忽略SIGCHLD信号
这一步只对需要创建子进程的守护进程才有必要,很多服务器守护进程设计成通过派生子进程来处理客户端的请求,如果父进程不对SIGCHLD信号进行处理的话,子进程在终止后变成僵尸进程,通过将信号SIGCHLD的处理方式设置为SIG_IGN可以避免这种情况发生。
7 用日志系统记录出错信息
因为守护进程没有控制终端,当进程出现错误时无法写入到标准输出上,可以通过调用syslog将出错信息写入到指定的文件中。
8 守护进程退出处理
当用户需要外部停止守护进程运行时,往往会使用 kill命令停止该守护进程。所以,守护进程中需要编码来实现kill发出的signal信号处理,达到进程的正常退出。