3. 守護進程

Linux系統啟動時會啟動很多系統服務進程,例如第 1.3 節 “網絡登錄過程”講的inetd,這些系統服務進程沒有控制終端,不能直接和用戶交互。其它進程都是在用戶登錄或運行程序時創建,在運行結束或用戶註銷時終止,但系統服務進程不受用戶登錄註銷的影響,它們一直在運行着。這種進程有一個名稱叫守護進程(Daemon)

下面我們用ps axj命令查看系統中的進程。參數a表示不僅列當前用戶的進程,也列出所有其他用戶的進程,參數x表示不僅列有控制終端的進程,也列出所有無控制終端的進程,參數j表示列出與作業控制相關的信息。

$ ps axj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    0     1     1     1 ?           -1 Ss       0   0:01 /sbin/init
    0     2     0     0 ?           -1 S<       0   0:00 [kthreadd]
    2     3     0     0 ?           -1 S<       0   0:00 [migration/0]
    2     4     0     0 ?           -1 S<       0   0:00 [ksoftirqd/0]
...
    1  2373  2373  2373 ?           -1 S<s      0   0:00 /sbin/udevd --daemon
...
    1  4680  4680  4680 ?           -1 Ss       0   0:00 /usr/sbin/acpid -c /etc
...
    1  4808  4808  4808 ?           -1 Ss     102   0:00 /sbin/syslogd -u syslog
...

凡是TPGID一欄寫着-1的都是沒有控制終端的進程,也就是守護進程。在COMMAND一列用[]括起來的名字表示內核綫程,這些綫程在內核裡創建,沒有用戶空間代碼,因此沒有程序檔案名和命令行,通常採用以k開頭的名字,表示Kernel。init進程我們已經很熟悉了,udevd負責維護/dev目錄下的設備檔案,acpid負責電源管理,syslogd負責維護/var/log下的日誌檔案,可以看出,守護進程通常採用以d結尾的名字,表示Daemon。

創建守護進程最關鍵的一步是調用setsid函數創建一個新的Session,並成為Session Leader。

#include <unistd.h>

pid_t setsid(void);

該函數調用成功時返回新創建的Session的id(其實也就是當前進程的id),出錯返回-1。注意,調用這個函數之前,當前進程不允許是進程組的Leader,否則該函數返回-1。要保證當前進程不是進程組的Leader也很容易,只要先fork再調用setsid就行了。fork創建的子進程和父進程在同一個進程組中,進程組的Leader必然是該組的第一個進程,所以子進程不可能是該組的第一個進程,在子進程中調用setsid就不會有問題了。

成功調用該函數的結果是:

例 34.2. 創建守護進程

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

void daemonize(void)
{
	pid_t  pid;

	/*
	 * Become a session leader to lose controlling TTY.
	 */
	if ((pid = fork()) < 0) {
		perror("fork");
		exit(1);
	} else if (pid != 0) /* parent */
		exit(0);
	setsid();

	/*
	 * Change the current working directory to the root.
	 */
	if (chdir("/") < 0) {
		perror("chdir");
		exit(1);
	} 

	/*
	 * Attach file descriptors 0, 1, and 2 to /dev/null.
	 */
	close(0);
	open("/dev/null", O_RDWR);
	dup2(0, 1);
	dup2(0, 2);
}

int main(void)
{
	daemonize();
	while(1);
}

為了確保調用setsid的進程不是進程組的Leader,首先fork出一個子進程,父進程退出,然後子進程調用setsid創建新的Session,成為守護進程。按照守護進程的慣例,通常將當前工作目錄切換到根目錄,將檔案描述符0、1、2重定向到/dev/null。Linux也提供了一個庫函數daemon(3)實現我們的daemonize函數的功能,它帶兩個參數指示要不要切換工作目錄到根目錄,以及要不要把檔案描述符0、1、2重定向到/dev/null

$ ./a.out 
$ ps
  PID TTY          TIME CMD
11494 pts/0    00:00:00 bash
13271 pts/0    00:00:00 ps
$ ps xj | grep a.out
    1 13270 13270 13270 ?           -1 Rs    1000   0:05 ./a.out
11494 13273 13272 11494 pts/0    13272 S+    1000   0:00 grep a.out
(關閉終端窗口重新打開,或者註銷重新登錄)
$ ps xj | grep a.out
    1 13270 13270 13270 ?           -1 Rs    1000   0:21 ./a.out
13282 13338 13337 13282 pts/1    13337 S+    1000   0:00 grep a.out
$ kill 13270

運行這個程序,它變成一個守護進程,不再和當前終端關聯。用ps命令看不到,必須運行帶x參數的ps命令才能看到。另外還可以看到,用戶關閉終端窗口或註銷也不會影響守護進程的運行。