進程是Linux操作系統環境的基礎,它控制著系統幾乎所有的活動,下面介紹Linux下多進程的系統調用API。
fork()系統調用
Linux下創建新進程的系統調用時fork(),定義如下:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
該函數每的每次調用都返回兩次,在父進程中返回的是子進程的PID, 在子進程中返回0 。返回值是后續用來判斷當前進程為父進程還是子進程的依據。fork調用失敗返回-1,并設置errno。
fork()函數復制當前進程,在內核進程表中創建一個新的進程表項。新的進程表項有許多屬性和原進程相同。比如,堆指針,棧指針,標志寄存器的值。但也有許多屬性被賦予了新的值,比如。PPID,信號位圖被清除,原來進程設置的信號處理函數不在對新進程起作用。
子進程的代碼和父進程的代碼完全相同,同時他還會復制父進程的數據(堆數據,棧數據和靜態數據)。但是數據的復制采用的是寫時復制(copy on write)。即只有在任一進程對數據執行了寫操作的時候,復制才會發生(先是發生缺頁中斷,然后操作系統給子進程分配內存并復制父進程的數據)。即便如此,我們在程序中分配了大量內存的時候,也要謹慎使用fork(),避免復制沒有必要的內存和數據。
此外,父進程中打開了文件描述符,fork()后,子進程也是打開的,且文件描述符的引用計數加1。不僅如此,父進程的用戶根目錄,當前工作目錄等變量的引用計數都會加1。
exec系列系統調用
有時我們需要在子進程中執行其他程序,即在fork()后替換當前進程的映像,需要使用到一下的函數:
extern char** environ;
// 替換當前進程映像
// path 參數指定可執行文件的全路徑,
// arg 接受可變參數
int execl(const char* path, const char* arg, ...);
// file 參數可以接受文件名,該文件的具體位置則在環境變量PATH中搜索,
int execlp(const char* file, const char* arg, ...);
// argp 用于設置環境變量
int execle(const char* path, const char* arg, ..., char* const envp[]);
// argv 表示可以接受參數數組,他們都會被傳遞給新進程
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函數是不返回的,除非出錯,出錯時返回 -1,并設置errno。
如果exec執行成果,exec下面的代碼不會執行的,類似于return 語句。
exec 函數不會關閉源程序打開的文件描述符,除非該文件描述符被設置了類似于SOCK_CLOEXEC的屬性。
wait處理僵尸進程
在多進程的編程中,父進程一般會跟蹤子進程的退出狀態。因此,當子進程結束運行時,內核不會立即釋放該進程的進程表表項,以滿足父進程后續對子進程進程退出信息的查詢。
在子進程退出,父進程沒有獲取其退出狀態之前,我們任務他是僵尸進程。
在僵尸態的進程,它依然占據著內核資源。這時絕對不允許的,畢竟內核資源有限。
#include <sys/types.h>
#include <sys/wait.h>
// 返回子進程的pid, stat_loc獲取退出狀態 ,阻塞等待。
pid_t wait(int* stat_loc);
// pid == -1時,獲取任意個子進程,非阻塞的。
pid_t waitpid(pid_t pid, int* stat_loc, int option);
wait函數阻塞進程,直到該進程的某個子進程運行結束為止。
waitpid函數只等待pid參數指定的子進程。如果pid取值為-1,那么和wait函數相同。waitpid是非阻塞的,如果pid指定的目標子進程還沒有結束,或者意外終止,waitpid 返回0,如果子進程確實正確退出了,waitpid返回子進程的PID。waitpid調用失敗返回-1,并設置errno。
static void handle_chile(int signal) {
pid_t pid;
int stat ;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) {
// 對子進程進行善后處理
}
}