柚子快報邀請碼778899分享:運維 【Linux】進程控制
??個人主頁:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343??系列專欄:https://blog.csdn.net/qinjh_/category_12625432.html
目錄
fork函數(shù)初識
fork 函數(shù)返回值?
寫時拷貝
?fork調(diào)用失敗的原因
進程終止?
?進程退出場景
?進程常見退出方法
?進程等待
進程等待必要性
進程等待的方法
wait方法
?waitpid方法
?獲取子進程status
非阻塞等待
前言
????? hello! 各位鐵子們大家好哇。
? ? ? ? ? ? ?今日更新了Linux的進程控制的內(nèi)容 ????? 歡迎大家關(guān)注?點贊?收藏??留言?
fork函數(shù)初識
在linux中fork函數(shù)時非常重要的函數(shù),它從已存在進程中創(chuàng)建一個新進程。新進程為子進程,而原進程為父進程。
#include
pid_t fork(void);
//返回值:子進程中返回0,父進程返回子進程id,出錯返回-1
進程調(diào)用fork,當(dāng)控制轉(zhuǎn)移到內(nèi)核中的fork代碼后,內(nèi)核做:
分配新的內(nèi)存塊和內(nèi)核數(shù)據(jù)結(jié)構(gòu)給子進程將父進程部分數(shù)據(jù)結(jié)構(gòu)內(nèi)容拷貝至子進程添加子進程到系統(tǒng)進程列表當(dāng)中 fork返回,開始調(diào)度器調(diào)度
當(dāng)一個進程調(diào)用fork之后,就有兩個二進制代碼相同的進程。而且它們都運行到相同的地方。但每個進程都將可以 開始它們自己的旅程。?
fork 函數(shù)返回值?
子進程返回0,父進程返回的是子進程的pid。?
為什么父進程返回的是子進程的pid?
為了讓父進程方便對子進程進行標(biāo)識,進而進行管理。?
寫時拷貝
通常,父子代碼共享,父子不寫入時,數(shù)據(jù)也是共享的,當(dāng)任意一方試圖寫入,便以寫時拷貝的方式各自一份副本。?(進程的獨立性)
?fork調(diào)用失敗的原因
系統(tǒng)中有太多的進程實際用戶的進程數(shù)超過了限制?
進程終止?
進程終止做的事:
釋放曾經(jīng)的代碼和數(shù)據(jù)所占據(jù)的空間釋放內(nèi)核數(shù)據(jù)結(jié)構(gòu)
內(nèi)核數(shù)據(jù)結(jié)構(gòu)中,PCB會被延期處理,因為有一種狀態(tài)是僵尸狀態(tài)。
#include
2 #include
3
4 int main()
5 {
6 printf("I am process, pid:%d, ppid:%d\n",getpid(),getppid());
7 sleep(2);
8 return 100;
9 }
任何命令行啟動的進程,它的父進程都是bash,所以ppid都一樣。?
echo是內(nèi)建命令,打印的都是bash內(nèi)部的變量數(shù)據(jù)。?是一個變量名。
echo $?表示的是父進程獲取到的,最近一個子進程退出的退出碼。
main函數(shù)的返回值叫做進程的退出碼。
退出碼:
為0,標(biāo)識成功不為0,表示失敗
第一個echo $?返回./myprocess 的退出碼,第二個echo $?返回上一個echo $?的退出碼
雖然echo $?沒有創(chuàng)建子進程,但它是由父進程執(zhí)行的,所以他也會影響?的值。?
#include
2 #include
3 #include
4
5 int main()
6 {
7 for(int errcode=0;errcode<=255;errcode++)
8 {
9 printf("%d:%s\n",errcode,strerror(errcode));
10 }
11 printf("I am process, pid:%d, ppid:%d\n",getpid(),getppid());
12 sleep(2);
13 return 0;
14 }
退出碼不為0表示失敗。不同的非0值,一方面表示失敗,另一方面表示失敗的原因。
strerror函數(shù)會將錯誤碼轉(zhuǎn)成對應(yīng)的錯誤描述,如下圖;
父進程為什么要得到子進程的退出碼呢?
因為要知道子進程的退出情況。(成功還是失敗,失敗的原因是什么),然后展現(xiàn)給用戶看。
退出碼可以使用系統(tǒng)默認的,也可以自定義。?
?進程退出場景
?進程終止的3中情況:
代碼跑完,結(jié)果正確代碼跑完,結(jié)果不正確代碼異常終止
代碼跑完,結(jié)果不正確的原因可以通過退出碼確定。
一旦出現(xiàn)異常,退出碼就沒有意義了。
進程出異常,本質(zhì)是因為進程收到了OS發(fā)給進程的信號。
int main()
6 {
7 int *p=NULL;
8 while(1)
9 {
10 printf("I am a process,pid:%d\n",getpid());
11 sleep(1);
12 *p=100;//野指針
13 }
19 sleep(2);
20 return 0;
21 }
當(dāng)外面運行上面代碼后,會報段錯誤。?OS就會提前終止進程。
?我們把代碼里的野指針注釋掉,此時代碼正常運行,一直循環(huán)。此時我們給該進程發(fā)11號信號,該進程即使沒有錯誤,收到信號后,也會進行對應(yīng)的報錯。 所以說進程出異常,本質(zhì)是因為進程收到了OS發(fā)給進程的信號。
所以如果進程異常了,我們可以通過退出信號,就可以判斷進程為什么異常了,此時的退出碼是無意義的。
在用戶層面上,要確定進程是什么情況:
先確認是否異常如果不是異常,就一定是代碼跑完了,看退出碼即可。
衡量一個進程退出,只需要兩個數(shù)字:退出碼和退出信號。
?進程的PCB里面有退出信號和退出碼,當(dāng)進程退出時,會釋放代碼和數(shù)據(jù),但是PCB會保存一段時間,該進程變成Z(僵尸)狀態(tài)。父進程就可以從子進程的PCB中拿到退出信息。
?進程常見退出方法
正常終止:
main函數(shù)return,表示進程終止(非main函數(shù)的return,都只是表示函數(shù)結(jié)束)調(diào)用exit函數(shù)? 注意:在代碼的任意位置調(diào)用exit,都表示進程退出_exit (系統(tǒng)調(diào)用)
下面是exit的使用舉例:?
_exit和exit在使用上沒什么區(qū)別,只有一個細微的差別,如下例子:?
?上圖是帶\n的。結(jié)果打印并且換行了。
?
?上面是不帶\n的。結(jié)果打印了但沒換行。
?
上面是不帶\n的_exit的使用。結(jié)果什么也沒打印。?
?結(jié)論:exit會在進程退出的時候,沖刷緩沖區(qū),_exit不會。
exit在底層是調(diào)用_exit的。?由上面的結(jié)論可得,緩沖區(qū)在_exit之上,不然_exit也會沖刷緩沖區(qū)。
?進程等待
進程等待必要性
子進程退出,父進程如果不管不顧,就可能造成‘僵尸進程’的問題,進而造成內(nèi)存泄漏。進程一旦變成僵尸狀態(tài),那就刀槍不入,“殺人不眨眼”的kill -9 也無能為力,因為誰也沒有辦法 殺死一個已經(jīng)死去的進程。父進程派給子進程的任務(wù)完成的如何,我們需要知道。如,子進程運行完成,結(jié)果對還是不對, 或者是否正常退出。父進程通過進程等待的方式,回收子進程資源,獲取子進程退出信息
進程等待的方法
wait方法
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7
8 void ChildRun()
9 {
10 int cnt = 5;
11 while(cnt)
12 {
13 printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), get ppid(), cnt);
14 sleep(1);
15 cnt--;
16 }
17 }
18
19 int main()
20 {
21 printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());
22
23 pid_t id = fork();
24 if(id == 0)
25 {
26 // child
27 ChildRun();
28 printf("child quit ...\n");
29 exit(0);
30 }
31 // fahter
32 pid_t rid = wait(NULL);
33 if(rid > 0)
34 {
35 printf("wait success, rid: %d\n", rid);
36 }
37 }
作用:等待任何一個子進程退出
返回值:等待成功時,返回子進程的pid。失敗返回-1。?
參數(shù): 輸出型參數(shù),獲取子進程退出狀態(tài),不關(guān)心則可以設(shè)置成為NULL
運行上面的代碼,結(jié)果如下圖:
?上面代碼if后面不需要else就表示是父進程的代碼了。因為if里面即子進程里面用exit退出了,所以后面的都是父進程的。
?下面做出修改:
運行結(jié)果:
?修改后的代碼先讓父進程休眠十秒。子進程運行五秒后退出,此時由于父進程還在休眠無法回收,所以子進程就變成Z狀態(tài),再過五秒后,子進程就被父進程回收了。
?如果我們把sleep(10)注釋掉,此時父進程開始就馬上進入等待,等待期間父進程不會被調(diào)度。如果子進程沒有退出,父進程其實一直在阻塞等待。
?waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
當(dāng)正常返回的時候waitpid返回收集到的子進程的進程ID;如果設(shè)置了選項WNOHANG,而調(diào)用中waitpid發(fā)現(xiàn)沒有已退出的子進程可收集,則返回0;如果調(diào)用中出錯,則返回-1,這時errno會被設(shè)置成相應(yīng)的值以指示錯誤所在;
參數(shù):
pid:
Pid=-1,等待任一個子進程。與wait等效。Pid>0.等待其進程ID與pid相等的子進程。??status:
WIFEXITED(status): 若為正常終止子進程返回的狀態(tài),則為真。(查看進程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子進程退出碼。(查看進程的退出碼)options:
WNOHANG: 若pid指定的子進程沒有結(jié)束,則waitpid()函數(shù)返回0,不予以等待。若正常結(jié)束,則返回該子進程的ID。?
waitpid有三個參數(shù),當(dāng)pid,即第一個參數(shù)為-1時,等待任意一個子進程,與wait等效。
當(dāng)?shù)谝粋€參數(shù)pid>0時,就會等待其進程ID與pid相等的子進程 。
如下圖,此時等待上方父進程的子進程。
?
等待失敗例子:
當(dāng)我們把pid給一個錯誤的,此時進程就是等待失敗。?
?獲取子進程status
?第二個參數(shù)status代表的是子進程的退出信息。
退出信息=退出碼+退出信號
wait和waitpid,都有一個status參數(shù),該參數(shù)是一個輸出型參數(shù)。如果傳遞NULL,表示不關(guān)心子進程的退出狀態(tài)信息。否則,操作系統(tǒng)會根據(jù)該參數(shù),將子進程的退出信息反饋給父進程。status不能簡單的當(dāng)作整形來看待,可以當(dāng)作位圖來看待(只研究status低16比特 位):
?最低7位表示終止信號,9到16位表示退出碼。所以退出碼的范圍是0~255。退出信號的范圍是0~125。這兩個范圍足以表示退出碼的退出信號的情況了。
19 int main()
20 {
21 printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());
22
23 pid_t id = fork();
24 if(id == 0)
25 {
26 // child
27 ChildRun();
28 printf("child quit ...\n");
29 exit(1);
30 }
31 sleep(7);
32 // fahter
33 // pid_t rid = wait(NULL);
34 int status=0;
35 pid_t rid=waitpid(id,&status,0);
36 if(rid > 0)
37 {
38 printf("wait success, rid: %d\n", rid);
39 }
40 else
41 {
42 printf("wait failed !\n");
43 }
44 sleep(3);
45 printf("father quit, status:%d, child quit code : %d,child quit signal: % d\n",status,(status>>8)&0xFF,status&0x7F);
46 }
上面是通過status退出信息來獲取退出碼和退出信號的代碼。
status右移8位,然后與0xFF即二進制的8個1進行按位與,獲取退出碼。
status按位與0x7F即二進制的7個1來獲取退出信號。
結(jié)果表明,前面所說都是正確的。
?實際上我們不使用位操作符處理status,而是使用兩個宏,WIFEXITED和WEXITSTATUS。
WIFEXITED(status): 若為正常終止子進程返回的狀態(tài),則為真。(查看進程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子進程退出碼。(查看進程的退出碼)
非阻塞等待
?我們用的大部分接口都是阻塞等待接口,在阻塞等待時,父進程干不了別的事,一直在等待子進程退出。下面介紹非阻塞等待。
這里也需要用到一個宏:WNOHANG
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7
8 void ChildRun()
9 {
10 int cnt = 5;
11 while(cnt)
12 {
13 printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid (), cnt);
14 sleep(1);
15 cnt--;
16 }
17 }
18
19 int main()
20 {
21 printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());
22
23 pid_t id = fork();
24 if(id == 0)
25 {
26 // child
27 ChildRun();
28 printf("child quit ...\n");
29 exit(123);
30 }
31
32 //father
33 while(1)
34 {
35 int status=0;
36 pid_t rid=waitpid(id,&status,WNOHANG); //非阻塞等待
37 if(rid==0)
38 {
39 sleep(1);
40 printf("child is running, father check next time!\n");
41 //DoOtherThing();
42 }
43 else if(rid>0)
44 {
45 if(WIFEXITED(status))
46 {
47 printf("child quit success, child exit code:%d\n",WEXITSTATUS(status));
48 }
49 else
50 {
51 printf("child quit unnormal!\n");
52 }
53 break;
54 }
55 else
56 {
57 printf("waitpid failed!\n");
58 break;
59 }
60 }
使用WNOHANG的時候,需要使用循環(huán)結(jié)構(gòu)。因為WNOHANG只會查看一次子進程是否結(jié)束,使用循環(huán)結(jié)構(gòu)就可以到最后判斷子進程是什么情況了。即非阻塞等待的時候+循環(huán)=非阻塞輪詢。
在非阻塞等待時,父進程可以在每次查看子進程的間隙做其他事情。
pid_ t waitpid(pid_t pid, int *status, int options);
pid_t >0 :等待成功,子進程退出了,并且父進程回收成功
pid_t <0 :等待失敗了
pid_t =0 :檢測是成功的,只不過子進程還沒退出,需要下一次進行重復(fù)等待。
柚子快報邀請碼778899分享:運維 【Linux】進程控制
好文鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。