博客 / 詳情

返回

進程控制

進程的創建

  • 第一個進程:進程0,是在操作系統內核的啓動過程中手工構成的。
  • 第二個進程:進程1,是由進程0在內核態下通過fork而來。
  • 其他的進程:在用户態下,通過fork而來。

創建進程(fork)

  • 注意:在不同進程中fork返回值不同

    • 在父進程中,fork返回值為子進程PID
    • 在子進程中,fork返回值為0
    • 在fork內部就已經開始分流了,根據判斷當前是哪個進程,從而返回不同的值。
  • fork之前的代碼只有父進程執行,fork之後的代碼由父子進程分別共同執行(但是兩個進程誰先被調度是不確定的)
  • 在創建子進程後,操作系統會給子進程創建一個進程地址空間和頁表,但是此時子進程的進程地址空間所對應的物理地址和父進程的進程地址空間所對應的物理地址是一樣的,即父子進程同時使用一塊物理內存(代碼和數據共用)
  • 但是,當子進程或父進程對數據進行更改時(例如子進程更改數據),子進程會進行 “寫時拷貝” 將整個數據段在物理地址中拷貝一份,並且讓進程地址空間對應的虛擬地址映射到拷貝後的物理地址中。這樣不會影響父進程的數據(進程具有獨立性)
#include<<unistd.h>
pid_t fork(void);  //返回值為進程的PID

fork創建失敗的情況

  • 系統中進程太多。
  • 實際用户的進程超過了限制。

進程終止

  • 程序結束,進程正常終止時,會完全釋放消耗的系統資源(內存、IO等)
  • 如果沒有釋放,則相應的資源就會丟失(異常終止時)
  • Linux系統設計時規定:每個進程結束時,操作系統會自動回收這個進程的所有資源(例如malloc申請的內存沒有free釋放時,進程結束後這個內存也會被釋放)
  • 但是操作系統的回收只會回收這個進程工作時消耗的內存和IO,而不會回收這個進程本身佔用的內存(主要是task_struck和棧內存)

進程終止方法

  • 正常終止:

    • 程序正常結束,從main函數return返回。
    • 調用 exit() 函數退出進程。( void exit(int status); )
    • _exit ( void _exit(int status); )
  • 異常終止:

    • 程序異常
    • 命令行輸入ctrl + c 發送退出信號。

使用 return、exit、_exit 終止進程時的區別

  • 使用return 和 exit 終止函數時,都會釋放曾經佔用資源(例如行緩衝區等)
  • 使用_exit 終止程序時 ,是直接終止進程,不會釋放系統資源。

進程等待

  • 進程等待一般都是由父進程完成。
  • 父進程通過進程等待的方式,回收子進程資源,獲取子進程退出信息。

wait方法

wait函數原型

  • wstatus :用來返回子進程結束時的狀態信息(傳參一個int類型的變量的地址即可)
  • pid_t :這個是返回本次wait回收的子進程的PID。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);

wait工作原理

  • 子進程結束時,操作系統會向父進程發送SIGCHILD信號。
  • 父進程調用wait函數時,會等待SIGCHILD信號(函數會阻塞在這裏)
  • 父進程收到信號後,會被喚醒然後去回收殭屍進程(子進程退出,但還未被回收時的狀態)
  • 如果父進程沒有子進程,但是調用了wait函數,這時wait函數會返回錯誤。
  • 如果父進程有多個子進程,wait函數會阻塞到其中一個子進程結束為止。
  • 所以父進程如果調用wait函數,那麼某一個子進程一定會先比父進程結束。

waitpid方法

wait和waitpid差別

  • 基本功能是一樣的,都是用來回收子進程。
  • 但是waitpit可以回收指定PID的子進程。
  • waitpit可以選擇阻塞式和非阻塞式兩種工作模式。

waitpid函數原型

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
  • pid :指定要回收的子進程PID(傳遞值為 -1 時,表示不指定要回收的子進程)
  • status :用來存儲子進程結束時的狀態信息(傳參一個int類型的變量的地址即可)
  • options :實現某些特殊功能。

    • 傳遞的參數為 0 時 :表示不使用此項(即使用阻塞式)
    • WNOHANG :如果沒有子進程,則立馬返回 0(非阻塞式)

退出結果status

  • status 是int 類型,32位,實際使用時只使用低16位。
  • status 的第 8 -- 15 位就是退出碼。
  • 異常退出時,status的低 7 位是終止信號(終止信號可以用 kill -l 顯示)
    image.png

檢測status退出狀態的宏

  • WIFEXITED(status) :查看進程是否正常退出,若是則為真。
  • WEXITSTATUS(status) :若進程正常退出,則查看進程退出碼。

進程程序替換(exec函數)

替換原理

  • 用fork創建子進程後,父子進程執行的是相同的程序,但是為了讓子進程執行不同的程序往往需要調用一種 exec 函數來替換掉子進程中原來的程序。
  • 當調用exec函數後,該進程的物理空間的數據會被新程序完全替換,並且重新建立映射關係,然後讓進程從新程序開始執行。
  • 注意:調用exec函數並不創建新進程,所以調用前後進程的PID並未被改變。
  • 所以,有了exec族函數,子進程中的代碼可以單獨編寫、單獨編譯鏈接成一個可執行程序。
  • 當調用exec族函數後,則會加載新的程序,從新程序的啓動代碼開始執行,不再返回當前代碼,那麼之前進程的後續代碼將不會被執行。
    image.png

替換函數

execl和execv函數

  • path :要執行的可執行程序的全路徑。
  • arg :給可執行程序傳遞的參數。
  • ... :變參,表示可以傳遞多個參數,但是最終結尾的參數必須是NULL。
  • argv[ ] :參數數組,要事先將傳遞的參數寫到數組中。
  • 如果調用出錯,則返回 -1,調用成功則執行新程序,不會返回。
int execl(const char *path, const char *arg, .../* (char  *) NULL */);
int execv(const char *path, char *const argv[]);
  • 例如:執行 ls 程序,傳遞參數 -a -l

    • 這裏第一個傳參ls,貌似沒有什麼作用,只是佔位,第二個開始的參數才會傳遞到程序中。
execl("/bin/ls", "ls", "-l", "-a",NULL);

execlp和execvp函數

  • 效果與上面的兩個函數相同
  • 但是上面兩個只能指定可執行程序的全路徑(否則會找不到文件而報錯)
  • 而這兩個函數可以只傳遞可執行程序的文件名,也可以是全路徑
  • 它會在先去當前目錄下找file,如果找到了則直接執行,如果沒找到則會去環境變量PATH所指定的目錄中去尋找,如果還是沒找到則報錯。

    int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
    int execvp(const char *file, char *const argv[]);

execle和execvpe函數

  • 執行可執行程序時會多傳一個環境變量的字符串數組 envp 給待執行程序。
  • 傳遞的這個環境變量會替代默認的環境變量,在找不到file時,會到這個環境變量中去找,而不會到默認環境變量PATH中去找。
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execvpe(const char *file, char *const argv[],char *const envp[]);
user avatar philadelphia 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.