進程創建
在Linux 中 fork 函數是非常重要的,它從已經存在的進程中創建一個新的進程。
#include<unistd.h>
pid_t frok(void);
/*
創建成功:
父進程:返回子進程 PID
子進程:返回 0
創建失敗:
父進程:返回 -1,並設置 errno
*/
程序調用 fork 後,當控制轉移到內核中的 fork 代碼後,內核:
- 分配新的內存塊和內核數據結合給子進程;
- 將父進程部分數據結構拷貝至子程序;
- 添加子進程到系統進程列表中;
- fork 返回後,開始調度執行;
寫時拷貝:通常代碼共享,父子不再寫入時,數據也是共享的,當任意一方試圖寫入的時候,便以寫詩拷貝的方式各自一份副本;
#include<sys/types.h>
#include<unistd.h>
pid_t vfork(void);
vfork():創建子進程,子進程和父進程公用同一塊虛擬地址空間,爲了防止調用棧混亂,因此阻塞父進程直到子進程退出調用exit() 退出 或者 進行程序替換;
fork() 和 vfork() 底層都是通過調用 clone() 實現進程的創建,但是在實現的時候,會稍有區別;
fork常規用法:父進程希望複製自己創建子進程從而同時執行不同的代碼段;一個進程要執行不同的程序;
fork調用失敗的原因:系統中的進程太多、實際用戶的進程數超過了限制;
進程終止
進程退出的場景:運行完畢,結果正確;運行完畢,結果不正確;代碼異常中止;
正常中止:從mian函數返回後;調用exit()函數;調用_exit();
exit 和 _exit 區別是 _exit 退出的時候不會刷新緩衝區,直接釋放資源;但是,實際上exit 最後也會調用 _exit 函數,但是在調用之前,還做了其他的工作:
- 執行用戶通過 atecit 或 no_exit 定義的清理函數;
- 關閉所有打開的流,所有的緩存數據均被寫入;
- 調用 _exit();
return 退出:return 是一種更常見的退出進程發的方法,執行 return n 等同於執行 exit(n),因爲調用 main 函數的運行時函數會將 main 函數的返回值當作 exit 的參數;
進程等待
進程等待可以有效的避免出現程序出現殭屍進程!
wait 方法:
#include<sys/types>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:成功返回被等待進程的pid,失敗返回-1;
參數:輸出型參數,獲取子進程退出狀態,不關心則可以設爲NULL;
waitpid 方法:
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int* status,int options);
返回值:
當正常返回的時候 waitpid 返回收集到的子進程的ID;
如果設置了WNOHANG,而調用中waitpid發現沒有已退出的子進程可收集,則返回0;
如果調用中出了錯,則返回-1,這時errno會被設置成相應的值以示錯誤所在;
參數:
pid:
pid=-1,等待任意一個子進程;
pid>0,等待其進程ID與PID相等的子進程
status:
WIFEXITED:若爲正常中止子進程返回的轉來,則爲真;
WEXITSTATUS:如果WIFEXITED非0,提取子進程推出碼;
options:
WNOHANG:若pid指定的子進程沒有結束,則waitpid()函數返回0,不予等待,若正常結束,返回子進程的ID
- 如果子進程已經退出,調用wait / waitpid 時,wait / waitpid 會立即返回,並且釋放資源,獲得子進程退出信息。
- 如果在任意時刻調用 wait / waitpid ,子進程存在且正常運行,則進程可能阻塞。
- 如果不存在該子進程,則立即返回報錯;
進程替換
用fork創建的子進程執行的時和父進程相同的程序(有可能是不同的代碼分支),子進程可以通過調用 exec 函數來執行另一個程序。當程序調用一種 exec 函數的時候,該進程的用戶空間和代碼完全被新程序替換,從新程序的啓動例程開始執行。調用exec 並不創建新的進程,所以調用 exec 函數後該進程的ID 並未改變。
#include<unistd.h>
int execl(const char* path,const char* arg……);
int execlp(const char* flie,const char* arg……);
int execle(const char* path,const char* arg,...,char* const envp[]);
int execv(const char* path,char* const argv[]);
int execvp(const char* file,char* const argv[]);
int execve(const char* path,char* const argv[],char* const envp[]);
這6個函數都以exec開頭,統稱爲exec函數;
- l(list):表示參數採用列表
- v(vector):參數用數組
- p(path):有 p 自動搜素環境變量 PATH
- e(env):表示自己維護環境變量
#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 ,可以使用環境變量PATH,無需寫全路徑
execlp("ps","ps","-ef",NULL);
//帶 e ,需要自己組裝環境變量
execle("ps","ps","-ef",NULL,envp);
execv("/bin/ps",argv);
//帶 p ,可以使用環境變量PATH,無需寫全路徑
execvp("ps",argv);
//帶 e ,需要自己組裝環境變量
execve("/bin/ps",argv,envp);
exit(0);
}
事實上,只有execve是真正的系統調用,其他五個函數最終都調用 execve。