UNIX環境高階程式設計 第八章:程序控制
1.專用程序:
ID0是排程程序,被稱為交換程序,不執行任何磁碟程式,被稱為系統程序
ID1是init程序,永遠不會死亡的一般使用者程序,而不是kernel裡的系統程序,事物偶有孤兒程序的父程序。是核心在bootstrap結束後呼叫。
ID2是頁守護程序,負責支援虛擬儲存系統的分頁操作。
2.fork
exec系統呼叫從指定程式重新初始化程序,雖然程序還在,但程式已經改變了。exec初始化新的程式
fock系統呼叫僅通過複製指令、使用者資料和系統資料段來建立從現存程序克隆的新程序,該新程序不是從程式初始化得來的,所以舊程序和新程序執行同樣的指令。fork建立新的程序
(1)子程序複製了父程序的資料空間,棧,堆,但是之後互不影響
(2)共享正文段
(3)fork的特性,所有父程序開啟的檔案描述符都會複製到子程序中
自從fork經常接著呼叫exec,當前很多實現不執行完整的父資料,棧,堆的複製。這些區域是父子程序以read-only方式共享的。在需要修改這些區域的時候,kernel會以page進行復制。
當父子程序寫同一秒叔叔指向的檔案時,父程序等待child完成再進行write操作,在child進行write後更新了file offset。然後父程序的操作會在該offset後進行write,父子程序各自做各自的事情:fork之後,父子程序關閉其不需要的描述符。
child簡單的在vfork之後呼叫exec or exit。換句話說,在執行exec或者exit之前,child執行在parent的地址空間。vfork保證child首先執行,直到child呼叫exec或者exit。呼叫之後,恢復parent的執行。
1.如果parent在child之前中止會發生什麼?
init程序會成為所有parent程序中止的child程序的父程序。
2.child在parent之前中止會發生什麼?
kernel儲存了所有中止程序的小部分資訊(processID,中止狀態,CPU執行時間),在parent呼叫waitorwaitpid時可以獲得這些資訊。如果parent沒有呼叫這些函式,已經中止的子程序處於zombie狀態
3.init程序的子程序中止後,會進入zombie狀態嗎?
不會!init會自動呼叫wait獲取其中止狀態
4.wait和waitpid區別
wait會阻塞直到子程序中止。waitpid能有option能防止阻塞。waitpid提供了無阻塞版本的wait
waitpid不等待第一個中止的childprocess,有相應option控制等待哪個程序。waitpid是我們等待一個特定的程序
waitid和waitpid一樣允許等待指定的子程序將process ID和process group ID 使用了分離的引數,而不是像waitpid和wait一樣合成為一個引數。
5.waitpid引數pid的四種value
pid = -1, 等待任何子程序。在這種情況下waitpid等於wait
pid > 0, 等待程序ID等於pid的子程序
pid == 0, 等待任何程序組ID等於呼叫程序的(process group id)的程序
pid < -1, 等待任何程序組ID(process group id)等於pid絕對值的子程序
6.wait3,wait4是什麼?
提供了與wait,waitpid,waitid函式唯一不能提供的特性:增加了額外的引數,用於存放中止程序的所有資源的彙總和其所有的子程序
7.exec
呼叫exec的程序會完全被新的程式替代,從新程式的main開始執行
8.改變User IDs and Group
IDs:setuid,setgid,setreuid,setregid,seteuid,setegid
9.system的具體實現內部有哪些系統呼叫?使用system的優勢!
fork、exec、waitpid.相對於直接呼叫fork和exec,system能夠處理error和處理signal。
絕對不要在set-user-ID、set-group-ID程式裡呼叫system,這樣做會產生安全漏洞,
如果一個程序以特殊許可權(設定使用者ID和組ID)執行,又想生成另外一個程序執行另外的程式,可以直接使用fork和exec,並且在fork之後,exec之前改回普通許可權。
10.getlogin:用於得到登入名;#include
(一)終端登入:
init—>init—>getty—>login–>getpwname–>getpass–>crypt–>登入成功
1.當系統自舉時,核心建立程序ID為 1 的程序,即init程序
2.init程序讀取檔案/etc/ttys(對於linux:init讀取的是/etc/inittab檔案而不是/etc/ttys檔案),對每個允許登入的終端裝置,init呼叫一次fork,其所生成的子程序則exec getty程式(以一個空的環境)
3.getty對終端裝置呼叫open函式,以讀、寫方式將終端開啟。
4.login:如果使用者鍵入的口令正確,則login完成下列工作:
1.將當前工作目錄更改為使用者的起始目錄
2.呼叫chown更改該終端的所有權,使得登入使用者成為它的所有者
3.將對該終端裝置的訪問許可權變成”使用者讀和寫“
4.呼叫setgid和initgroups設定程序組ID
5.用login得到的所有資訊初始化環境:起始目錄(HOME)、shell(SHELL)、使用者名稱(USER和LOGNAME)以及一個系統預設路徑(PATH)
6.login程序呼叫setuid,將程序的使用者ID更改登入使用者的使用者ID,並呼叫該使用者的登入shell,其方式類似於:execl("/bin/sh","-sh",(char*)0);
7.至此,登入使用者的登入shell開始執行。登入shell讀取其啟動檔案(如.profile)。這些啟動檔案通常是更改某些環境變數並增加很多環境變數。當執行完啟動檔案後,使用者最後得到shell提示符,並能鍵入命令。
(二)網路登入:對於網路登入,所有登入都是經由核心的網路介面驅動程式。
在BSD系統中,由init執行shell指令碼/etc/rc,此shell指令碼啟動inetd守護程序。由inetd負責處理網路登入。
在Linux系統中,使用xinetd代替inetd程序
(三)程序組:每個程序除了有一個程序ID之外,還屬於一個程序組。程序組是一個或者多個程序的集合。
1.通常程序組是在同一個作業中結合起來的
2.同一個程序組中的各程序接收來自同一個終端的訊號
3.每個程序中都有一個唯一的程序組ID標誌
程序組ID類似於程序ID,是一個整數並且可以存放在pid_t資料型別中
4.每個程序組都有一個組長程序。程序組ID就等於組長程序的程序ID,程序組的組長程序可以建立該組中的程序,程序組的組長程序也可以終止。只要程序組中至少有一個程序存在,則該程序中就存在,這與組長程序是否終止無關.
5.程序組的生命週期:從組長程序建立程序中開始,到組內最後一個程序離開為止的時間;這裡用離開,是因為程序可以從一個程序組轉移到另一個程序組
getpgrp/getpgid函式:獲取程序所屬的程序組:
#include<unistd.h>//成功,則返回程序組ID;失敗返回 -1
pid_t getpgrp(void);
pid_t getpgid(pid_t pid);
對於getpgrp函式:其返回值是呼叫程序的程序組ID(沒有失敗值)
對於getpgid函式: 引數:pid為待檢視程序的程序ID。如果pid=0,則返回呼叫程序的程序組ID.
(四)會話:是一個或者多個程序組的集合。setsid函式:建立一個新會話
1.setsid( )函式
pid_t setsid(void);//成功:返回程序組ID;失敗:返回 -1。
程序呼叫setsid建立一個新會話。如果呼叫此函式的程序不是一個程序組的組長程序,則此函式建立一個新會話並且發生下面三件事:
1.該程序會變成新會話的會話首程序session leader。此時該程序是新會話中的唯一程序
會話首程序是建立該會話的程序
2.該程序成為一個新程序組的組長程序,新程序組ID就是該呼叫程序的程序ID
3.該程序沒有控制終端。即使呼叫setsid之前該程序有一個控制終端,該聯絡也被切斷
如果呼叫此函式的程序是個程序組的組長,則此函式返回出錯。通常是程序首先fork,然後父程序終止,子程序呼叫setsid繼續執行。這確保了子程序不是一個程序組的組長。
2.getsid函式:
pid_t getsid(pid_t pid);//pid:待檢視程序的程序ID;成功:會話ID;失敗:返回-1
返回程序所在的會話ID(會話ID等於會話首程序的程序組ID,會話首程序總是程序組的組長程序,因此它也等於會話首程序的程序ID)
如果pid為0,則getsid返回呼叫程序的會話ID。如果pid並不屬於呼叫者所在的會話,則呼叫程序就不能得到該會話ID.
(五)作業控制
1.會話和程序組還有一些特性:
1.一個會話可以有一個控制終端controlling terminal,這可以是終端裝置(在終端登入的情況下),也可以是偽終端裝置(在網路登入的情況下)
2.建立與控制終端連線的會話首程序稱作控制程序controlling process
3.一個會話中的程序組可以分成一個前臺程序組,以及一個或者多個後臺程序組
4.如果一個會話有一個控制終端,則它有一個前臺程序組,其他程序組為後臺程序組
5.無論何時鍵入終端的中斷鍵(通常是Ctrl+C),都會將中斷訊號傳送至前臺程序組的所有程序
6.無論何時鍵入終端的退出鍵(通常是Ctrl+\),都會將退出訊號傳送至前臺程序組的所有程序
2.tcgetpgrp/tcsetpgrp函式:獲取/設定當前程序所在會話的前臺程序組ID
#include<unistd.h>
pid_t tcgetpgrp(int fd);//fd:程序在fd這個描述符上開啟的終端,成功則返回前臺程序組ID,失敗返回 -1
int tcsetpgrp(int fd,pid_t pgrpid);//pgrpid:待設定的前臺程序組ID,成功返回 0;失敗返回 -1
如果程序有一個控制終端,則該程序可以呼叫tcsetpgrp將前臺程序組ID設定為pgrpid,其中:
- pgrpid必須是同一個會話的一個程序組的ID
- fd必須引用該會話的控制終端
注意:大多數應用程式並不直接使用這兩個函式,它們通常是由作業控制shell呼叫
3.tcgetsid函式:獲取會話首程序的程序組ID(也就是會話ID)
#include<termios.hh>
pid_t tcgetsid(int fd);//fd:程序在fd這個描述符上開啟的終端;成功則返回前臺程序組ID,失敗返回 -1
注意:會話ID不一定等於前臺程序組的組ID。對於一個會話,會話ID通常不變(前提是沒有函式主動設定它);但是前臺程序組程序由於作業排程會經常發生變化
4. 孤兒程序組:
一個父程序已終止的程序稱為孤兒程序,這種程序由init程序收養。
一個程序組不是孤兒程序組的條件是:該組中有一個程序,其父程序在屬於同一對話期的另一個組中。如果程序組不是孤兒程序組,則在屬於同一對話期的另一個組中的父程序就有機會重新啟動該組中停止的程序。