色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

深度剖析Linux中進程控制(下)

jf_78858299 ? 來源:一口Linux ? 作者:土豆居士 ? 2023-05-12 10:49 ? 次閱讀

三、進程等待

進程等待的必要性

  1. 子進程退出,父進程如果不讀取子進程的退出信息,子進程就會變成僵尸進程,進而造成內存泄漏。
  2. 進程一旦變成僵尸進程,那么就算是kill -9命令也無法將其殺死,因為誰也無法殺死一個已經死去的進程。
  3. 對于一個進程來說,最關心自己的就是其父進程,因為父進程需要知道自己派給子進程的任務完成的如何。
  4. 父進程需要通過進程等待的方式,回收子進程資源,獲取子進程的退出信息。

獲取子進程status

下面進程等待所使用的兩個函數wait和waitpid,都有一個status參數,該參數是一個輸出型參數,由操作系統進行填充。

如果對status參數傳入NULL,表示不關心子進程的退出狀態信息。否則,操作系統會通過該參數,將子進程的退出信息反饋給父進程。

status是一個整型變量,但status不能簡單的當作整型來看待,status的不同比特位所代表的信息不同,具體細節如下(只研究status低16比特位):

圖片

在status的低16比特位當中,高8位表示進程的退出狀態,即退出碼。進程若是被信號所殺,則低7位表示終止信號,而第8位比特位是core dump標志。

圖片

我們通過一系列位操作,就可以根據status得到進程的退出碼和退出信號。

exitCode = (status >> 8) & 0xFF; //退出碼
exitSignal = status & 0x7F;      //退出信號

對于此,系統當中提供了兩個宏來獲取退出碼和退出信號。

  • WIFEXITED(status):用于查看進程是否是正常退出,本質是檢查是否收到信號。
  • WEXITSTATUS(status):用于獲取進程的退出碼。
exitNormal = WIFEXITED(status);  //是否正常退出
exitCode = WEXITSTATUS(status);  //獲取退出碼

需要注意的是,當一個進程非正常退出時,說明該進程是被信號所殺,那么該進程的退出碼也就沒有意義了。

進程等待的方法

wait方法

函數原型:pid_t wait(int* status);

作用:等待任意子進程。

返回值:等待成功返回被等待進程的pid,等待失敗返回-1。

參數:輸出型參數,獲取子進程的退出狀態,不關心可設置為NULL。

例如,創建子進程后,父進程可使用wait函數一直等待子進程,直到子進程退出后讀取子進程的退出信息。

#include 
#include 
#include 
#include 
#include 
int main()
{
	pid_t id = fork();
	if(id == 0){
		//child
		int count = 10;
		while(count--){
			printf("I am child...PID:%d, PPID:%d\\n", getpid(), getppid());
			sleep(1);
		}
		exit(0);
	}
	//father
	int status = 0;
	pid_t ret = wait(&status);
	if(ret > 0){
		//wait success
		printf("wait child success...\\n");
		if(WIFEXITED(status)){
			//exit normal
			printf("exit code:%d\\n", WEXITSTATUS(status));
		}
	}
	sleep(3);
	return 0;
}

我們可以使用以下監控腳本對進程進行實時監控:

[cl@VM-0-15-centos procWait]$ while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep;echo "######################";sleep 1;done

這時我們可以看到,當子進程退出后,父進程讀取了子進程的退出信息,子進程也就不會變成僵尸進程了。

圖片

waitpid方法

函數原型:pid_t waitpid(pid_t pid, int* status, int options);

作用:等待指定子進程或任意子進程。

返回值:

1、等待成功返回被等待進程的pid。

2、如果設置了選項WNOHANG,而調用中waitpid發現沒有已退出的子進程可收集,則返回0。

3、如果調用中出錯,則返回-1,這時errno會被設置成相應的值以指示錯誤所在。

參數:

1、pid:待等待子進程的pid,若設置為-1,則等待任意子進程。

2、status:輸出型參數,獲取子進程的退出狀態,不關心可設置為NULL。

3、options:當設置為WNOHANG時,若等待的子進程沒有結束,則waitpid函數直接返回0,不予以等待。若正常結束,則返回該子進程的pid。

例如,創建子進程后,父進程可使用waitpid函數一直等待子進程(此時將waitpid的第三個參數設置為0),直到子進程退出后讀取子進程的退出信息。

