請人做網(wǎng)站得多少錢雅詩蘭黛網(wǎng)絡(luò)營銷策劃書
一、進(jìn)程創(chuàng)建:fork函數(shù)
我們在命令行中輸入man fork 即可得到fork函數(shù)的函數(shù)接口的函數(shù)的使用方法。
?我們可以看到,fork函數(shù)位于man手冊的第2部分,由于第2部分通常是用于描述系統(tǒng)調(diào)用和庫函數(shù),所以我們可以了解到fork函數(shù)實際是一個系統(tǒng)調(diào)用函數(shù)。
接下來,我們先了解一下什么是系統(tǒng)調(diào)用函數(shù)?
系統(tǒng)調(diào)用函數(shù)是操作系統(tǒng)提供給用戶程序或應(yīng)用程序的一組接口,通過這些接口,用戶程序可以請求操作系統(tǒng)執(zhí)行特定的操作,如文件操作、進(jìn)程管理、網(wǎng)絡(luò)通信等。系統(tǒng)調(diào)用函數(shù)允許用戶程序訪問操作系統(tǒng)的底層功能,以完成對硬件資源的管理和控制。
系統(tǒng)調(diào)用函數(shù)與一般的函數(shù)調(diào)用有所不同。一般的函數(shù)調(diào)用是在用戶程序內(nèi)部進(jìn)行的,而系統(tǒng)調(diào)用函數(shù)是用戶程序與操作系統(tǒng)之間的通信方式。當(dāng)用戶程序調(diào)用系統(tǒng)調(diào)用函數(shù)時,會觸發(fā)一個特殊的處理機(jī)制,將控制權(quán)轉(zhuǎn)移給操作系統(tǒng)內(nèi)核,執(zhí)行相應(yīng)的操作,然后將結(jié)果返回給用戶程序。
系統(tǒng)調(diào)用函數(shù)通常是由操作系統(tǒng)提供的庫函數(shù)封裝的,以便用戶程序更方便地調(diào)用。這些函數(shù)通常包含在標(biāo)準(zhǔn)庫中,例如在 C 語言中,可以通過?
unistd.h
?頭文件來訪問系統(tǒng)調(diào)用函數(shù)。常見的系統(tǒng)調(diào)用函數(shù)包括?
fork()
、exec()
、open()
、read()
、write()
?等,它們提供了對文件系統(tǒng)、進(jìn)程管理、內(nèi)存管理、網(wǎng)絡(luò)通信等底層功能的訪問。系統(tǒng)調(diào)用函數(shù)是編寫操作系統(tǒng)相關(guān)程序和系統(tǒng)編程的重要工具,也是操作系統(tǒng)與用戶程序之間的橋梁。
如果不理解,我們先記住加粗藍(lán)字描述的部分。?
在操作系統(tǒng)中,用戶程序處于用戶態(tài)(用戶層),而操作系統(tǒng)內(nèi)核處于內(nèi)核態(tài)(核心層)。用戶程序不能直接訪問系統(tǒng)的硬件資源或執(zhí)行特權(quán)指令,而是通過系統(tǒng)調(diào)用接口來請求操作系統(tǒng)執(zhí)行特定的任務(wù),包括對硬件資源的管理和控制。
通過系統(tǒng)調(diào)用接口,用戶程序可以向操作系統(tǒng)發(fā)出請求,比如讀寫文件、創(chuàng)建進(jìn)程、進(jìn)行網(wǎng)絡(luò)通信等。操作系統(tǒng)會根據(jù)請求執(zhí)行相應(yīng)的操作,然后將結(jié)果返回給用戶程序。這樣的設(shè)計有效地保護(hù)了系統(tǒng)的穩(wěn)定性和安全性,同時也提供了一種方便而有效的方式,讓用戶程序與系統(tǒng)進(jìn)行交互。
?fork函數(shù)詳解:
fork函數(shù)從已存在進(jìn)程中創(chuàng)建一個新進(jìn)程。新進(jìn)程為子進(jìn)程,而原進(jìn)程為父進(jìn)程。
接口:
#include <unistd.h> pid_t fork(void);
作用:
fork()?函數(shù)用于創(chuàng)建一個新的進(jìn)程,該進(jìn)程是調(diào)用進(jìn)程的副本。子進(jìn)程與父進(jìn)程幾乎完全相同,包括代碼段、數(shù)據(jù)段、堆棧等。在子進(jìn)程中,fork()?返回 0,而在父進(jìn)程中,它返回新創(chuàng)建子進(jìn)程的 PID(進(jìn)程標(biāo)識符)。返回值:
- 在父進(jìn)程中,fork()?返回新創(chuàng)建子進(jìn)程的 PID。
- 在子進(jìn)程中,fork()?返回 0。
- 如果?fork() 失敗,返回值為 -1,表示創(chuàng)建子進(jìn)程失敗。
進(jìn)程的執(zhí)行:
- 子進(jìn)程從?fork()?返回的地方【return】開始執(zhí)行,而父進(jìn)程則繼續(xù)執(zhí)行它的代碼。這意味著在?fork()?調(diào)用之后,父進(jìn)程和子進(jìn)程會并行執(zhí)行。
錯誤處理:
如果?fork()?失敗,返回值為 -1。失敗的原因可能是系統(tǒng)資源不足或者進(jìn)程數(shù)達(dá)到了限制。注意事項:
- 在?fork()?后,父子進(jìn)程共享文件描述符,這意味著在一個進(jìn)程中打開的文件在另一個進(jìn)程中也是打開的。如果不適當(dāng)?shù)靥幚?#xff0c;可能會導(dǎo)致意想不到的結(jié)果。
- 子進(jìn)程通常需要調(diào)用 exec?系列函數(shù)來加載新的程序,以便替換掉自己的內(nèi)存映像。否則,子進(jìn)程將繼承父進(jìn)程的內(nèi)存映像,可能會導(dǎo)致一些意外的行為。
?接下來我們來看一段程序:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{printf("父進(jìn)程開始運行!!!\n");pid_t id = fork();if(id == 0){printf("我是子進(jìn)程!!!\n");sleep(1);}else if(id > 0){printf("我是父進(jìn)程!!!\n");sleep(1);}else {perror("進(jìn)程創(chuàng)建失敗!!!\n");}return 0;
}
?這段代碼的結(jié)果是這樣的:
?接下來我們來詳細(xì)聊一下fork函數(shù)。相信大家都有這樣的疑問:
1、為什么fork()函數(shù)可以有兩個返回值,也就是函數(shù)會返回兩次,這和我們平時見到的函數(shù)不同。
當(dāng)進(jìn)程調(diào)用?
fork()
?函數(shù)時,控制會轉(zhuǎn)移到操作系統(tǒng)內(nèi)核中執(zhí)行?fork()
?函數(shù)的代碼。在內(nèi)核中,fork()
?函數(shù)主要完成以下操作:
創(chuàng)建新的進(jìn)程控制塊(Process Control Block,PCB):內(nèi)核會為新的子進(jìn)程分配一個唯一的進(jìn)程標(biāo)識符(PID),并在內(nèi)存中為其創(chuàng)建一個新的進(jìn)程控制塊(PCB)。這個 PCB 將包含子進(jìn)程的運行狀態(tài)、程序計數(shù)器、堆棧指針、文件描述符等信息。
復(fù)制父進(jìn)程的地址空間以創(chuàng)建自己的地址空間:在大多數(shù)情況下,
fork()
?函數(shù)會創(chuàng)建子進(jìn)程的完整副本,包括代碼段、數(shù)據(jù)段、堆棧等。這意味著子進(jìn)程將會獲得與父進(jìn)程幾乎完全相同的內(nèi)存映像。這一步通常通過 Copy-On-Write(寫時復(fù)制)技術(shù)來實現(xiàn),即在子進(jìn)程需要修改內(nèi)存時才會進(jìn)行實際的復(fù)制操作。將子進(jìn)程的狀態(tài)設(shè)置為就緒:一旦子進(jìn)程的地址空間準(zhǔn)備好,內(nèi)核將其狀態(tài)設(shè)置為就緒態(tài),以便在合適的時機(jī)可以被調(diào)度執(zhí)行。
返回不同的值:在內(nèi)核中,
fork()
?函數(shù)會返回兩次,一次是在父進(jìn)程的上下文中返回子進(jìn)程的 PID,另一次是在子進(jìn)程的上下文中返回 0。這樣,父進(jìn)程和子進(jìn)程可以根據(jù)返回值來執(zhí)行不同的代碼路徑。
??
?在fork函數(shù)內(nèi)部,在執(zhí)行 return pid 之前,子進(jìn)程就已經(jīng)創(chuàng)建完成,所以 return pid 實際也是父子進(jìn)程的共享代碼部分,所以父進(jìn)程會執(zhí)行一次,返回子進(jìn)程的pid;而子進(jìn)程也會執(zhí)行一次 return pid?返回進(jìn)程是否創(chuàng)建完成的信息。
?2、為什么父進(jìn)程接收子進(jìn)程的PID,而子進(jìn)程返回0或-1?
父進(jìn)程接收子進(jìn)程的PID:父進(jìn)程在調(diào)用
fork()
函數(shù)后,會得到子進(jìn)程的PID作為返回值。通過這個PID,父進(jìn)程可以對子進(jìn)程進(jìn)行跟蹤、管理和通信。例如,父進(jìn)程可能會使用子進(jìn)程的PID來等待子進(jìn)程的結(jié)束狀態(tài)(通過waitpid()
函數(shù)),或者向子進(jìn)程發(fā)送信號(通過kill()
函數(shù))等。子進(jìn)程返回0或-1:子進(jìn)程在
fork()
函數(shù)返回時,需要確定自己是父進(jìn)程還是子進(jìn)程。因此,子進(jìn)程通常會檢查fork()
的返回值來確定自己的身份。具體來說:
- 如果
fork()
返回0,則表示當(dāng)前進(jìn)程是子進(jìn)程。子進(jìn)程可以通過這個返回值來區(qū)別自己和父進(jìn)程,并且通常會在這個基礎(chǔ)上執(zhí)行特定的任務(wù)或代碼段。- 如果
fork()
返回-1,則表示進(jìn)程創(chuàng)建失敗。通常這種情況會發(fā)生在系統(tǒng)資源不足或者其他錯誤發(fā)生時。子進(jìn)程在這種情況下會立即退出或者采取相應(yīng)的錯誤處理措施。
由于fork()函數(shù)具有以上兩個特性,我們可以采取 if---else?語句對父子進(jìn)程進(jìn)行分流,這樣就可以讓父子進(jìn)程去做不同的事情,這也是我們后續(xù)進(jìn)行進(jìn)程替換的基礎(chǔ)。
3、父子進(jìn)程哪個先運行??
在一般情況下,無法確定父進(jìn)程和子進(jìn)程哪一個先運行。這取決于操作系統(tǒng)的調(diào)度策略以及各種競爭條件的發(fā)生情況。
通過上面的知識,我們了解到在fork函數(shù)內(nèi)部子進(jìn)程創(chuàng)建完成之后,父子進(jìn)程共享進(jìn)程創(chuàng)建完成之后的代碼,包括fork函數(shù)內(nèi)部的代碼。
二、進(jìn)程終止
進(jìn)程退出的場景:
正常退出:進(jìn)程完成了它的任務(wù),并通過調(diào)用?
exit()、_exit()
函數(shù)或者在?main()
?函數(shù)中使用?return
?語句返回。在這種情況下,進(jìn)程會執(zhí)行清理操作,并返回退出狀態(tài)給操作系統(tǒng)。異常退出:進(jìn)程在執(zhí)行過程中遇到了錯誤或異常情況,無法繼續(xù)執(zhí)行下去。這可能是因為代碼中的錯誤、系統(tǒng)資源不足、權(quán)限不足等原因?qū)е碌?。在這種情況下,進(jìn)程可以調(diào)用?
exit()
?函數(shù)或者?_exit()
?函數(shù)來立即終止程序執(zhí)行,并返回退出狀態(tài)給操作系統(tǒng)。收到信號:進(jìn)程可以收到來自操作系統(tǒng)或其他進(jìn)程發(fā)送的信號,例如 使用 kill 命令搭配SIGKILL 或 SIGTERM 等。
父進(jìn)程終止:如果一個子進(jìn)程的父進(jìn)程終止了,而沒有等待子進(jìn)程完成(通過調(diào)用?
wait()
?或?waitpid()
),則子進(jìn)程可能會成為孤兒進(jìn)程。在這種情況下,操作系統(tǒng)通常會將孤兒進(jìn)程的父進(jìn)程設(shè)置為 init 進(jìn)程(進(jìn)程號為 1),并由 init 進(jìn)程接管孤兒進(jìn)程的管理。孤兒進(jìn)程的退出方式與其他進(jìn)程相同。系統(tǒng)關(guān)閉:當(dāng)系統(tǒng)關(guān)閉或重啟時,所有正在運行的進(jìn)程都會被終止。操作系統(tǒng)通常會向所有進(jìn)程發(fā)送信號,以便它們有機(jī)會在關(guān)閉之前執(zhí)行清理操作。
其他。。。
?進(jìn)程退出方法:
調(diào)用 exit()、_exit() 函數(shù)
在main()函數(shù)中使用return語句返回
Ctrl + c 組合鍵 或者 使用 kill 命令搭配終止信號
?exit()函數(shù)、_exit()函數(shù)詳解
exit()
?函數(shù)和?_exit()
?函數(shù)都是用于終止程序的執(zhí)行
exit()
?函數(shù):
exit()
?函數(shù)是標(biāo)準(zhǔn) C 庫中的函數(shù),用于正常終止程序的執(zhí)行,并返回退出狀態(tài)給操作系統(tǒng)。- 調(diào)用?
exit()
?函數(shù)會執(zhí)行以下步驟:
- 執(zhí)行所有注冊的退出處理程序(使用?
atexit()
?注冊的函數(shù))。- 關(guān)閉所有打開的流(文件)。
- 刷新所有的緩沖區(qū)。
- 返回退出狀態(tài)給操作系統(tǒng)。
exit()
?函數(shù)的原型在?<stdlib.h>
?頭文件中聲明,其函數(shù)原型如下:#include <stdlib.h> void exit(int status);
status
?參數(shù)是傳遞給操作系統(tǒng)的退出狀態(tài),通常用來指示程序的結(jié)束狀態(tài),一般約定 0 表示成功,非零值表示失敗或其他特定狀態(tài)。exit()
?函數(shù)在正常終止程序時應(yīng)該被調(diào)用,它會執(zhí)行標(biāo)準(zhǔn)的程序清理操作,并返回狀態(tài)給操作系統(tǒng)。
_exit()
?函數(shù):
_exit()
?函數(shù)是系統(tǒng)調(diào)用,用于立即終止程序的執(zhí)行,不執(zhí)行標(biāo)準(zhǔn)的程序清理操作。- 調(diào)用?
_exit()
?函數(shù)會立即終止程序的執(zhí)行,并且不會執(zhí)行以下操作:
- 不執(zhí)行注冊的退出處理程序。
- 不關(guān)閉打開的流(文件)。
- 不刷新緩沖區(qū)。
_exit()
?函數(shù)的原型在?<unistd.h>
?頭文件中聲明,其函數(shù)原型如下:#include <unistd.h> void _exit(int status);
status
?參數(shù)同樣是傳遞給操作系統(tǒng)的退出狀態(tài)。_exit()
?函數(shù)通常在需要立即終止程序執(zhí)行,并且不需要執(zhí)行標(biāo)準(zhǔn)清理操作時使用。比如,在子進(jìn)程中的錯誤處理中可以使用?_exit()
?來避免執(zhí)行父進(jìn)程中的清理操作。主要區(qū)別總結(jié):
exit()
?是標(biāo)準(zhǔn) C 庫函數(shù),執(zhí)行標(biāo)準(zhǔn)的程序清理操作后返回退出狀態(tài)給操作系統(tǒng)。_exit()
?是系統(tǒng)調(diào)用,立即終止程序的執(zhí)行,不執(zhí)行標(biāo)準(zhǔn)的程序清理操作。在使用時,通常情況下,應(yīng)該優(yōu)先使用?
exit()
?函數(shù)來正常終止程序,并執(zhí)行必要的清理操作。
exit最后也會調(diào)用exit, 但在調(diào)用exit之前,還做了其他工作:
- 執(zhí)行標(biāo)準(zhǔn)的程序清理操作:調(diào)用?
atexit()
?注冊的清理函數(shù)、關(guān)閉文件描述符等 - 關(guān)閉所有打開的流,所有的緩存數(shù)據(jù)均被寫入
- 調(diào)用_exit
?通過上圖,我們可以明顯觀察到三者的差異。
return退出:
return是一種更常見的退出進(jìn)程方法。執(zhí)行return n;等同于執(zhí)行 exit(n) ,因為調(diào)用main的運行時函數(shù)會將main的返回值當(dāng)做 exit() 函數(shù)的參數(shù)。
?
三、進(jìn)程等待
在學(xué)習(xí)進(jìn)程等待前,我們要先了解什么是進(jìn)程等待?
進(jìn)程等待(Process Waiting)是指在操作系統(tǒng)中,一個進(jìn)程因為某種原因(通常是等待某些資源或條件滿足)而被阻塞,暫時無法執(zhí)行,需要等待直到滿足條件才能繼續(xù)執(zhí)行的狀態(tài)。這種狀態(tài)通常發(fā)生在進(jìn)程請求某種資源(如輸入/輸出設(shè)備、內(nèi)存、鎖等)而資源暫時不可用時,進(jìn)程會被置于等待狀態(tài),直到資源可用或條件滿足后才能繼續(xù)執(zhí)行。在進(jìn)程等待的過程中,操作系統(tǒng)可以將該進(jìn)程從可執(zhí)行狀態(tài)轉(zhuǎn)換為阻塞狀態(tài),以便其他可執(zhí)行的進(jìn)程有機(jī)會執(zhí)行。
舉個簡單的例子:在C語言中,我們使用printf函數(shù)向屏幕打印信息。我們讓下面的程序一直向顯示器打印信息,并觀察該進(jìn)程的狀態(tài)。
#include <stdio.h>
#include <unistd.h>int main()
{while(1){printf("I am a running process, my_pid:%d\n", getpid());sleep(1);}return 0;
}
看到這兒可能有同學(xué)會產(chǎn)生疑惑了:進(jìn)程明明是一直在運行呀,不應(yīng)該是R狀態(tài)(運行態(tài))嗎?為什么是S狀態(tài)(睡眠態(tài))呢?
其實這與CPU和顯示器的響應(yīng)速度有關(guān)。我們知道,CPU處理信息的速度是極快的,特別對于這種極其簡單的程序。但當(dāng)CPU每次處理完自身任務(wù)后,顯示器可能仍在處理之前的信息。而在這期間后續(xù)進(jìn)程需要等待顯示器準(zhǔn)備就緒,這會導(dǎo)致后續(xù)進(jìn)程處于阻塞狀態(tài),直到顯示器空閑并且操作系統(tǒng)將控制權(quán)交給它們。
?進(jìn)程等待的必要性
- ?之前講過,子進(jìn)程退出,父進(jìn)程如果不管不顧,就可能造成“僵尸進(jìn)程”的問題,進(jìn)而造成內(nèi)存泄漏。
- 另外,進(jìn)程一旦變成僵尸狀態(tài),那就刀槍不入,“殺人不眨眼”的kill -9 也無能為力,因為誰也沒有辦法 殺死一個已經(jīng)死去的進(jìn)程。
- 最后,父進(jìn)程派給子進(jìn)程的任務(wù)完成的如何,我們需要知道。如,子進(jìn)程運行完成,結(jié)果對還是不對, 或者是否正常退出。 父進(jìn)程通過進(jìn)程等待的方式,回收子進(jìn)程資源,獲取子進(jìn)程退出信息?
進(jìn)程等待方法:wait() / waitpid() 函數(shù)詳解
wait()
?和?waitpid()
?函數(shù)都是用于父進(jìn)程等待子進(jìn)程結(jié)束的方法。它們的功能是類似的,但在使用上有些微妙的差別。wait() 函數(shù)
wait()
?函數(shù)的原型如下:#include <sys/types.h> #include <sys/wait.h>pid_t wait(int *status);
- 參數(shù)?
status
?是一個指向整數(shù)的指針,用于存儲子進(jìn)程的退出狀態(tài)信息,不關(guān)心可以設(shè)置成NULL。- 返回值是被等待子進(jìn)程的進(jìn)程ID(PID),如果沒有子進(jìn)程或出錯,則返回 -1。
wait()
?函數(shù)的作用是掛起當(dāng)前進(jìn)程,直到任何一個子進(jìn)程退出為止。當(dāng)子進(jìn)程結(jié)束時,父進(jìn)程會收到一個信號,并在收到信號后繼續(xù)執(zhí)行。此時,可以通過?status
?參數(shù)獲取子進(jìn)程的退出狀態(tài)信息。waitpid() 函數(shù)
waitpid()
?函數(shù)的原型如下:#include <sys/types.h> #include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int options);
pid
?參數(shù)指定要等待的子進(jìn)程ID。如果?pid
?為 -1,則等待任意子進(jìn)程;如果為 0,則等待與當(dāng)前進(jìn)程組ID相同的任一子進(jìn)程;如果為正數(shù),則等待指定PID的子進(jìn)程;如果為負(fù)數(shù),則等待進(jìn)程組ID與?pid
?絕對值相同的任一子進(jìn)程。status
?參數(shù)同樣是用于存儲子進(jìn)程退出狀態(tài)信息的指針,不關(guān)心可以設(shè)置成NULL。options
?參數(shù)是一組選項,用于指定等待行為的一些額外條件,如是否非阻塞等,如不使用可以設(shè)置為0,默認(rèn)阻塞等待。
waitpid()
?函數(shù)的返回值有三種可能性:
如果返回一個正值,這個值就是已經(jīng)終止的子進(jìn)程的進(jìn)程 ID(PID)。
如果返回 0,表示調(diào)用了?
WNOHANG
?選項,并且當(dāng)前沒有已終止的子進(jìn)程。如果返回 -1,表示發(fā)生了錯誤,這時需要檢查?
errno
?來獲取具體的錯誤信息。區(qū)別總結(jié)
wait()
?函數(shù)只能等待任何子進(jìn)程退出,而?waitpid()
?函數(shù)允許指定具體的子進(jìn)程ID。waitpid()
?函數(shù)還可以通過?options
?參數(shù)指定一些額外的選項,如是否非阻塞等。- 兩個函數(shù)都會掛起調(diào)用它們的進(jìn)程,直到等待的子進(jìn)程退出為止。
?waitpid()函數(shù)options選項:
waitpid() 函數(shù)的?options?參數(shù)用于指定等待行為的一些額外條件,包括但不限于以下幾種選項:
- WNOHANG:指定非阻塞模式。如果沒有子進(jìn)程退出,立即返回,不掛起父進(jìn)程。
- WUNTRACED:也會等待已經(jīng)停止的子進(jìn)程退出,但不會等待已經(jīng)恢復(fù)執(zhí)行的子進(jìn)程。
- WCONTINUED:等待已經(jīng)繼續(xù)執(zhí)行的子進(jìn)程退出。
- WSTOPPED:等待已經(jīng)停止的子進(jìn)程退出,與?WUNTRACED?類似。
這些選項可以單獨使用,也可以通過按位或運算組合使用。例如,options?可以是?
WNOHANG | WUNTRACED
,表示以非阻塞模式等待子進(jìn)程退出,并且同時等待已經(jīng)停止的子進(jìn)程退出。這些選項實際上是預(yù)先定義好的宏,在?
sys/wait.h
?頭文件中進(jìn)行了聲明。當(dāng)你使用這些宏時,編譯器會將其替換為相應(yīng)的整數(shù)值,以便在?waitpid()
?函數(shù)中使用。
status參數(shù)詳解
status?參數(shù)是一個輸出型參數(shù),用于由進(jìn)程本身提供子進(jìn)程的退出狀態(tài)信息。當(dāng)子進(jìn)程退出時,其退出狀態(tài)會被寫入到?status?參數(shù)指向的內(nèi)存位置中,以供父進(jìn)程獲取。?
- wait和waitpid,都有一個status參數(shù),該參數(shù)是一個輸出型參數(shù),由操作系統(tǒng)填充。
如果傳遞NULL,表示不關(guān)心子進(jìn)程的退出狀態(tài)信息。 否則,操作系統(tǒng)會根據(jù)該參數(shù),將子進(jìn)程的退出信息反饋給父進(jìn)程。- status不能簡單的當(dāng)作整形來看待,可以當(dāng)作位圖來看待,具體細(xì)節(jié)如下圖(只研究status低16比特位)
![]()
在status的低16比特位當(dāng)中,高8位表示進(jìn)程的退出狀態(tài),即退出碼。進(jìn)程若是被信號所殺,則低7位表示終止信號,而第8位比特位是core dump標(biāo)志。?

