Linux C編程一站式學習 學習筆記
一,基本概念
Linux 系統啓動時會啓動很多系統服務進程,如inetd,init等,這些系統服務進程沒有控制終端,不能直接和用戶交互。其它進程都是在用戶登錄或運行程序時創建,在運行結束或用戶註銷時終止,但系統服務進程不受用戶登錄註銷的影響,它們一直在運行着。這種進程有一個名稱叫守護進程(Daemon)。
我們用ps axj命令查看系統中的進程。參數a表示不僅列當前用戶的進程,也列出所有其他用戶的進程,參數x表示不僅列有控制終端的進程,也列出所有無控制終端的進程,參數j表示列出與作業控制相關的信息。
凡是TPGID
一欄寫着-1的都是沒有控制終端的進程,也就是守護進程。在COMMAND
一列用[]
括起來的名字表示內核線程,這些線程在內核裏創建,沒有用戶空間代碼,因此沒有程序文件名和命令行,通常採用以k
開頭的名字,表示Kernel。syslogd
負責維護/var/log
下的日誌文件,可以看出,守護進程通常採用以d
結尾的名字,表示Daemon。
二,創建守護進程
先介紹一下Linux中的進程與控制終端,登錄會話和進程組之間的關係:進程屬於一個進程組,進程組號(GID)就是進程Leader的進程號(PID)。登錄會話可以包含多個進程組。這些進程組共享一個控制終端。這個控制終端通常是創建進程的登錄終端。
#include <unistd.h>
pid_t setsid(void);
該函數調用成功時返回新創建的會話的id(其實也就是當前進程的id),出錯返回-1。注意,調用這個函數之前,當前進程不允許是進程組的Leader,否則該函數返回-1。要保證當前進程不是進程組的Leader也很容易,只要先fork
再調用setsid
就行了。fork
創建的子進程和父進程在同一個進程組中,進程組的Leader必然是該組的第一個進程,所以子進程不可能是該組的第一個進程,在子進程中調用setsid
就不會有問題了。setsid()調用成功後,進程成爲新的會話組長和新的進程組長,並與原來的登錄會話和進程組脫離。由於會話過程對控制終端的獨佔性,進程同時與控制終端脫離。
#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);
}
按照守護進程的慣例,通常將當前工作目錄切換到根目錄,將文件描述符0、1、2重定向到/dev/null
。Linux也提供了一個庫函數daemon(3)
實現我們的daemonize
函數的功能,它帶兩個參數指示要不要切換工作目錄到根目錄,以及要不要把文件描述符0、1、2重定向到/dev/null
。
測試如下:
xgx@ubuntu:~/Clan/thread$ ./a.out
xgx@ubuntu:~/Clan/thread$ ps
PID TTY TIME CMD
7168 pts/2 00:00:02 bash
13463 pts/2 00:00:00 ps
xgx@ubuntu:~/Clan/thread$ ps xj |grep a.out
1 13462 13462 13462 ? -1 Rs 1000 0:07 ./a.out
7168 13467 13466 7168 pts/2 13466 S+ 1000 0:00 grep --color=auto a.out