#include 
#include 
#include 
#include 
#include 
int main()
{
	pid_t id = fork();
	if (id == 0){
		//child          
		int count = 10;
		while (count--){
			printf("I am child...PID:%d, PPID:%d\\n", getpid(), getppid());
			sleep(1);
		}
		exit(0);
	}
	//father           
	int status = 0;
	pid_t ret = waitpid(id, &status, 0);
	if (ret >= 0){
		//wait success                    
		printf("wait child success...\\n");
		if (WIFEXITED(status)){
			//exit normal                                 
			printf("exit code:%d\\n", WEXITSTATUS(status));
		}
		else{
			//signal killed                              
			printf("killed by siganl %d\\n", status & 0x7F);
		}
	}
	sleep(3);
	return 0;
}

在父進程運行過程中,我們可以嘗試使用kill -9命令將子進程殺死,這時父進程也能等待子進程成功。

圖片

注意: 被信號殺死而退出的進程,其退出碼將沒有意義。

多進程創建以及等待的代碼模型

上面演示的都是父進程創建以及等待一個子進程的例子,實際上我們還可以同時創建多個子進程,然后讓父進程依次等待子進程退出,這叫做多進程創建以及等待的代碼模型。

例如,以下代碼中同時創建了10個子進程,同時將子進程的pid放入到ids數組當中,并將這10個子進程退出時的退出碼設置為該子進程pid在數組ids中的下標,之后父進程再使用waitpid函數指定等待這10個子進程。

#include 
#include 
#include 
#include 
#include 
int main()
{
	pid_t ids[10];
	for (int i = 0; i < 10; i++){
		pid_t id = fork();
		if (id == 0){
			//child
			printf("child process created successfully...PID:%d\\n", getpid());
			sleep(3);
			exit(i); //將子進程的退出碼設置為該子進程PID在數組ids中的下標
		}
		//father
		ids[i] = id;
	}
	for (int i = 0; i < 10; i++){
		int status = 0;
		pid_t ret = waitpid(ids[i], &status, 0);
		if (ret >= 0){
			//wait child success
			printf("wiat child success..PID:%d\\n", ids[i]);
			if (WIFEXITED(status)){
				//exit normal
				printf("exit code:%d\\n", WEXITSTATUS(status));
			}
			else{
				//signal killed
				printf("killed by signal %d\\n", status & 0x7F);
			}
		}
	}
	return 0;
}

運行代碼,這時我們便可以看到父進程同時創建多個子進程,當子進程退出后,父進程再依次讀取這些子進程的退出信息。

圖片

基于非阻塞接口的輪詢檢測方案

上述所給例子中,當子進程未退出時,父進程都在一直等待子進程退出,在等待期間,父進程不能做任何事情,這種等待叫做阻塞等待。

實際上我們可以讓父進程不要一直等待子進程退出,而是當子進程未退出時父進程可以做一些自己的事情,當子進程退出時再讀取子進程的退出信息,即非阻塞等待。

做法很簡單,向waitpid函數的第三個參數potions傳入WNOHANG,這樣一來,等待的子進程若是沒有結束,那么waitpid函數將直接返回0,不予以等待。而等待的子進程若是正常結束,則返回該子進程的pid。

例如,父進程可以隔一段時間調用一次waitpid函數,若是等待的子進程尚未退出,則父進程可以先去做一些其他事,過一段時間再調用waitpid函數讀取子進程的退出信息。

#include 
#include 
#include 
#include 
#include 
int main()
{
	pid_t id = fork();
	if (id == 0){
		//child
		int count = 3;
		while (count--){
			printf("child do something...PID:%d, PPID:%d\\n", getpid(), getppid());
			sleep(3);
		}
		exit(0);
	}
	//father
	while (1){
		int status = 0;
		pid_t ret = waitpid(id, &status, WNOHANG);
		if (ret > 0){
			printf("wait child success...\\n");
			printf("exit code:%d\\n", WEXITSTATUS(status));
			break;
		}
		else if (ret == 0){
			printf("father do other things...\\n");
			sleep(1);
		}
		else{
			printf("waitpid error...\\n");
			break;
		}
	}
	return 0;
}