我們通過位運算操作就可以得出程序的退出狀態(tài)和終止信號:
exit_code = (status >> 8) & 0xff; //退出狀態(tài),取9~16位
exit_signal = status & 0x7f; //終止信號,取1~7位
同時,庫中也提供了兩個宏來代替上面的運算:
WEXITSTATUS(status)
?宏函數(shù)用于提取子進(jìn)程的退出碼,前提是該子進(jìn)程是通過正常終止而退出的。其原型為:int WEXITSTATUS(int status);
當(dāng)且僅當(dāng)?
WIFEXITED(status)
?返回真(非零值)時,才應(yīng)該用?WEXITSTATUS(status)
。這個宏可以幫助你獲取子進(jìn)程退出時傳遞給?exit()
?函數(shù)的退出碼,通常用于查看子進(jìn)程的退出狀態(tài)。
WIFEXITED(status)
?宏函數(shù)用于檢查子進(jìn)程是否為正常終止,本質(zhì)是檢查是否收到信號,其原型為:int WIFEXITED(int status);
如果子進(jìn)程正常終止并成功退出,則該宏返回真(非零值),否則返回假(0)。這個宏可以幫助你確定子進(jìn)程是否是通過調(diào)用?
exit()
?或?_exit()
?等函數(shù)正常退出的。
?所以也可以如下表示:
exit_code = WEXITSTATUS(status); //獲取退出碼
exit_signal = WIFEXITED(status); //是否正常退出
使用示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if(id == 0){//childint count = 10;while(count--){printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}exit(0);}//fatherint status = 0;pid_t ret = wait(&status);//pid_t ret = wait(-1, &status, 0); 此方法同上if(ret > 0){//wait successprintf("wait child success...\n");if(WIFEXITED(status)){//exit normalprintf("exit code:%d\n", WEXITSTATUS(status));}}sleep(1);return 0;
}
?可以看到父進(jìn)程成功等待到子進(jìn)程,且子進(jìn)程的是正常退出。
如果我們使用kill -9 信號使子進(jìn)程強(qiáng)制退出呢?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if(id == 0){//childint count = 20;while(count--){printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}exit(0);}//fatherint status = 0;pid_t ret = waitpid(-1, &status, 0);if(ret > 0){//wait successprintf("wait child success...\n");//判斷是否正常退出if(WIFEXITED(status)){//exit normalprintf("exit code:%d\n", WEXITSTATUS(status));//正常退出則打印退出碼}else {//exit abnormalprintf("exit_signal:%d\n",status & 0x7f);}}sleep(3);return 0;
}
?可以看到,當(dāng)使用kill -9 強(qiáng)制終止子進(jìn)程時,父進(jìn)程依然等待成功,但是由于使用的是信號,所以是異常退出,最終程序只打印出了退出信號。
進(jìn)程的阻塞等待方式:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;}else if (pid == 0) { //childprintf("child is run, pid is : %d\n", getpid());sleep(5);exit(257);}else {int status = 0;pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5Sprintf("this is test for wait\n");if (WIFEXITED(status) && ret == pid) {printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));}else {printf("wait child failed, return.\n");return 1;}}return 0;
}
上述所給例子中,當(dāng)子進(jìn)程未退出時,父進(jìn)程都在一直等待子進(jìn)程退出,在等待期間,父進(jìn)程不能做任何事情,這種等待叫做阻塞等待。
實際上我們可以讓父進(jìn)程不要一直等待子進(jìn)程退出,而是當(dāng)子進(jìn)程未退出時父進(jìn)程可以做一些自己的事情,當(dāng)子進(jìn)程退出時再讀取子進(jìn)程的退出信息,即非阻塞等待。
我們將options參數(shù)設(shè)置為WNOHANG:指定非阻塞模式。如果沒有子進(jìn)程退出,立即返回,不掛起父進(jìn)程。并且使用循環(huán)進(jìn)行輪番檢測,如果等待不成功,那父進(jìn)程就去做別的事情。過段時間再去調(diào)用waitpid函數(shù),等待子進(jìn)程成功后,父進(jìn)程才會退出。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id = fork();if (id == 0) {//childint count = 3;while (count--) {printf("child do something...PID:%d, PPID:%d\n", getpid(), getppid());sleep(3);}exit(0);}//fatherwhile (1) {int status = 0;pid_t ret = waitpid(id, &status, WNOHANG);if (ret > 0) //父進(jìn)程等待成功{printf("wait child success...\n");printf("exit code:%d\n", WEXITSTATUS(status));break;}else if (ret == 0) //子進(jìn)程仍在運行{printf("father do other things...\n");//可以穿插其他函數(shù)讓父進(jìn)程去做其他事情sleep(1);}else //waitpid返回-1,等待失敗,終止等待{printf("waitpid error...\n");break;}}return 0;
}
?四、進(jìn)程替換
進(jìn)程替換原理:
用fork創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支),子進(jìn)程往往要調(diào)用一種exec函數(shù)以執(zhí)行另一個程序。當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時,該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動例程開始執(zhí)行。調(diào)用exec并不創(chuàng)建新進(jìn)程,所以調(diào)用exec前后該進(jìn)程的id并未改變。
當(dāng)一個進(jìn)程執(zhí)行了進(jìn)程替換(如exec()
系列函數(shù))后,它會被替換為另一個程序。
以下函數(shù)統(tǒng)稱exec函數(shù):?
execl():
- 函數(shù)接口:
int execl(const char *path, const char *arg0, ... /* (char *) NULL */);
- 參數(shù)解釋:
path
:要執(zhí)行的程序的路徑。arg0
:程序的名稱,一般來說是?path
?的基本文件名。...
:傳遞給新程序的參數(shù)列表,以 NULL 結(jié)尾。execle():
- 函數(shù)接口:
int execle(const char *path, const char *arg0, ..., /* (char *) NULL, char *const envp[] */);
- 參數(shù)解釋:
- 除了 execl() 的參數(shù)外,還接受一個額外的參數(shù)?
envp
,用于傳遞環(huán)境變量數(shù)組。execlp():
- 函數(shù)接口:
int execlp(const char *file, const char *arg0, ... /* (char *) NULL */);
- 參數(shù)解釋:
file
:要執(zhí)行的程序的文件名,會在環(huán)境變量 PATH 指定的路徑中搜索。- 其他參數(shù)與 execl() 類似。
execv():
- 函數(shù)接口:
int execv(const char *path, char *const argv[]);
- 參數(shù)解釋:
path
:要執(zhí)行的程序的路徑。argv[]
:傳遞給新程序的參數(shù)數(shù)組,以 NULL 結(jié)尾。execvp():
- 函數(shù)接口:
int execvp(const char *file, char *const argv[]);
- 參數(shù)解釋:
file
:要執(zhí)行的程序的文件名,會在環(huán)境變量 PATH 指定的路徑中搜索。argv[]
:傳遞給新程序的參數(shù)數(shù)組,以 NULL 結(jié)尾。execve():
- 函數(shù)接口:
int execve(const char *filename, char *const argv[], char *const envp[]);
- 參數(shù)解釋:
filename
:要執(zhí)行的程序的路徑。argv[]
:傳遞給新程序的參數(shù)數(shù)組,以 NULL 結(jié)尾。envp[]
:環(huán)境變量數(shù)組。execvpe():
- 函數(shù)接口:
int execvpe(const char *file, char *const argv[], char *const envp[]);
- 參數(shù)解釋:
file
:要執(zhí)行的程序的文件名,會在環(huán)境變量 PATH 指定的路徑中搜索。argv[]
:傳遞給新程序的參數(shù)數(shù)組,以 NULL 結(jié)尾。envp[]
:環(huán)境變量數(shù)組。這些函數(shù)的參數(shù)解釋中,
path
代表要執(zhí)行的程序的路徑,?file
?代表要執(zhí)行的可執(zhí)行文件的文件名,argv[]
?是傳遞給新程序的參數(shù)數(shù)組,而?envp[]
?是環(huán)境變量數(shù)組。函數(shù)的返回值為 -1 表示執(zhí)行失敗,具體的錯誤信息可以通過查看 errno 來獲取。進(jìn)程替換如果調(diào)用成功則加載新的程序從啟動代碼開始執(zhí)行,不再返回到原來的程序中。?
命名理解
- 這些函數(shù)用于執(zhí)行其他程序。
- 以 “l(fā)(list)” 結(jié)尾的函數(shù)(如 execl、execle)采用參數(shù)列表的方式傳遞參數(shù),參數(shù)個數(shù)在函數(shù)調(diào)用時需要提前確定。
- 以 “v(vector)” 結(jié)尾的函數(shù)(如 execv、execvp)采用指針數(shù)組的方式傳遞參數(shù),參數(shù)個數(shù)在數(shù)組的結(jié)束標(biāo)志(NULL)前確定。
- 以 “p(path)” 結(jié)尾的函數(shù)(如 execlp、execvp、execvpe)可以根據(jù)環(huán)境變量 PATH 來搜索可執(zhí)行文件。
- 以 “e(env)” 結(jié)尾的函數(shù)(如 execle、execve、execvpe)允許設(shè)置環(huán)境變量。
- execvpe() 在搜索可執(zhí)行文件時除了搜索 PATH 環(huán)境變量外,還可以通過傳遞一個環(huán)境變量數(shù)組來搜索。
應(yīng)用舉例:?
#include <unistd.h>
int main()
{char* const argv[] = { "ps", "-ef", NULL };char* const envp[] = { "PATH=/bin:/usr/bin", "TERM=console", NULL };execl("/bin/ps", "ps", "-ef", NULL);// 帶p的,可以使用環(huán)境變量PATH,無需寫全路徑execlp("ps", "ps", "-ef", NULL);// 帶e的,需要自己組裝環(huán)境變量execle("ps", "ps", "-ef", NULL, envp);execv("/bin/ps", argv);// 帶p的,可以使用環(huán)境變量PATH,無需寫全路徑execvp("ps", argv);// 帶e的,需要自己組裝環(huán)境變量execve("/bin/ps", argv, envp);exit(0);
}
?事實上,只有execve才是真正的系統(tǒng)調(diào)用,其它五個函數(shù)最終都是調(diào)用的execve,所以execve在man手冊的第2節(jié),而其它五個函數(shù)在man手冊的第3節(jié),也就是說其他五個函數(shù)實際上是對系統(tǒng)調(diào)用execve進(jìn)行了封裝,以滿足不同用戶的不同調(diào)用場景的。