一、進(jìn)程等待的概念
?進(jìn)程等待通常是指:父進(jìn)程通過(guò)wait()/waitpid()的方式,讓父進(jìn)程對(duì)子進(jìn)程進(jìn)行資源回收的等待過(guò)程??!
二、進(jìn)程等待存在的意義
?進(jìn)程等待通常是為了解決以下兩種情況:
- 解決子進(jìn)程僵尸所帶來(lái)的內(nèi)存泄漏問(wèn)題,對(duì)僵尸子進(jìn)程進(jìn)行資源回收! 原因在于當(dāng)子進(jìn)程僵尸后,便“刀槍不入”了。即使是操作系統(tǒng)也沒(méi)法對(duì)僵尸進(jìn)程進(jìn)行資源回收,進(jìn)而導(dǎo)致內(nèi)存泄漏問(wèn)題。
- 讓父進(jìn)程獲得子進(jìn)程運(yùn)行結(jié)果(代碼運(yùn)行正常結(jié)果正確、代碼運(yùn)行正常結(jié)果錯(cuò)誤、代碼異常)。父進(jìn)程創(chuàng)建子進(jìn)程,通常是希望子進(jìn)程幫父進(jìn)程執(zhí)行某些任務(wù)。但子進(jìn)程任務(wù)執(zhí)行的如何,父進(jìn)程需要得到反饋。此時(shí)父進(jìn)程可以通過(guò)進(jìn)程等待的方式來(lái)獲取子進(jìn)程的退出信息(退出碼和退出信號(hào))。
三、如何進(jìn)行進(jìn)程等待
下面依次介紹進(jìn)程等待所需調(diào)用的接口:wait()/waitpid()。
3.1 wait()是實(shí)現(xiàn)進(jìn)程等待
1、wait()原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
- 返回值:如果成功返回被等待進(jìn)程的pid,否則返回-1.
- 參數(shù):ststus為輸出型參數(shù),獲取子進(jìn)程的退出信息,由操作系統(tǒng)自動(dòng)填充。(后續(xù)會(huì)單獨(dú)詳細(xì)介紹,這里我們暫且不關(guān)心該參數(shù),設(shè)為NULL)
-
wait()
用于等待任意進(jìn)程,而waitpid
則可以等待任意進(jìn)程!!
2. 驗(yàn)證wait()能回收僵尸子進(jìn)程的空間
?下面這樣一段代碼:fork()創(chuàng)建子進(jìn)程,讓子進(jìn)程運(yùn)行約5秒后退出但父進(jìn)程不退出。此時(shí)子進(jìn)程變?yōu)榻┦M(jìn)程,進(jìn)程狀態(tài)為Z。此時(shí)調(diào)用父進(jìn)程調(diào)用wait()接口回收僵尸子進(jìn)程的資源空間。
【源代碼】:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void worker()
{
int cnt = 5;
while(cnt)
{
printf("I am child process, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);
sleep(1);
}
}
int main()
{
pid_t id = fork();
if(id == 0)
{
//child
worker();
exit(0); //子進(jìn)程執(zhí)行完worker()后直接退出,變成僵尸狀態(tài)
}
else{
//parent
sleep(10);
pid_t rid = wait(NULL);//對(duì)子進(jìn)程進(jìn)行回收
if(rid == id)
{
printf("child process being recyceled sucess!, pid:%d, rid:%d\n", getpid(), rid);
}
sleep(3);
}
return 0;
}
【運(yùn)行結(jié)果】:
?我們觀察左邊監(jiān)視腳本發(fā)現(xiàn),子進(jìn)程在執(zhí)行5次代碼后退出,進(jìn)程狀態(tài)變?yōu)?code>Z。一段時(shí)間后,父進(jìn)程調(diào)用wait()
函數(shù)對(duì)子進(jìn)程進(jìn)行回收,子進(jìn)程消失。即父進(jìn)程通過(guò)wait()實(shí)現(xiàn)了對(duì)子進(jìn)程的回收??!
tips:
- 父進(jìn)程調(diào)用
wait()
后,如果子進(jìn)程沒(méi)有退出,父進(jìn)程會(huì)在wait上發(fā)生進(jìn)程阻塞。直到子進(jìn)程僵尸,wait自動(dòng)回收后,返回被回收的子進(jìn)程pid。 - 對(duì)于多個(gè)進(jìn)程來(lái)說(shuō),誰(shuí)先被調(diào)度是未知的,由內(nèi)核調(diào)度算法決定。但可以肯定的是,父進(jìn)程一定是最后退出的!
3.2 waitpid()實(shí)現(xiàn)進(jìn)程等待
1、系統(tǒng)調(diào)用接口waitpid()原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
- 參數(shù)pid: 如果pid=-1,等待任意進(jìn)程,和wait效果一樣。如果pid>0,等待進(jìn)程ID和pid值相等的子進(jìn)程!
- 參數(shù)status:子進(jìn)程的退出信息。status為NULL,表示不關(guān)心子進(jìn)程的退出狀態(tài)信息,否則操作系統(tǒng)會(huì)將子進(jìn)程的退出碼和錯(cuò)誤碼相關(guān)信息寫入該參數(shù)中。(后續(xù)具體介紹其實(shí)現(xiàn)機(jī)制)
- 參數(shù)options: 為0表示阻塞等待。options除了0外,還可以被設(shè)置為
WNOHANG
,此時(shí)表示父進(jìn)程以非阻塞方式進(jìn)行等待(非阻塞 + 輪詢方案)。 - 返回值:當(dāng)正常退出時(shí),
waitpid
返回收集到的子進(jìn)程ID;如果進(jìn)程異常,返回-1,此時(shí)errno會(huì)被設(shè)置為對(duì)于的錯(cuò)誤碼;如果進(jìn)程采用非阻塞輪詢方案,即將options
設(shè)置為WNOHANG
,如果子進(jìn)程waitpid
收集到的子進(jìn)程沒(méi)有退出,此時(shí)返回0??!
四、獲取子進(jìn)程status實(shí)現(xiàn)機(jī)制
?在wait()/waitpid()中,均存在參數(shù)status
,該參數(shù)是一個(gè)輸出型參數(shù),由操作系統(tǒng)自動(dòng)填充。如果該參數(shù)被設(shè)為NULL,表示不關(guān)心子進(jìn)程的退出信息;否則OS會(huì)通過(guò)status的值,來(lái)將子進(jìn)程相關(guān)退出信息返回給父進(jìn)程?。?/p>
status如何保存相關(guān)信息?
?status是int類型,32bit。這里我們僅研究低16位??!
?其中status的最低7位保存子進(jìn)程的退出信號(hào)(exit signal);第8位表示的是core dump標(biāo)志
;9~16位表示的是進(jìn)程的退出碼(exit code)
status中保存信息驗(yàn)證
?下面我們來(lái)做實(shí)驗(yàn):我們通過(guò)fork()創(chuàng)建出子進(jìn)程,然后讓子進(jìn)程運(yùn)行約3秒;此時(shí)父進(jìn)程通過(guò)waitpid
以阻塞方式對(duì)子進(jìn)程進(jìn)行等待回收。然后通過(guò)位運(yùn)算對(duì)status
進(jìn)行處理,獲取status
中的子進(jìn)程退出碼和退出信號(hào)。
【源代碼】:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int status = 0;
pid_t id = fork();
if(id == 0)
{
int cnt = 3;
while(cnt)
{
printf("I am child, cnt:%d\n", cnt--);
sleep(1);
}
exit(3);
}
else if(id > 0)
{
pid_t rid = waitpid(id, &status, 0);//以阻塞方式等待
printf("I am parent, pid:%d, rid:%d, status:%d, exit code:%d, signal:%d\n", getpid(), rid, status, (status>>8)&0xFF, status&0x7F);
}
return 0;
}
【運(yùn)行結(jié)果】:
?我們發(fā)現(xiàn)status變量中確實(shí)保存著子進(jìn)程的退出碼和退出信號(hào)等相關(guān)信息。
在系統(tǒng)中提供了一些宏函數(shù),用于直接獲取進(jìn)程的相關(guān)退出信息,具體如下:
- WIFEXITED(status): 若為正常終止子進(jìn)程返回的狀態(tài),則為真。(查看進(jìn)程是否是正常退出)
- WEXITSTATUS(status): 若WIFEXITED非零,提取子進(jìn)程退出碼。(查看進(jìn)程的退出碼)
父進(jìn)程如何得知子進(jìn)程的退出信息(底層執(zhí)行流程)
?在子進(jìn)程pcb中存在如下幾個(gè)變量,分別用于保存進(jìn)程的狀態(tài)、退出碼、退出信號(hào):(Linux為例)
strucr task_struct{
int exit_state; //退出狀態(tài)
int exit_code; //退出碼
int exit_signal;//退出信號(hào)
}
?當(dāng)子進(jìn)程退出時(shí),操作系統(tǒng)會(huì)將子進(jìn)程的退出碼和退出信號(hào)保存到子進(jìn)程PCB的exit_code
變量和exit_signal
變量中。
?而父進(jìn)程通過(guò)waitpid/wait
等待子進(jìn)程時(shí),OS會(huì)將子進(jìn)程PCB中的退出碼和退出信號(hào)通過(guò)組合放入status變量中,并將子進(jìn)程的狀態(tài)從Z
改成S
?。?br> ?此時(shí),父進(jìn)程便可通過(guò)status來(lái)獲取子進(jìn)程退出信息,子進(jìn)程可以被操作系統(tǒng)回收。
五、阻塞等待和非阻塞等待
?前面我們介紹waitpis
接口時(shí)提到過(guò),options參數(shù)設(shè)為0,表示父進(jìn)程進(jìn)行的時(shí)阻塞等待;設(shè)為WNOHANG
表示父進(jìn)程以==(非阻塞方式),即非阻塞輪詢方式==進(jìn)行進(jìn)程等待。
?那兩種等待方式究竟是什么?有什么區(qū)別呢?
5.1 阻塞等待
?父進(jìn)程以阻塞方式進(jìn)行等待和普通阻塞進(jìn)程一樣。
?當(dāng)父進(jìn)程調(diào)用waitpid
接口等待子進(jìn)程時(shí),如果此時(shí)子進(jìn)程沒(méi)有退出,操作系統(tǒng)會(huì)將父進(jìn)程設(shè)置為阻塞進(jìn)程,然后將父進(jìn)程的PCB鏈入到子進(jìn)程的等待隊(duì)列中。一旦子進(jìn)程退出,操作系統(tǒng)會(huì)將父進(jìn)程PCB重新加載到運(yùn)行隊(duì)列中等待調(diào)度!
5.2 非阻塞等待(非阻塞 + 輪詢方案)
? 非阻塞等待是指父進(jìn)程在等待子進(jìn)程時(shí)發(fā)現(xiàn)子進(jìn)程還未退出,此時(shí)父進(jìn)程和阻塞等待一樣一直在"原地等地子進(jìn)程運(yùn)行結(jié)束"。父進(jìn)程會(huì)執(zhí)行一些其他任務(wù),并每隔一段時(shí)間查看子進(jìn)程是否退出。一旦子進(jìn)程退出后,父進(jìn)程才會(huì)開(kāi)始執(zhí)行后續(xù)程序。
?非阻塞等待的好處就是讓父進(jìn)程在等待時(shí),可以做一些自己占據(jù)時(shí)間不多的任務(wù)??!
六、非阻塞輪詢方案示例演示
?下面我們通過(guò)fork
創(chuàng)建子進(jìn)程。然后讓子進(jìn)程做一些工作(打印輸出一些信息,整個(gè)過(guò)程約10s),此時(shí)父進(jìn)程通過(guò)waitpid
接口進(jìn)行非阻塞輪詢方案進(jìn)行等待。在等待過(guò)程中,我們讓父進(jìn)程做一些自己的“小任務(wù)”(這些小任務(wù),博主同樣采用輸出信息代替,各位可根據(jù)實(shí)際情況修改)
【源代碼】:
輪詢時(shí),父進(jìn)程執(zhí)行任務(wù):
?博主將任務(wù)簡(jiǎn)化為輸出一些信息,各位可自行更改任務(wù)
void download()
{
printf("This download task is running!\n");
}
void writelog()
{
printf("This write log task is running!\n");
}
void printinfo()
{
printf("This print info task is running!\n");
}
大致框架,父進(jìn)程和子進(jìn)程執(zhí)行任務(wù):
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define TASK_NUM 5
void worker(int cnt)
{
printf("I am child, pid:%d, cnt:%d\n", getpid(), cnt);
}
int main()
{
//下面5行模擬父進(jìn)程的任務(wù)被加載好了,方便父進(jìn)程等待子進(jìn)程時(shí)被執(zhí)行
task tasks[TASK_NUM];
Init(tasks, TASK_NUM);//初始化
taskadd(tasks, download); //加載任務(wù)
taskadd(tasks, writelog);
taskadd(tasks, printinfo);
pid_t id = fork();
if(id == 0)
{//child
int cnt = 5;
while(cnt)
{
worker(cnt--);
sleep(1);
}
exit(3);
}
//parent
while(1)
{
int status = 0;
pid_t rid = waitpid(id, &status, WNOHANG);
if(rid > 0)
{//子進(jìn)程正常退出
printf("wait sucess!\n, pid:%d, rid:%d, exit code:%d, signal:%d\n",getpid(), rid, (status>>8)&0xFF, status&0x7F);
break;
}
else if(rid == 0)
{//父進(jìn)程等待成功,但子進(jìn)程沒(méi)有退出。父進(jìn)程開(kāi)始做自己的小任務(wù),一段時(shí)間后在查詢子進(jìn)程是否退出
printf("------------------------------------------------\n");
printf("wait sucess, but chils alive, wait again!\n");
executeTask(tasks, 3);
printf("------------------------------------------------\n");
}
else
{//子進(jìn)程退出異常
printf("wait failed!\n");
break;
}
sleep(1);
}
return 0;
}
父進(jìn)程加載任務(wù)代碼:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-853195.html
void Init(task tasks[], int num)
{
for(int i = 0; i < num; i++)
{
tasks[i] = NULL;
}
}
int taskadd(task tasks[], task t)
{
for(int i = 0; i < TASK_NUM; i++)
{
if(tasks[i] == NULL)
{
tasks[i] = t;
return 1;//增加任務(wù)成功
}
}
return 0;//增加任務(wù)失敗
}
void executeTask(task tasks[], int num)
{
for(int i = 0; i < num; i++)
{
tasks[i]();
}
}
運(yùn)行結(jié)果:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-853195.html
到了這里,關(guān)于Linux:進(jìn)程等待究竟是什么?如何解決子進(jìn)程僵尸所帶來(lái)的內(nèi)存泄漏問(wèn)題?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!