運行結果就是,父進程每隔一段時間就去查看子進程是否退出,若未退出,則父進程先去忙自己的事情,過一段時間再來查看,直到子進程退出后讀取子進程的退出信息。

圖片

四、進程程序替換

替換原理

用fork創建子進程后,子進程執行的是和父進程相同的程序(但有可能執行不同的代碼分支),若想讓子進程執行另一個程序,往往需要調用一種exec函數。

當進程調用一種exec函數時,該進程的用戶空間代碼和數據完全被新程序替換,并從新程序的啟動例程開始執行。

圖片

當進行進程程序替換時,有沒有創建新的進程?

進程程序替換之后,該進程對應的PCB、進程地址空間以及頁表等數據結構都沒有發生改變,只是進程在物理內存當中的數據和代碼發生了改變,所以并沒有創建新的進程,而且進程程序替換前后該進程的pid并沒有改變。

子進程進行進程程序替換后,會影響父進程的代碼和數據嗎?

子進程剛被創建時,與父進程共享代碼和數據,但當子進程需要進行進程程序替換時,也就意味著子進程需要對其數據和代碼進行寫入操作,這時便需要將父子進程共享的代碼和數據進行寫時拷貝,此后父子進程的代碼和數據也就分離了,因此子進程進行程序替換后不會影響父進程的代碼和數據。

替換函數

替換函數有六種以exec開頭的函數,它們統稱為exec函數:

一、int execl(const char *path, const char *arg, ...);

第一個參數是要執行程序的路徑,第二個參數是可變參數列表,表示你要如何執行這個程序,并以NULL結尾。

例如,要執行的是ls程序。

execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);

二、int execlp(const char *file, const char *arg, ...);

第一個參數是要執行程序的名字,第二個參數是可變參數列表,表示你要如何執行這個程序,并以NULL結尾。

例如,要執行的是ls程序。

execlp("ls", "ls", "-a", "-i", "-l", NULL);

三、int execle(const char *path, const char *arg, ..., char *const envp[]);

第一個參數是要執行程序的路徑,第二個參數是可變參數列表,表示你要如何執行這個程序,并以NULL結尾,第三個參數是你自己設置的環境變量。

例如,你設置了MYVAL環境變量,在mycmd程序內部就可以使用該環境變量。

char* myenvp[] = { "MYVAL=2021", NULL };
execle("./mycmd", "mycmd", NULL, myenvp);

四、int execv(const char *path, char *const argv[]);

第一個參數是要執行程序的路徑,第二個參數是一個指針數組,數組當中的內容表示你要如何執行這個程序,數組以NULL結尾。

例如,要執行的是ls程序。

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execv("/usr/bin/ls", myargv);

五、int execvp(const char *file, char *const argv[]);

第一個參數是要執行程序的名字,第二個參數是一個指針數組,數組當中的內容表示你要如何執行這個程序,數組以NULL結尾。

例如,要執行的是ls程序。

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execvp("ls", myargv);

六、int execve(const char *path, char *const argv[], char *const envp[]);

第一個參數是要執行程序的路徑,第二個參數是一個指針數組,數組當中的內容表示你要如何執行這個程序,數組以NULL結尾,第三個參數是你自己設置的環境變量。

例如,你設置了MYVAL環境變量,在mycmd程序內部就可以使用該環境變量。

char* myargv[] = { "mycmd", NULL };
char* myenvp[] = { "MYVAL=2021", NULL };
execve("./mycmd", myargv, myenvp);

函數解釋

  • 這些函數如果調用成功,則加載指定的程序并從啟動代碼開始執行,不再返回。
  • 如果調用出錯,則返回-1。

也就是說,exec系列函數只要返回了,就意味著調用失敗。

命名理解

這六個exec系列函數的函數名都以exec開頭,其后綴的含義如下:

  • l(list):表示參數采用列表的形式,一一列出。
  • v(vector):表示參數采用數組的形式。
  • p(path):表示能自動搜索環境變量PATH,進行程序查找。
  • e(env):表示可以傳入自己設置的環境變量。

圖片

事實上,只有execve才是真正的系統調用,其它五個函數最終都是調用的execve,所以execve在man手冊的第2節,而其它五個函數在man手冊的第3節,也就是說其他五個函數實際上是對系統調用execve進行了封裝,以滿足不同用戶的不同調用場景的。

