一個進程在終止時會關閉所有檔案描述符,釋放在使用者空間分配的記憶體,但它的PCB還保留著。
核心在其中儲存了一些資訊:如果是正常終止則儲存著退出狀態,如果是異常退出則儲存著導致該進程終止的資訊是哪個。這個進程的父進程可以調用wait或waitpid擷取這些資訊,然後徹底清除掉這個進程。我們知道一個進程的退出狀態可以在Shell中用特殊變數 $? 查看,因為Shell是它的父進程,當它終止時Shell調用 wait 或 waitpid得到它的退出狀態同時徹底清除掉這個進程。
當一個進程正常或異常終止時,核心就向其父進程發送一個SIGCHLD訊號。因為子進程終止是一個非同步事件,所以發生這種訊號也是核心向父進程發的非同步通知。父進程可以選擇忽略該訊號,或者提供一個該訊號發生時即被調用執行的函數。對於這種訊號的系統預設動作是忽略它。
上述敘述中提到了檔案描述符、wait或waitpid,下面進行說明
1、檔案描述符
三種標準輸出資料流:stdin,stdout和stderr
#include<stdio.h>
extern FILE* stdin; extern FILE* stdout; externa FILE* stderr;
FILE*這種方式是由c語言(標準庫)提供的;系統中任何一個程式跑起來都會預設得與這個程式關聯3個標準輸入輸出資料流,分別是鍵盤、顯示 器和顯示器。
1)檔案標識符:0表示標準輸入,1表示標準輸出,2表示標準錯誤。
下面程式進行說明
2)檔案描述符是從0開始的一連串的小整數,程式運行起來後形成一個進程,通過進程找到檔案,預設開啟3個檔案0,1,2。之後開啟的檔案,檔案 描述符從3開始進行遞增。如圖所示
一旦關閉了某個檔案描述符,則之後的檔案描述符先為那個關閉檔案描述符,接著從3開始遞增。關閉0,即關閉標準輸入,如圖:
3)系統調用在庫函數的下層,庫函數調用系統函數。
2、wait()或waitpid()函數
標頭檔:
#include<sys/types.h>
#include<sys/wait.h>
等待函數:
1)pid_t wait(int *status);
傳回值:成功返回被等待進程pid,失敗返回-1
參數:輸出型參數,擷取進程退出狀態,不關心則可以設定為NULL
如果進程由於接收到SIGCHLD而調用wait,則可期望wait會立即返回;但如果在任意時刻調用wait,則進程可能阻塞。
在一個子進程終止前, wait使其調用者堵塞,而waitpid有一個選項,可使調用者不堵塞。如果status不是null 指標,則終止進程的終止狀態就存放在它所指的單元內。如果不關心終止狀態,則可將參數設定為空白指標(waitpid同樣適用)。
2)pid_t waitpid(int *status);
傳回值:
a、當正常返回的時候waitpid返回收集到的子進程的進程id
b、如果設定了選項WNOHANG,而調用中waipid發現沒有已退出的子進程可收集,則返回0
c、如果調用者出錯,則返回-1,這是errno會被設定成相應的值以指示錯誤所在
d、當pid所指示的子進程不存在,或此進程存在,但不是調用進程的子進程,waitpid就會出錯返回,這時errno被設定為ECHILD
參數:
a、pid(進程id)
pid=-1;等待任一個子進程,與wait等效。
pid >0;等待其進程ID與pid相等的子進程。
pid == 0;等待其組id等於進程組id的任一個子進程。
pid <-1;等待其組ID等於pid絕對值的任一個子進程。
b、status(int型)
status的高8位(WIFEXITED(status)) :若為正常終止子進程返回的狀態,則為真。(查看進程是否是正常退出)
status的低8位 (WEXITSTATUS(status)) : 若WIFEXITED非零,提取子進程退出碼。(查看進程的退出碼)
c、options(等待方式)
options為0:表示以阻塞方式進行等待,這種叫做阻塞式等待。
options為WNOHANG:若pid指定的子進程沒有結束,則waitpid()函數返回0(沒有錯誤,也沒有讀取成功)不予以等待。若正常結 束,則返回該子進程的ID。
1 /************************************************************************* 2 > File Name: wait.c 3 > Author: ma6174 4 > Mail: ma6174@163.com 5 > Created Time: 2016年07月24日 星期日 19時57分23秒 6 ************************************************************************/ 7 8 #include<stdio.h> 9 #include<sys/types.h> 10 #include<sys/wait.h> 11 #include<stdlib.h> 12 13 int main() 14 { 15 pid_t id = fork(); 16 if(id < 0){ 17 printf("fork failure\n"); 18 } 19 else if(id == 0){ 20 printf("child, pid:%d\n",getpid()); 21 sleep(3); 22 //while(1);//會一直在等待子進程結束 23 printf("child deading...\n"); 24 exit(0); 25 //exit(123);//子進程的退出碼 26 //exit(257);//異常退出,發生溢出 27 } 28 else{ 29 printf("father,pid:%d\n",getpid()); 30 //pid_t ret = wait(NULL);//不關心進程退出狀態 31 //int status = 0; 32 //pid_t ret = waitpid(id, &status,0);//0表示預設以阻塞方式進行等待 33 while(1){ 34 pid_t ret = waitpid(id, NULL,WNOHANG); 35 printf("father wait return...\n"); 36 sleep(1); 37 if(ret == 0){//進程沒有結束 38 printf("child is running,Please wait\n"); 39 }//等待成功或失敗就退出迴圈 40 else if(ret = id){//wait child success 41 printf("wait success\n"); 42 break; 43 //printf("code is done? %d\n",status&0xff);//查看進程是否正常退出 44 //printf("exit code? %d\n",(status>>8)&0xff);//查看退出碼 45 }else{ 46 printf("wait failed\n"); 47 break; 48 } 49 sleep(2);//休眠2秒,不會一直等待 50 } 51 } 52 53 }<span style="color:#cc0000;"></span>
運行結果如下:
3)檢查wait和waitpid所返回的終止狀態:
a、利用按位與,得到static的前8位和後8位
b、利用系統中的宏
WIFEXITED(status) : 若為正常終止子進程返回的狀態,則為真。
WEXITSTATUS(status) : 若WIFEXITED非零,返回子進程退出碼,提取進程退出傳回值,如果子進程調exit(7),WEXITSTATUS(status就會返 回7。請注意,如果進程不是正常退出的,也就是說,WIFEXITED返回0,這個值就毫無意義。
說明:status 並不簡簡單單是一個整形變數,父進程和子進程之間所有的狀態互動都要通過這個int來表示,所以這個int的若干bit位都是有特殊的含義的,那麼這個“int”如何編碼就比較重要了,和IP地址一樣,它是比較緊湊的,或者說是比較擁擠的。
status指出了子進程是正常退出還是被非正常結束的(一個進程也可以被其他進程用訊號結束),以及正常結束時的傳回值,或被哪一個訊號結束或進程的退出碼是多少等資訊,這些資訊都被放在整數的不同二進位位中,所以用常規的方法讀取會非常麻煩,所以開發人員就設計了一套專門的宏(macro)來完成這項工作。
4)wait和waitpid的作用:
a、等待目的:子進程讀取到子進程退出的一個狀態資訊
b、讓系統釋放掉子進程佔有的殭屍狀態的資源
c、運行時不保證父子進程誰先運行,可保證子進程先退出,等待使父子進程的退出同步起來(即退出產生一定的順序)
5)waitpid提供了wait沒有提供的功能:
a、waitpid可等待一個特定的進程
b、waitpid提供了一個wait的非阻塞版本
c、waitpid支援作業控制
3、進程程式替換
用fork建立子進程後執行的是和父進程相同的程式(但有可能執行不同的代碼分支),子進程往往要調用一種exec函數以執行另一個程式。當進程調用一種exec函數時,該進程的使用者空間代碼和資料完全被新程式替換,從新程式的啟動常式開始執行。調用exec並不建立新進程,所以調用exec前後該進程的id並未改變。
1)其實有6種以exec開頭的函數,統稱為exec函數:
#incldue<unistd.h>
int execl(const char* path,const char* arg,....);
int execlp(const char* file,const char* arg,....);
int execle(const char* path,const char* arg,..., char* const emp[]);
int execv(const char* path,char* const argv[]);
int execp(const char* file,char* const argv[]);
int execve(const char* path,char* const argv[],char* const emp[]);
這些函數如果調用成功,則載入新的程式從啟動代碼開始執行,不再返回。
如果調用出錯則返回-1,所以exec函數只有出錯的傳回值而沒有成功的傳回值。
2)這些函數的規則:
不帶字母p (表示path)的exec函數 第一個參數必須是程式的相對路徑或絕對路徑,例如"/bin/ls"或"./a.out",而不能 是"ls"或"a.out"。對於帶字母p的函數: 如果參數中包含/,則將其視為路徑名。 否則視為不帶路徑的程式名,在PATH環境變數的目錄列表中搜尋這個程式。
帶有字母l( 表示list)的exec函數要求將新程式的每個命令列參數都當作一個參數傳給它,命令列參數的個數是可變的,因此函數原型中有...,...中的最後一個可變參數應該是NULL, 起sentinel的作用。
帶有字母v( 表示vector)的函數,則應該先構造一個指向各參數的指標數 組,然後將該數組的首地址當作參數傳給它,數組中的最後一個指標也應該是NULL,就像main函數的argv參數或者環境變數表一樣。
對於以e (表示environment)結尾的exec函數,可以把一份新的環境變數表傳給它,其他exec函數仍使用當前的環境變數表執行新程式。
上述進程用程式替換執行如下:
Linux下的open()、close()、write()和read()函數
1. open()函數
功能描述:用於開啟或建立檔案,在開啟或建立檔案時可以指定檔案的屬性及使用者的許可權等各種參數。
所需標頭檔:#include <sys/types.h>,#include <sys/stat.h>,#include <fcntl.h>
函數原型:int open(const char *pathname,int flags,int perms)
參數:
pathname:被開啟的檔案名稱(可包括路徑名如"dev/ttyS0")
flags:檔案開啟檔案,
O_RDONLY:以唯讀方式開啟檔案
O_WRONLY:以唯寫方式開啟檔案
O_RDWR:以讀寫方式開啟檔案
O_CREAT:如果改檔案不存在,就建立一個新的檔案,並用第三個參數為其設定許可權
O_EXCL:如果使用O_CREAT時檔案存在,則返回錯誤訊息。這一參數可測試檔案是否存在。此時open是原子操作,防止多個進程同時建立同一個檔案
O_NOCTTY:使用本參數時,若檔案為終端,那麼該終端不會成為調用open()的那個進程的控制終端
O_TRUNC:若檔案已經存在,那麼會刪除檔案中的全部原有資料,並且設定檔案大小為0
O_APPEND:以添加方式開啟檔案,在開啟檔案的同時,檔案指標指向檔案的末尾,即將寫入的資料添加到檔案的末尾
O_NONBLOCK: 如果pathname指的是一個FIFO、一個塊特殊檔案或一個字元特殊檔案,則此選擇項為此檔案的本次開啟操作和後續的I/O操作設定非阻塞方式。
O_SYNC:使每次write都等到物理I/O操作完成。
O_RSYNC:read 等待所有寫入同一地區的寫操作完成後再進行
在open()函數中,falgs參數可以通過“|”組合構成,但前3個標準常量(O_RDONLY,O_WRONLY,和O_RDWR)不能互相組合。
perms:被開啟檔案的存取許可權,可以用兩種方法表示,可以用一組宏定義:S_I(R/W/X)(USR/GRP/OTH),其中R/W/X表示讀寫執行許可權,
USR/GRP/OTH分別表示檔案的所有者/檔案所屬組/其他使用者,如S_IRUUR|S_IWUUR|S_IXUUR,(-rex------),也可用八進位800表示同樣的許可權
傳回值:
成功:返迴文件描述符
失敗:返回-1
2. close()函數
功能描述:用於關閉一個被開啟的的檔案
所需標頭檔: #include <unistd.h>
函數原型:int close(int fd)
參數:fd檔案描述符
函數傳回值:0成功,-1出錯
3. read()函數
功能描述: 從檔案讀取資料。
所需標頭檔: #include <unistd.h>
函數原型:ssize_t read(int fd, void *buf, size_t count);
參數:
fd: 將要讀取資料的檔案描述詞。
buf:指緩衝區,即讀取的資料會被放到這個緩衝區中去。
count: 表示調用一次read操作,應該讀多少數量的字元。
傳回值:返回所讀取的位元組數;0(讀到EOF);-1(出錯)。
以下幾種情況會導致讀取到的位元組數小於 count :
A. 讀取普通檔案時,讀到檔案末尾還不夠 count 位元組。例如:如果檔案只有 30 位元組,而我們想讀取 100
位元組,那麼實際讀到的只有 30 位元組,read 函數返回 30 。此時再使用 read 函數作用於這個檔案會導致 read 返回 0 。
B. 從終端裝置(terminal device)讀取時,一般情況下每次只能讀取一行。
C. 從網路讀取時,網路緩衝可能導致讀取的位元組數小於 count位元組。
D. 讀取 pipe 或者