一.
1.進程組
每個進程除了有一個ID,還屬於一個進程組。進程組是一個或多個進程的集合。通常,它們與同一作業相關聯,可以接收來自同一終端的各種信號。每個進程組有唯一的一個進程組ID,每個進程組都有一個組長進程。組長進程的標識是,其進程組ID等於其進程ID。
2.作業
Shell 分前後臺來控制的不是進程而是作業或進程組。一個前臺作業可以由多個進程組成,一個後臺也可以由多個進程組成, Shell可以運行一個前臺作業和任意多個後臺作業,這稱爲作業控制。
作業與進程組的區別:如果作業中的某個進程又創建了子進程,則子進程不屬於作業。一
旦作業運行結束, Shell就把其提到前臺,如果原來的前臺進程還存在(如果這個子進
程還沒終止),它自動變爲後臺進程組。
3.會話
會話(Session)是一個或多個進程組的集合。一個會話可以有一個控制終端。建立與控制終端連接的會話被稱爲控制進程。一個會話中的一個進程組可被分爲一個前臺進程組以及一個或多個後臺進程組。所以一個會話中,應該包括控制進程(會話進程),一個前臺進程組和任意後臺進程組。
1 $ proc1 | proc2 &
2 $ proc3 | proc4 | proc5
其中proc1與proc2屬於同一個後臺進程組, proc3, proc4和proc5屬於同一個前臺進程組,Shell本身屬於一個單獨的進程組。這些進程組的控制終端相同,它們同屬於一個會話,當用戶在控制終端輸入特殊的控制鍵(如Ctrl+C,產生SIGINT,Ctrk+\,產生SIGQUIT,Ctrl+Z,
產生SIGTSTP), 內核發送相應的信號給前臺進程組中的所有進程。
二.終端概念
在UNIX系統中,用戶通過終端登錄系統後得到一個Shell進程,這個終端成爲Shell進程的控制終端 (Controlling Terminal),控制終端是保存在PCB中的信息,我們知道fork會複製PCB中的信息,因此由Shell進程啓動的其它進程的控制終端也是這個終端。默認情況下(沒有重定向),每個進程的標準輸入、標準輸出和標準錯誤輸出都指向控制終端,進程從標準輸入讀也就是讀用戶的鍵盤輸入,進程往標準輸出或標準錯誤輸出寫也就是輸出到顯示器上。此外在控制
終端輸入特殊的控制鍵可以給前臺進程發信號,例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。
每個進程都可以通過一個特殊的設備文件/dev/tty訪問它的控制終 端。事實上每個終端設備
都對應多個不同的設備文件,/dev/tty提供了一個通用的接口 ,一個進程要訪問它的控制終端
既可以通過/dev/tty也可以通過該終端設備所對應的設備文件來訪問。 ttyname函數可以由
文件描述符查出對應的文件名,該文件描述符必須指向一個終端設備而不能是任意文件。
下面我們通過實驗看一下各種不同的終端所對應的設備文件名。
查看終端對應的設備
重開一個終端:
終端登錄過程:
init創建子進程,進行程序替換到一個getty進程,輸入用戶名之後,執行login,在輸入密碼進行驗證,驗證成功之後再替換到bash。
大致情況:從getty開始exec到login在exec到shell。
終端設備的輸入輸出緩衝區:
三.作業控制
Session與進程組
$ proc1 | proc2 &
$ proc3 | proc4 | proc5
其中proc1和proc2屬於同一個後臺進程組,proc3、 proc4、 proc5屬於同一個前臺進程組,Shell進
程本身屬於一個單獨的進程組。這些進程組的控制終端相同,它們屬於同一個Session。當用戶在控制終端輸入特殊的控制鍵(例如Ctrl-C)時,內核會發送相應的信號(例如SIGINT)給前臺進程組的所有進程。
各進程、進程組、 Session的關係如下圖所示:
從Session和進程組的度重新來看登錄和執命令的過程。例子:
這個作業由ps和cat兩個進程組成,在前臺運行。從PPID列可以看出這兩個進程的父進程是
bash。 從PGRP列可以看出,bash在id爲6994的進程組中,這個id等於bash的進程id,所以它是進程組的Leader,而兩個子進程在id爲8762的進程組中,ps是這個進程組的Leader。從SESS可以看出三個進程都在同一Session中,bash是Session Leader。從TPGID可以看出,前臺進程組的id是8762,也就是兩個子進程所在的進程組。
四.守護進程
守護進程也被稱爲精靈進程,是運行在後臺的一種特殊進程。它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。它與終端無關,自成一個會話,不與終端進行關聯。所以不會在標準輸出上打印東西,也不能從後臺到前臺。
創建守護進程:
創建守護進程最關鍵的一步是調用setsid函數創建立一個新的Session,併成爲Session Leader。
該函數調用成功時返回新創建的Session的id(其實也就是當前進程的id),出錯返回-1。注意,調用這個函數之前,當前進程不允許是進程組的Leader,否則該函數返回-1。要保證當前進程不是進程組的Leader也很容易,只要先fork再調setsid就可以了。 fork創建的子進程和父進程在同一個進程組中,進程組的Leader必然是該組的第一個進程,所以子進程不可能是該組的第一個進程,在子進程中調setsid就不會有問題了。
1. 調umask將文件模式創建屏蔽字設置爲0.
2. 調fork,父進程退出(exit) 。原因: 1)如果該守護進程是作爲一條簡單的shell命令
啓動的,那麼父進程終止使得shell認爲該命令已經執行完畢。 2)保證子進程不是這個
進程組的組長進程。
3. 調setsid創建一個新會話。 setsid會導致: 1)調進程成爲新會話的首進程。 2)調進程成爲一個進程組的組長進程 。 3)調進程沒有控制終端。(再次fork一次,保證
daemon進程,之後不會打開tty設備)
4. 將當前工作目錄更改爲根目錄。
5. 關閉不在需要的文件描述符。
6. 其他:忽略SIGCHLD信號。
自己的守護進程:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<signal.h>
5 void my_daemon()
6 { umask(0);
7 if(fork()>0)
8 {
9 exit(0);
10 }
11
12 setsid();
13 chdir("/");
14 close(0);
15 close(1);
16 close(2);
17 signal(SIGCHLD,SIG_IGN);
18 }
19 int main()
20 {
21 my_daemon();
22 while(1)
23 {;}
return 0;
}