【Linux】進程創建、進程終止、進程等待、進程程序替換

進程創建

在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。

                              

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章