Linux:進程控制(創建、終止、等待、替換)

進程控制(創建、終止、等待、替換)

進程創建(fork)

fork之後系統裏多了一個進程,也就意味着多了一套PCB。

fork(用於創建子進程)

#include <unistd.h>
pid_t fork(void);

fork函數返回值:子進程返回0,父進程返回子進程pid,出錯返回-1

進程調用fork,當控制轉移到內核中的fork代碼後,內核做:

  • 分配新的內存塊和內核數據結構給子進程
  • 將父進程部分數據結構內容拷貝至子進程
  • 添加子進程到系統進程列表當中
  • fork返回,開始調度器調度
    在這裏插入圖片描述
    也就是說子進程繼承了父進程的代碼,父子進程數據以寫時拷貝的方式各自私有一份。
    寫時拷貝:
    父子代碼共享,父子再不寫入時,數據也是共享的,當任意一方試圖寫入,便以寫時拷貝的方式各自一份副本。
    在這裏插入圖片描述

fork常規用法

  • 一個父進程希望複製自己,使父子進程同時執行不同的代碼段。例如,父進程等待客戶端請求,生成子進程來處理請求。
  • 一個進程要執行一個不同的程序。例如子進程從fork返回後,調用exec函數。

fork調用失敗的原因

  • 系統中有太多的進程
  • 實際用戶的進程數超過了限制

進程終止(exit)

進程退出一共有三種情況:

  1. 代碼運行完畢,結果正確
  2. 代碼運行完畢,結果錯誤
  3. 代碼異常終止

進程的退出方法:

  1. 從main函數return返回
  2. 調用exit
  3. _exit
exit函數
#include <unistd.h>
void exit(int status);

參數:status 定義了進程的終止狀態(可以用0或非0表示),父進程通過wait來獲取該值

_exit函數
#include <unistd.h>
void _exit(int status);

參數:status 定義了進程的終止狀態(可以用0或非0表示),父進程通過wait來獲取該值

exit和_exit的區別如下圖:
在這裏插入圖片描述

進程等待(wait、waitpid)

進程等待必要性

  • 當子進程退出時,我們想要知道交給子進程的任務它完成的如何,因此我們需要等待子進程來獲取子進程的退出信息。
  • 如果子進程退出,父進程不管它,那麼就會造成殭屍子進程的問題,如果有無數個進程都這樣做,那麼就會產生無數的殭屍進程,殭屍進程要佔用資源,要進行數據維護,那麼就會產生內存泄漏問題。
  • 通過進程等待的方式來回收子進程資源。當父進程等待成功並拿到子進程的退出信息時,子進程就可以被操作系統回收了。

具體怎麼等待呢?
當進程在運行時進程狀態通常是R狀態,如果此時父進程需要等待子進程,那麼操作系統就會把父進程的狀態設置爲非R狀態,然後把父進程的PCB放到在該進程下的等待隊列中進行等待。那如果現在子進程運行完了,父進程此時就要被喚醒了,那麼怎麼喚醒呢?很簡單,操作系統把父進程的PCB從等待隊列中拿出來,然後把父進程的狀態由非R狀態變爲R狀態即可。(注:這種情況是阻塞式等待)

進程等待方法

wait

wait:等待任意一個子進程

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

返回值:
成功返回被等待進程pid,失敗返回-1。
參數:
輸出型參數,獲取子進程退出狀態,不關心則可以設置成爲NULL

waitpid

waitpid: 可以等待指定id的進程,也可以等待任意一個子進程

#include <sys/types.h>
#include <wait.h>
pid_t waitpid(pid_t pid,int *status,int options)

返回值:
當正常返回的時候waitpid返回收集到的子進程的進程ID;
如果設置了選項WNOHANG,而調用中waitpid發現沒有已退出的子進程可收集,則返回0;
如果調用中出錯,則返回-1,這時errno會被設置成相應的值以指示錯誤所在。
參數:
pid: 要等待的子進程的id(如果將該值設置爲-1,則等待任意一個子進程)
status: 子進程的退出信息(輸出型參數)

宏:WIFEXITED(status): 若爲正常終止子進程返回的狀態,則爲真。(查看進程是否是正常退出) 位操作:status&0x7F
宏:WEXITSTATUS(status): 若WIFEXITED非零,提取子進程退出碼。(查看進程的退出碼) 位操作:(status>>8)&0xFF

options: 等待方式(默認是阻塞等待)

WNOHANG(非阻塞等待): 若pid指定的子進程沒有結束,則waitpid()函數返回0,不予以等待。若正常結束,則返回該子進程的ID。

waitpid裏的status參數:
父進程按理說是不可以拿到子進程的退出信息的,但是父進程想通過status拿到子進程的退出信息,卻是可以的,這是爲什麼呢?因爲waitpid是系統調用,在進行調用waitpid函數時,父進程會陷入內核,權限會提升,操作系統就可以幫父進程拿到子進程的退出信息,waitpid剛好傳了一個status參數,操作系統就把子進程的退出信息放在status參數裏,進行傳出,所以status是輸出型參數。

0-7比特位表示是否正常退出,或者收到什麼信號
8-15比特位表示退出時的退出狀態(退出碼),如果被信號所殺則沒有此部分的數據
在這裏插入圖片描述

進程程序替換(exec*函數)

用fork創建子進程後執行的是和父進程相同的程序(但有可能執行不同的代碼分支),子進程往往要調用一種exec函數以執行另一個程序。當進程調用一種exec函數時,該進程的用戶空間代碼和數據完全被新程序替換,從新程序的啓動例程開始執行。調用exec並不創建新進程,所以調用exec前後該進程的id並未改變。
在這裏插入圖片描述
進程程序替換函數

#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, 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[]);

l(list) : 代表傳參的時候是以列表的形式去呈現
v(vector) : 代表傳參的時候是以指針數組的形式去呈現
p(path) : 有p自動搜索環境變量PATH(有p的函數默認會去PATH路徑下查找)
e(env) : 表示自己維護環境變量(說明需要主動的添加一些自己定義的環境變量)

注意事項:

  1. 有六個函數,但是execve纔是系統調用接口,其他的五個都是封裝的
  2. 一旦進行了程序替換,那麼說明以前的代碼和數據都被替換掉了,也就是說程序替換沒有成功時的返回值

簡單的進程程序替換函數使用:

#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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章