一、終端的概念
在UNIX系統中,使用者通過終端登入系統後得到一個Shell進程,這個終端成為Shell進程的控制終端(Controlling Terminal),控制終端是儲存在PCB中的資訊,而我們知道fork會複製PCB中的資訊,因此由Shell進程啟動的其它進程的控制終端也是這個終端。預設情況下(沒有重新導向),每個進程的標準輸入、標準輸出和標準錯誤輸出都指向控制終端,進程從標準輸入讀也就是讀使用者的鍵盤輸入,進程往標準輸出或標準錯誤輸出寫也就是輸出到顯示器上。在控制終端輸入一些特殊的修飾鍵可以給前台進程發訊號,例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。
每個進程都可以通過一個特殊的裝置檔案/dev/tty訪問它的控制終端。事實上每個終端裝置都對應一個不同的裝置檔案,/dev/tty提供了一個通用的介面,一個進程要訪問它的控制終端既可以通過/dev/tty也可以通過該終端裝置所對應的裝置檔案來訪問。ttyname函數可以由檔案描述符查出對應的檔案名稱,該檔案描述符必須指向一個終端裝置而不能是任意檔案。在linux上的命令tty 也可以查看到當前的終端。
比如我們在圖形介面下開啟一個終端可能是/dev/pts/0, 第二個可能是/dev/pts/1 ...(網路終端)
而切換到字元介面下可能是/dev/tty1 ...(虛擬終端)
二、作業控制
事實上,Shell分前後台來控制的不是進程而是作業(Job)或者進程組(Process Group)。一個前台作業可以由多個進程組成,一個後台作業也可以由多個進程組成,Shell可以同時運行一個前台作業和任意多個後台作業,這稱為作業控制(JobControl)。例如用以下命令啟動5個進程(這個例子出自APUE):
$ proc1 | proc2 &
$ proc3 | proc4 | proc5
其中proc1和proc2屬於同一個後台進程組,proc3、proc4、proc5屬於同一個前台進程組,Shell進程本身屬於一個單獨的進程組。這些進程組的控制終端相同,它們屬於同一個Session,一個Session與一個控制終端相關。當使用者在控制終端輸入特殊的修飾鍵(例如Ctrl-C)時,核心會發送相應的訊號(例如SIGINT)給前台進程組的所有進程。各進程、進程組、Session的關係如所示。
在上面的例子中,proc3、proc4、proc5被Shell放到同一個前台進程組,其中有一個進程是該進程組的Leader,Shell調用wait等待它們運行結束。一旦它們全部運行結束,Shell就調用tcsetpgrp函數將自己提到前台繼續接受命令。但是注意,如果proc3、proc4、proc5中的某個進程又fork出子進程,子進程也屬於同一進程組,但是Shell並不知道子進程的存在,也不會調用wait等待它結束。換句話說,proc3 | proc4
| proc5是Shell的作業,而這個子進程不是,這是作業和進程組在概念上的區別。一旦作業運行結束,Shell就把自己提到前台,如果原來的前台進程組還存在(如果這個子進程還沒終止),則它自動變成後台進程,被init進程接管。
三、守護進程
守護進程是在後台運行不受控端控制的進程,通常情況下守護進程在系統啟動時自動運行,使用者關閉終端視窗或登出也不會影響守護進程的運行,只能kill掉。守護進程的名稱通常以d結尾,比如sshd、xinetd、crond等
我們用ps axj命令查看系統中的進程,凡是TPGID(前台進程組ID)一欄寫著-1的都是沒有控制終端的進程,或者TTY一欄為?的,也就是守護進程。
四、建立守護進程步驟
調用fork(),建立新進程,它會是將來的守護進程
在父進程中調用exit,保證子進程不是進程組組長
調用setsid建立新的會話期
將目前的目錄改為根目錄
將標準輸入、標準輸出、標準錯誤重新導向到/dev/null
成功調用setsid函數的結果是:
建立一個新的Session,當前進程成為Session Leader,當前進程的id就是Session的id。
建立一個新的進程組,當前進程成為進程組的Leader,當前進程的id就是進程組的id。
如果當前進程原本有一個控制終端,則它失去這個控制終端,成為一個沒有控制終端的進程。
樣本程式:
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
|
/************************************************************************* > File Name: process_.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sat 23 Feb 2013 02:34:02 PM CST ************************************************************************/ #include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h>#define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int setup_daemon(int, int); /* 守護進程一直在後台運行且不控制終端 */ int main(int argc, char *argv[]) { // daemon(0, 0) setup_daemon(0, 0); printf("test ... \n"); // 無輸出 for(;;) ; return 0; } int setup_daemon(int nochdir, int noclose) { pid_t pid; pid = fork(); if (pid == -1) ERR_EXIT("fork error"); if (pid > 0) exit(EXIT_SUCCESS); /* 調用setsid的進程不能為進程組組長,故fork之後將父進程退出 */ setsid(); // 子進程調用後產生一個新的會話期 if (nochdir == 0) chdir("/"); //更改目前的目錄為根目錄 if (noclose == 0) { int i; for (i = 0; i < 3; ++i) close(i); open("/dev/null", O_RDWR); // 將標準輸入,標準輸出等都重新導向到/dev/null dup(0); dup(0); } return 0; } |
執行程式再ps axj 一下:
simba@ubuntu:~/Documents/code/linux_programming/APUE/process$ ./daemon
simba@ubuntu:~/Documents/code/linux_programming/APUE/process$ ps axj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
...........................................................................................................................
1 7678 7678 7678 ? -1 Rs 1000 0:03 ./daemon
可以看出守護進程的ID也是進程組的ID,也是會話期的ID,此外這個會話期沒有前台進程組。
五、使用daemon函數實現守護進程
功能:建立一個守護進程
原型:int daemon(int nochdir, int noclose);
參數:
nochdir:=0將目前的目錄更改至“/”
noclose:=0將標準輸入、標準輸出、標準錯誤重新導向至“/dev/null”
參考:《linux c 編程一站式學習》、《APUE》