下圖為exec系列函數族之間的關系:

圖片

做一個簡易的shell

shell也就是命令行解釋器,其運行原理就是:當有命令需要執行時,shell創建子進程,讓子進程執行命令,而shell只需等待子進程退出即可。

圖片

其實shell需要執行的邏輯非常簡單,其只需循環執行以下步驟:

  1. 獲取命令行。
  2. 解析命令行。
  3. 創建子進程。
  4. 替換子進程。
  5. 等待子進程退出。

其中,創建子進程使用fork函數,替換子進程使用exec系列函數,等待子進程使用wait或者waitpid函數。

于是我們可以很容易實現一個簡易的shell,代碼如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define LEN 1024 //命令最大長度
#define NUM 32 //命令拆分后的最大個數
int main()
{
	char cmd[LEN]; //存儲命令
	char* myargv[NUM]; //存儲命令拆分后的結果
	char hostname[32]; //主機名
	char pwd[128]; //當前目錄
	while (1){
		//獲取命令提示信息
		struct passwd* pass = getpwuid(getuid());
		gethostname(hostname, sizeof(hostname)-1);
		getcwd(pwd, sizeof(pwd)-1);
		int len = strlen(pwd);
		char* p = pwd + len - 1;
		while (*p != '/'){
			p--;
		}
		p++;
		//打印命令提示信息
		printf("[%s@%s %s]$ ", pass->pw_name, hostname, p);
		//讀取命令
		fgets(cmd, LEN, stdin);
		cmd[strlen(cmd) - 1] = '\\0';
		//拆分命令
		myargv[0] = strtok(cmd, " ");
		int i = 1;
		while (myargv[i] = strtok(NULL, " ")){
			i++;
		}
		pid_t id = fork(); //創建子進程執行命令
		if (id == 0){
			//child
			execvp(myargv[0], myargv); //child進行程序替換
			exit(1); //替換失敗的退出碼設置為1
		}
		//shell
		int status = 0;
		pid_t ret = waitpid(id, &status, 0); //shell等待child退出
		if (ret > 0){
			printf("exit code:%d\\n", WEXITSTATUS(status)); //打印child的退出碼
		}
	}
	return 0;
}

效果展示:

圖片

說明:

當執行./myshell命令后,便是我們自己實現的shell在進行命令行解釋,我們自己實現的shell在子進程退出后都打印了子進程的退出碼,我們可以根據這一點來區分我們當前使用的是Linux操作系統的shell還是我們自己實現的shell。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    87

    文章

    11292

    瀏覽量

    209333
  • PID
    PID
    +關注

    關注

    35

    文章

    1472

    瀏覽量

    85480
  • 函數
    +關注

    關注

    3

    文章

    4327

    瀏覽量

    62573
  • 數據結構
    +關注

    關注

    3

    文章

    573

    瀏覽量

    40123
