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
就不會有問題了。
成功調用該函數的結果是:
創建一個新的Session,當前進程成為Session Leader,當前進程的id就是Session的id。
創建一個新的進程組,當前進程成為進程組的Leader,當前進程的id就是進程組的id。
如果當前進程原本有一個控制終端,則它失去這個控制終端,成為一個沒有控制終端的進程。所謂失去控制終端是指,原來的控制終端仍然是打開的,仍然可以讀寫,但只是一個普通的打開檔案而不是控制終端了。
例 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
命令才能看到。另外還可以看到,用戶關閉終端窗口或註銷也不會影響守護進程的運行。