進程控制(1)
進程標識符
每一個進程都有肺腑的整形表示唯一的進程ID。按一個進程終止后,其進程ID就能夠再次使用了。例如以下是幾個典型進程的ID及其類型和功能。
ID???? 進程名????????中文名?????????????????類型?????????????????作用
0?????swapper????? 交換進程??????????????系統進程????????它是內核一部分,不運行磁盤上的程序,是調度進程。
1????init????????????????init進程???????????????用戶進程????????永遠不會終止,啟動系統,讀取系統初始化的文件。
2??? pagedaemon頁精靈進程??????????系統進程????????虛存系統的請頁操作
除了進程ID,每一個進程另一些其它的標識符。下列函數返回這些標識符:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);????????????????????? //返回值:調用進程的進程ID
pid_t getppid(void);????????? //返回值:調用進程的父進程ID
uid_t getuid(void);????????????????????? //返回值:調用進程的實際用戶ID
uid_t geteuid(void);?????????????????? //返回值:調用進程的有效用戶ID
gid_t getgid(void);????????????????????? //返回值:調用進程的實際組ID
gid_t getegid(void);?????????????????? //返回值:調用進程的有效組ID
fork函數
#include <unistd.h>
pid_t fork(void);
一個現有進程能夠調用fork創建一個新進程。
返回值:子進程中返回0,父進程中返回子進程ID,出錯返回-1。例如以下
子進程是父進程的副本。比如:子進程獲得父進程數據空間、堆和棧的副本。父子進程不共享這些存儲空間部分。父子進程共享正文段。
因為fork之后常常歸屬exec,所以如今非常多實現并不運行一個父進程數據段、棧和堆的全然復制。作為你替代,使用了寫時復制(Copy-On-Write)技術。這些區域由父子進程共享,并且內核將他們的訪問權限改變為僅僅讀的。假設父子進程中的任一個試圖改動這些區域,則內核僅僅為改動區域的那塊內存制作一個副本。
以下的程序演示了fork函數,從中能夠看出子進程對變量所作的改變并不去影響父進程中該變量的值。
#include <unistd.h> #include <stdio.h> int glob = 6; /* externalvariable in initialized data */ char buf[] = "a write to stdout\n"; int main(void) { int var; /* automatic variable on the stack */ pid_t pid; var = 88; if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1) perror("write error"); printf("before fork\n"); /* we don't flush stdout */ if ((pid = fork()) < 0) { perror("fork error"); }else if (pid == 0) { /* child */ glob++; /* modifyvariables */ var++; }else { sleep(2); /* parent*/ } printf("pid = %d, glob = %d, var = %d\n", getpid(), glob,var); exit(0); }
運行及輸出結果:
chen123@ubuntu:~/user/apue.2e$./a.out
awrite to stdout
beforefork
pid= 2755, glob = 7, var = 89
pid= 2754, glob = 6, var = 88
chen123@ubuntu:~/user/apue.2e$./a.out > temp.out
chen123@ubuntu:~/user/apue.2e$cat temp.out
awrite to stdout
beforefork
pid= 2758, glob = 7, var = 89
beforefork
pid= 2757, glob = 6, var = 88
一般來說fork之后父進程和子進程的運行順序是不確定的,這取決于內核的調度算法。在上面的程序中,父進程是自己休眠2秒鐘,以使子進程先運行。
程序中fork與I/O函數之間的關系:write是不帶緩沖 的,由于在fork之前調用write,所以其數據僅僅寫到標準輸出一次。標準I/O是緩沖的,假設標準輸出到終端設備,則它是行緩沖,否則它是全緩沖。當以交互方式執行該程序時,僅僅得到printf輸出的行一次,由于標準輸出到終端緩沖區由換行符沖洗。但將標準輸出重定向到一個文件時,由于緩沖區是全緩沖,遇到換行符不輸出,當調用fork時,其printf的數據仍然在緩沖區中,該數據將被拷貝到子進程中,該緩沖區也被拷貝到子進程中。于是父子進程的都有了帶改行內容的標準I/O緩沖區,所以每一個進程終止時,會沖洗其緩沖區中的數據,得到第一個printf輸出兩次。
文件共享
fork的一個特性是父進程的全部打開文件描寫敘述符都被拷貝到子進程中。父子進程的每一個同樣的打開描寫敘述符共享一個文件表項。如果一個進程有三個不同的打開文件,在從fork返回時,我們有例如以下所看到的結構:
在fork之后處理的文件描寫敘述符有兩種常見的情況:
1.??????父進程等待子進程完畢。在這樣的情況下,父進程無需對其描寫敘述符做不論什么處理。當子進程終止后,子進程對文件偏移量的改動已運行的更新。
2.?????? 父子進程各自運行不同的程序段。這樣的情況下,在fork之后,父子進程各自關閉他們不須要使用的文件描寫敘述符,這樣就不會干擾對方使用文件描寫敘述符。這樣的方法在網絡服務進程中常常使用。
父子進程之間的差別:
1.??????fork的返回值
2.??????進程ID不同
3.??????具有不同的父進程ID
4.??????子進程的tms_utime、tms_stime、tms_cutime及tms_ustime均被設置為0
5.??????父進程設置的文件鎖不會被子進程繼承
6.??????子進程的未處理鬧鐘被清除
7.??????子進程的未處理信號集被設置為空集
?
fork有以下兩種使用方法:
1.??????一個父進程希望復制自己,使父子進程同一時候運行不用的代碼段。比如,父進程等待client請求,生成子進程來處理請求。
2.??????一個進程要運行一個不同的程序。比如子進程從fork返回后,調用exec函數。
?
fork調用失敗的原因:
1.??????系統中有太多的進程
2.??????實際用戶的進程數超過了限制
vfork函數
vfork函數的調用序列和返回值與fork同樣,但兩者的語義不同。
vfork用于創建一個新進程,而該新進程的目的是exec一個新程序。vfork與fork都創建一個子進程,但它不將父進程的地址空間拷貝到子進程中,由于子進程會馬上調用exec,于是不會存訪問該地址空間。相反,在子進程調用exec或exit之前,它在父進程的空間中執行,也就是說會更改父進程的數據段、棧和堆。vfork和fork還有一差別在于:vfork保證子進程先執行,在它調用exec之后父進程才可能被調度執行。
以下是vfork的使用程序:
#include"unistd.h" #include"stdio.h" int glob = 6; /* external variable in initialized data*/ int main(void) { int var; /* automatic variableon the stack */ pid_t pid; var = 88; printf("before vfork\n"); /* we don't flush stdio */ if ((pid = vfork()) < 0) { perror("vfork error"); } else if (pid == 0) { /* child */ glob++; /* modify parent's variables*/ var++; _exit(0); /* child terminates */ } /* * Parent continues here. */ printf("pid = %d, glob = %d, var =%d\n", getpid(), glob, var); exit(0); }
運行及輸出結果例如以下所看到的:
chen123@ubuntu:~/user/apue.2e$./a.out
before vfork
pid = 2984, glob= 7, var = 89
可見子進程直接改變了父進程的變量值,由于子進程在父進程的地址空間中執行。
這里子進程調用_exit是由于_exit并不運行標準I/O緩沖的沖洗操作。假設調用exit,該程序結果不確定,依賴于標準I/O庫的實現。由于exit有可能關閉標準I/O流,那么會使父進程不產生不論什么輸出。
exit函數
進程有5中正常終止方式,3中異常終止方式。(見上一篇文章)。
對于隨意一種終止情形,我們都希望終止進程可以統治父進程它是怎樣終止的。對于三個終止函數(exit、_exit和_Exit),實現這一點的方法是,將其退出狀態作為參數傳送給函數。在異常終止情況下,內核產生一個指示其異常終止原因的終止狀態。在隨意一種情況下,該終止狀態的父進程都能使用wait或waitpid函數取得其終止狀態。
在調用_exit時,內核將進程的退出狀態轉換成終止狀態。
???????? 對于父進程已經終止的全部進程,他們的父進程都改變為init進程。我們稱這些進程有Init領養。一個init的子進程(包含領養進程)終止時,init會調用一個wait函數取得其終止狀態。
???????? 對于一個已經終止、但其父進程尚未對其進行善后處理(獲取終止子進程的有關信息,釋放它仍占有的資源)的進程被稱為僵尸進程。子進程終止時,盡管不在執行,但它仍然存在與系統中,進程表中代表子進程的表項不會立馬被釋放,由于它的退出碼還須要保存在進程表項中以備父進程今后的wait調用使用,也就是說終止子進程與父進程之間的關聯還會保持,直到父進程也正常的終止或父進程調用wait才告結束。
wait和waitpid函數
當一個進程正?;虍惓=K止時,內核就向其父進程發送一個SIGCHLD信號。由于子進程終止是一個異步事件,所以發生這樣的信號也是內核向父進程發的異步通知。父進程能夠選擇忽略該信號,或者提供一個該信號發生時即被調用運行的函數。對于這樣的信號的系統默認動作是忽略它。如今須要知道的是調用wait或waitpid的進程可能會發生什么情況:
1.假設其全部子進程都還在執行,則堵塞
2.假設一個子進程已終止,正等待父進程獲取其終止狀態,則取得該子進程的終止狀態馬上返回。
3.假設它沒有不論什么子進程,則馬上出錯返回。
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
pid_twaitpid(pid_t pid, int *status, int options);
假設進程因為接收到SIGCHLD而調用wait,則可期望wait會馬上返回。但假設在隨意時刻調用wait,則進程可能堵塞。
在一個子進程 終止前,wait使其調用者堵塞,而waitpid有一個選項,可使調用者不堵塞。
這兩個函數的參數statloc是一個整形指針。假設statloc不是一個空指針,則終止進程的終止狀態就存放在它所指的單元內。假設不關心終止狀態,則可將該參數設為空指針。
可用下面宏來檢查wait和waitpid所返回的終止狀態:
WIFEXITED(status)?????????? 若為正常終止子進程返回的狀態,則為真。
WEXITSTATUS(status)????? 若WIFEXITED非零,返回子進程退出碼。
WIFSIGNAKED(status)???? 若為子進程異常終止返回狀態(收到一個未捕捉的信號),則為真
WTERMSIG(status)????????? 若WIFSIGNAKED非零,則返回一個信號編號
WIFSTOPPED????????????????????? 若為子進程意外終止,則為真
???????? WSTOPSIG???????????????? ???????? 若WIFSTOPPED非零,返回一個信號編號
waiptpid提供了wait沒有提供的三個功能:
1.??????waitpid可等待一個特定的進程
2.??????waitpid提供了一個wait的非堵塞版本號
3.??????waitpid支持作業控制
?
?
?
?
?
?
?
?
?
?
?
?
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
