進(jìn)程是Linux操作系統(tǒng)環(huán)境的基礎(chǔ),它控制著系統(tǒng)幾乎所有的活動(dòng),下面介紹Linux下多進(jìn)程的系統(tǒng)調(diào)用API爬早。
fork()系統(tǒng)調(diào)用
Linux下創(chuàng)建新進(jìn)程的系統(tǒng)調(diào)用時(shí)fork(),定義如下:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
該函數(shù)每的每次調(diào)用都返回兩次,在父進(jìn)程中返回的是子進(jìn)程的PID, 在子進(jìn)程中返回0 臣樱。返回值是后續(xù)用來判斷當(dāng)前進(jìn)程為父進(jìn)程還是子進(jìn)程的依據(jù)。fork調(diào)用失敗返回-1腮考,并設(shè)置errno雇毫。
fork()函數(shù)復(fù)制當(dāng)前進(jìn)程,在內(nèi)核進(jìn)程表中創(chuàng)建一個(gè)新的進(jìn)程表項(xiàng)踩蔚。新的進(jìn)程表項(xiàng)有許多屬性和原進(jìn)程相同棚放。比如,堆指針馅闽,棧指針飘蚯,標(biāo)志寄存器的值馍迄。但也有許多屬性被賦予了新的值,比如孝冒。PPID柬姚,信號(hào)位圖被清除,原來進(jìn)程設(shè)置的信號(hào)處理函數(shù)不在對新進(jìn)程起作用庄涡。
子進(jìn)程的代碼和父進(jìn)程的代碼完全相同,同時(shí)他還會(huì)復(fù)制父進(jìn)程的數(shù)據(jù)(堆數(shù)據(jù)搬设,棧數(shù)據(jù)和靜態(tài)數(shù)據(jù))穴店。但是數(shù)據(jù)的復(fù)制采用的是寫時(shí)復(fù)制(copy on write)。即只有在任一進(jìn)程對數(shù)據(jù)執(zhí)行了寫操作的時(shí)候拿穴,復(fù)制才會(huì)發(fā)生(先是發(fā)生缺頁中斷泣洞,然后操作系統(tǒng)給子進(jìn)程分配內(nèi)存并復(fù)制父進(jìn)程的數(shù)據(jù))。即便如此默色,我們在程序中分配了大量內(nèi)存的時(shí)候球凰,也要謹(jǐn)慎使用fork(),避免復(fù)制沒有必要的內(nèi)存和數(shù)據(jù)腿宰。
此外呕诉,父進(jìn)程中打開了文件描述符,fork()后吃度,子進(jìn)程也是打開的甩挫,且文件描述符的引用計(jì)數(shù)加1。不僅如此椿每,父進(jìn)程的用戶根目錄伊者,當(dāng)前工作目錄等變量的引用計(jì)數(shù)都會(huì)加1。
exec系列系統(tǒng)調(diào)用
有時(shí)我們需要在子進(jìn)程中執(zhí)行其他程序间护,即在fork()后替換當(dāng)前進(jìn)程的映像亦渗,需要使用到一下的函數(shù):
extern char** environ;
// 替換當(dāng)前進(jìn)程映像
// path 參數(shù)指定可執(zhí)行文件的全路徑,
// arg 接受可變參數(shù)
int execl(const char* path, const char* arg, ...);
// file 參數(shù)可以接受文件名,該文件的具體位置則在環(huán)境變量PATH中搜索,
int execlp(const char* file, const char* arg, ...);
// argp 用于設(shè)置環(huán)境變量
int execle(const char* path, const char* arg, ..., char* const envp[]);
// argv 表示可以接受參數(shù)數(shù)組,他們都會(huì)被傳遞給新進(jìn)程
int execv(const char* path, char* const argv[]);
int execvp(const char* file, char* argv[]);
int execve(const char* path, char* const argv[], char* const envp[]);
一般情況下,exec函數(shù)是不返回的汁尺,除非出錯(cuò)法精,出錯(cuò)時(shí)返回 -1,并設(shè)置errno均函。
如果exec執(zhí)行成果亿虽,exec下面的代碼不會(huì)執(zhí)行的,類似于return 語句苞也。
exec 函數(shù)不會(huì)關(guān)閉源程序打開的文件描述符洛勉,除非該文件描述符被設(shè)置了類似于SOCK_CLOEXEC的屬性。
wait處理僵尸進(jìn)程
在多進(jìn)程的編程中如迟,父進(jìn)程一般會(huì)跟蹤子進(jìn)程的退出狀態(tài)收毫。因此攻走,當(dāng)子進(jìn)程結(jié)束運(yùn)行時(shí),內(nèi)核不會(huì)立即釋放該進(jìn)程的進(jìn)程表表項(xiàng)此再,以滿足父進(jìn)程后續(xù)對子進(jìn)程進(jìn)程退出信息的查詢昔搂。
在子進(jìn)程退出,父進(jìn)程沒有獲取其退出狀態(tài)之前输拇,我們?nèi)蝿?wù)他是僵尸進(jìn)程摘符。
在僵尸態(tài)的進(jìn)程,它依然占據(jù)著內(nèi)核資源策吠。這時(shí)絕對不允許的逛裤,畢竟內(nèi)核資源有限。
#include <sys/types.h>
#include <sys/wait.h>
// 返回子進(jìn)程的pid, stat_loc獲取退出狀態(tài) 猴抹,阻塞等待带族。
pid_t wait(int* stat_loc);
// pid == -1時(shí),獲取任意個(gè)子進(jìn)程蟀给,非阻塞的蝙砌。
pid_t waitpid(pid_t pid, int* stat_loc, int option);
wait函數(shù)阻塞進(jìn)程,直到該進(jìn)程的某個(gè)子進(jìn)程運(yùn)行結(jié)束為止跋理。
waitpid函數(shù)只等待pid參數(shù)指定的子進(jìn)程择克。如果pid取值為-1,那么和wait函數(shù)相同薪介。waitpid是非阻塞的祠饺,如果pid指定的目標(biāo)子進(jìn)程還沒有結(jié)束,或者意外終止汁政,waitpid 返回0道偷,如果子進(jìn)程確實(shí)正確退出了,waitpid返回子進(jìn)程的PID记劈。waitpid調(diào)用失敗返回-1,并設(shè)置errno勺鸦。
static void handle_chile(int signal) {
pid_t pid;
int stat ;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) {
// 對子進(jìn)程進(jìn)行善后處理
}
}