daemon_int

摘自 UNP

 1 #include "unp.h"

 2 #include <syslog.h>

 3 

 4 #define MAXFD 64

 5 

 6 extern int daemon_proc;        // defined in error.c

 7 

 8 int daemon_init(const char* pname, int facility)

 9 {

10     int        i;

11     pid_t    pid;

12 

13     if ( (pid = fork()) < 0)

14         return -1;

15     else if (pid)

16         _exit(0);    // parent terminates

17 

18     // child 1 continue...

19 

20     if (setsid() < 0)    // become session leader

21         return -1;

22     

23     signal(SIGHUP, SIG_IGN);

24     if ( (pid = fork()) < 0)

25         return -1;

26     else if (pid)

27         _exit(0);    // child 1 terminates

28     

29     // child 2 continue...

30 

31     daemon_proc = 1;    // for err_XXX() functions

32 

33     chdir("/");            // change working directory

34     

35     // close off file descriptors

36     for (i=0; i<MAXFD; ++i)

37         close(i);

38     

39     // redirect stdin, stdout, and stderr to /dev/null

40     open("/dev/null", O_RDONLY);

41     open("/dev/null", O_RDWR);

42     open("/dev/null", O_RDWR);

43 

44     openlog(pname, LOG_PID, facility);

45 

46     return 0;    // success

47 }

 

fork 

13~16  首先调用 fork,然后终止父进程,留下子进程继续运行。如果本进程是从前台作为一个 shell 命令启动的,当父进程终止时,shell 就认为该命令已执行完毕。这样子进程就自动在后台运行。另外,子进程继承了父进程的进程组 ID,不过它有自己的进程 ID。这就保证子进程不是一个进程组的头进程,这是接下去调用 setsid 的必要条件。

 

setsid 

20~21  setsid 是一个 POSIX 函数,用于创建一个新的会话(session)。当前进程变为新会话的会话头进程以及新进程组的进程组头进程,从而不再有控制终端。

 

忽略 SIGHUP 信号并再次 fork

23~27  忽略 SIGHUP 信号并再次调用 fork。该函数返回时,父进程实际上是上一次调用 fork 产生的子进程,它被终止掉,留下新的子进程继续运行。再次 fork 的目的是确保本守护进程将来即使打开了一个终端设备,也不会自动获得控制终端。当没有控制终端的一个会话头进程打开一个终端设备时(该终端不会是当前某个其他会话的控制终端),该终端自动成为这个会话头进程的控制终端。然而再次调用 fork 之后,我们确保新的子进程不再是一个会话头进程,从而不能自动获得一个控制终端。这里必须忽略 SIGHUP 信号,因为当会话头进程(即首次 fork 产生的子进程)终止时,其会话中的所有进程(即再次 fork 产生的子进程)都收到 SIGHUP 信号。

 

为错误处理函数设置标识

31    把全局变量 daemon_proc 置为非 0 值。这个外部变量由我们的 err_XXX 函数定义,其值非 0 是在告知它们改为调用 syslog,以取代 fprintf 到标准错误输出。该变量省得我们从头到尾修改程序代码,在服务器不是作为守护进程运行的场合(例如测试服务器程序时)调用某个错误处理函数,在服务器作为守护进程运行的场合调用 syslog。

 

改变工作目录

33    把工作目录改到根目录,不过有些守护进程另有原因需改到其他目录。举例来说,打印机守护进程可能改到打印机的假脱机处理(spool)目录,因为那里是它做全部工作的地方。要是守护进程产生了某个 core 文件,该文件就存放在当前工作目录中。改变工作目录的另一个理由是,守护进程可能是在某个任意的文件系统中启动,如果仍然在其中,那么该文件系统就无法拆卸(unmounting),除非使用潜在破坏性的强制措施。

 

关闭所有打开的描述符

36~37  关闭本守护进程从执行它的进程(通常是一个 shell)继承来的所有打开着的描述符。问题是怎样监测正在使用的最大描述符:没有现成的 Unix 函数提供该值。监测当前进程能够打开的最大描述符数目自有办法,然而由于这个限制可以是无限的,这样的监测也变得复杂起来。我们的解决办法是干脆关闭前 64 个描述符,即使其中大部分可能并没有打开。

    Solaris 提供了一个名为 closefrom 的函数,可以用于解决守护进程的这个问题。

 

将 stdin、stdout 和 stderr 重定向到 /dev/null

40~42  打开 /dev/null 作为本守护进程的标准输入、标准输出和标准错误输出。这一点保证这些常用描述符是打开的,针对它们的 read 系统调用返回 0(EOF),write 系统调用则由内核丢弃所写数据。打开这些描述符的理由在于,守护进程调用的那些假设能从标准输入读或者往标准输出或标准错误输出写的库函数将不会因为这些描述符未打开而失败。这种失败是一种隐患。要是一个守护进程未打开这些描述符,却作为服务器打开了与某个客户关联的一个套接字,那么这个套接字很可能占用这些描述符(譬如标准输出或标准错误输出的描述符 1 或 2),这种情况下如果守护进程调用诸如 perror 之类函数,那就会把非预期的数据发送给那个客户。

 

使用 syslog 处理错误

44    调用 openlog。其中第一个参数来自调用者,通常是程序的名字(譬如 argv[0])。第二个参数指定把进程 ID 加到每个日志消息中。第三个参数同样由调用者指定,其值为图13-2所示的常值之一或为0(如果默认值 LOG_USER 可以接受的话)。

 

    我们指出,既然守护进程在没有控制终端的环境下运行,它绝不会收到来自内核的 SIGHUP 信号。许多守护进程因此把这个信号作为来自系统管理员的一个通知,表示其配置文件已发生改动,守护进程应该重新读入其配置文件。守护进程同样绝不会收到来自内核的 SIGINT 信号和 SIGWINCH 信号,因此这些信号也可以安全地用作系统管理员的通知手段,指示守护进程应该做出反应的某种变动已经发生。

你可能感兴趣的:(Daemon)