收藏 人收藏

    評論

    相關推薦

    Linux開發_Linux進程編程

    介紹Linux進程概念、進程信號捕獲、進程管理相關的命令的使用等知識點。
    的頭像 發表于 09-17 15:38 ?1355次閱讀
    <b class='flag-5'>Linux</b>開發_<b class='flag-5'>Linux</b><b class='flag-5'>下</b><b class='flag-5'>進程</b>編程

    Linux中進程和線程的深度對比

    關于進程和線程,在 Linux 中是一對兒很核心的概念。但是進程和線程到底有啥聯系,又有啥區別,很多人還都沒有搞清楚。
    發表于 10-14 16:47 ?1304次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>中進程</b>和線程的<b class='flag-5'>深度</b>對比

    Linux進程是如何創建出來的?

    Linux 中,進程是我們非常熟悉的東東了,哪怕是只寫過一天代碼的人也都用過它。但是你確定它不是你最熟悉的陌生人?我們今天通過深度剖析進程
    發表于 11-15 09:27 ?576次閱讀

    linux內核深度剖析,另附有光盤資料

    linux內核深度剖析,對于想學linux內核的人來說,絕對值得一看,另附有光盤資料。
    發表于 01-15 21:25

    C語言深度剖析

    C語言深度剖析[完整版].pdfC語言深度剖析[完整版].pdf (919.58 KB )
    發表于 03-19 05:11

    Linux系統中進程如何查看及控制

    Linux系統中進程的查看及控制
    發表于 06-09 08:34

    Linux系統中的進程控制該怎樣去實現呢

    Linux系統編程、網絡編程》第5章 進程控制 2008年畢業于沈陽航空航...
    發表于 12-23 07:55

    Linux進程控制編程

    7.2 Linux進程控制編程 1.fork() 在Linux中創建一個新進程的惟一方法是使用fork()函數。fork()函數是Linux
    發表于 10-18 14:16 ?0次下載

    Linux守護進程詳解

    分享到:標簽:進程控制 Linux 守護進程進程 7.3 Linux守護進程 7.3.1 守
    發表于 10-18 14:24 ?0次下載
    <b class='flag-5'>Linux</b>守護<b class='flag-5'>進程</b>詳解

    uClinux進程調度器的實現分析

    uClinux中進程調度器的實現原理,展示了uClinux中獨具特色的進程調度機制。 關鍵詞:uClinux;調度策略;進程調度器 0. 引言 uClinux是針對控制領域的嵌入式
    發表于 11-06 14:30 ?0次下載

    基于Linux進程管理的詳細剖析

    上一篇,我們講到了Linux內核開發和應用程序開發,今天我們來講講Linux重點部分Linux進程管理。
    的頭像 發表于 01-26 11:24 ?3733次閱讀
    基于<b class='flag-5'>Linux</b><b class='flag-5'>進程</b>管理的詳細<b class='flag-5'>剖析</b>

    Linux和UNIX可以用什么命令查看運行中進程的相關信息

      你可以使用ps命令。它能顯示當前運行中進程的相關信息,包括進程的PID。Linux和UNIX都支持ps命令,顯示所有運行中進程的相關信息。ps命令能提供一份當前
    發表于 01-20 09:42 ?6461次閱讀

    Linux0.11-進程控制塊數據結構

    嵌入式Linux中文站收集整理Linux0.11版本內核學習筆記,本文分析了Linux進程控制模塊的數據結構。
    發表于 05-15 15:22 ?972次閱讀

    深度剖析Linux中進程控制(上)

    Linux中,fork函數是非常重要的函數,它從已存在進程中創建一個新進程。新進程為子進程,而原進程
    的頭像 發表于 05-12 10:49 ?542次閱讀
    <b class='flag-5'>深度</b><b class='flag-5'>剖析</b><b class='flag-5'>Linux</b><b class='flag-5'>中進程控制</b>(上)

    Linux中進程、線程和協程的基礎概念

    進程是計算機中運行的程序的實例,它是操作系統中最基本的執行單元之一。每個進程都有自己的獨立內存空間、系統資源和代碼執行流。這意味著一個進程的崩潰通常不會影響其他進程
    的頭像 發表于 12-06 09:22 ?851次閱讀
    主站蜘蛛池模板: 超碰caoporn| 十分钟视频影院免费| 久久久久婷婷国产综合青草| 国产强奷伦奷片| 国产精品96久久久久久AV不卡| 超碰在线视频公开| XXX老姥群交| A级毛片无码久久精品免费| 在线视频一区二区三区在线播放| 亚洲娇小性色xxxx| 亚洲高清无在码在线电影| 羞羞漫画在线播放| 无套内射无矿码免费看黄| 无码人妻99久久密AV| 体内精69xxxxxx喷潮| 午夜福利体验免费体验区| 我和黑帮老大第365天第2季在线 | 床上色APP下载免费版| 超碰98人人插| 俄罗斯6一9泑女网站| 动漫成年美女黄漫网站| 国产 欧美 亚洲 日韩视频| 国产99精品视频一区二区三区| 高H纯肉NP 弄潮NP男男| 国产h视频在线观看免费| 国产激情视频在线播放| 国产成人拍精品视频网| 国产人成无码视频在线观看| 国精产品一区二区三区四区糖心| 花蝴蝶在线观看免费8 | 91精品国产高清久久久久久| 99pao成人国产永久免费视频| 99影视久久电影网久久看影院| QVOD理论| 国产99久久亚洲综合精品西瓜tv| 国产免费看片| 九九电影伦理片| 男人边吃奶边摸边做刺激情话| 日韩精品人成在线播放| 性xxxx18公交车| 伊人久久大香线蕉影院95|