18video性欧美19sex,欧美高清videosddfsexhd,性少妇videosexfreexxx片中国,激情五月激情综合五月看花,亚洲人成网77777色在线播放

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

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

3天內(nèi)不再提示

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

jf_78858299 ? 來源:一口Linux ? 作者:土豆居士 ? 2023-05-12 10:49 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

三、進程等待

進程等待的必要性

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

獲取子進程status

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

如果對status參數(shù)傳入NULL,表示不關(guān)心子進程的退出狀態(tài)信息。否則,操作系統(tǒng)會通過該參數(shù),將子進程的退出信息反饋給父進程。

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

圖片

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

圖片

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

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

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

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

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

進程等待的方法

wait方法

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

作用:等待任意子進程。

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

參數(shù):輸出型參數(shù),獲取子進程的退出狀態(tài),不關(guān)心可設(shè)置為NULL。

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

#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;
}

我們可以使用以下監(jiān)控腳本對進程進行實時監(jiān)控:

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

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

圖片

waitpid方法

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

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

返回值:

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

2、如果設(shè)置了選項WNOHANG,而調(diào)用中waitpid發(fā)現(xiàn)沒有已退出的子進程可收集,則返回0。

3、如果調(diào)用中出錯,則返回-1,這時errno會被設(shè)置成相應(yīng)的值以指示錯誤所在。

參數(shù):

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

2、status:輸出型參數(shù),獲取子進程的退出狀態(tài),不關(guān)心可設(shè)置為NULL。

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

例如,創(chuàng)建子進程后,父進程可使用waitpid函數(shù)一直等待子進程(此時將waitpid的第三個參數(shù)設(shè)置為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命令將子進程殺死,這時父進程也能等待子進程成功。

圖片

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

多進程創(chuàng)建以及等待的代碼模型

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

例如,以下代碼中同時創(chuàng)建了10個子進程,同時將子進程的pid放入到ids數(shù)組當中,并將這10個子進程退出時的退出碼設(shè)置為該子進程pid在數(shù)組ids中的下標,之后父進程再使用waitpid函數(shù)指定等待這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); //將子進程的退出碼設(shè)置為該子進程PID在數(shù)組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;
}

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

圖片

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

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

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

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

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

#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;
}

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

圖片

四、進程程序替換

替換原理

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

當進程調(diào)用一種exec函數(shù)時,該進程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,并從新程序的啟動例程開始執(zhí)行。

圖片

當進行進程程序替換時,有沒有創(chuàng)建新的進程?

進程程序替換之后,該進程對應(yīng)的PCB、進程地址空間以及頁表等數(shù)據(jù)結(jié)構(gòu)都沒有發(fā)生改變,只是進程在物理內(nèi)存當中的數(shù)據(jù)和代碼發(fā)生了改變,所以并沒有創(chuàng)建新的進程,而且進程程序替換前后該進程的pid并沒有改變。

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

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

替換函數(shù)

替換函數(shù)有六種以exec開頭的函數(shù),它們統(tǒng)稱為exec函數(shù):

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

第一個參數(shù)是要執(zhí)行程序的路徑,第二個參數(shù)是可變參數(shù)列表,表示你要如何執(zhí)行這個程序,并以NULL結(jié)尾。

例如,要執(zhí)行的是ls程序。

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

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

第一個參數(shù)是要執(zhí)行程序的名字,第二個參數(shù)是可變參數(shù)列表,表示你要如何執(zhí)行這個程序,并以NULL結(jié)尾。

例如,要執(zhí)行的是ls程序。

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

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

第一個參數(shù)是要執(zhí)行程序的路徑,第二個參數(shù)是可變參數(shù)列表,表示你要如何執(zhí)行這個程序,并以NULL結(jié)尾,第三個參數(shù)是你自己設(shè)置的環(huán)境變量。

例如,你設(shè)置了MYVAL環(huán)境變量,在mycmd程序內(nèi)部就可以使用該環(huán)境變量。

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

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

第一個參數(shù)是要執(zhí)行程序的路徑,第二個參數(shù)是一個指針數(shù)組,數(shù)組當中的內(nèi)容表示你要如何執(zhí)行這個程序,數(shù)組以NULL結(jié)尾。

例如,要執(zhí)行的是ls程序。

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

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

第一個參數(shù)是要執(zhí)行程序的名字,第二個參數(shù)是一個指針數(shù)組,數(shù)組當中的內(nèi)容表示你要如何執(zhí)行這個程序,數(shù)組以NULL結(jié)尾。

例如,要執(zhí)行的是ls程序。

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

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

第一個參數(shù)是要執(zhí)行程序的路徑,第二個參數(shù)是一個指針數(shù)組,數(shù)組當中的內(nèi)容表示你要如何執(zhí)行這個程序,數(shù)組以NULL結(jié)尾,第三個參數(shù)是你自己設(shè)置的環(huán)境變量。

例如,你設(shè)置了MYVAL環(huán)境變量,在mycmd程序內(nèi)部就可以使用該環(huán)境變量。

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

函數(shù)解釋

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

也就是說,exec系列函數(shù)只要返回了,就意味著調(diào)用失敗。

命名理解

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

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

圖片

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

下圖為exec系列函數(shù)族之間的關(guān)系:

圖片

做一個簡易的shell

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

圖片

其實shell需要執(zhí)行的邏輯非常簡單,其只需循環(huán)執(zhí)行以下步驟:

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

其中,創(chuàng)建子進程使用fork函數(shù),替換子進程使用exec系列函數(shù),等待子進程使用wait或者waitpid函數(shù)。

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

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define LEN 1024 //命令最大長度
#define NUM 32 //命令拆分后的最大個數(shù)
int main()
{
	char cmd[LEN]; //存儲命令
	char* myargv[NUM]; //存儲命令拆分后的結(jié)果
	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(); //創(chuàng)建子進程執(zhí)行命令
		if (id == 0){
			//child
			execvp(myargv[0], myargv); //child進行程序替換
			exit(1); //替換失敗的退出碼設(shè)置為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;
}

效果展示:

圖片

說明:

當執(zhí)行./myshell命令后,便是我們自己實現(xiàn)的shell在進行命令行解釋,我們自己實現(xiàn)的shell在子進程退出后都打印了子進程的退出碼,我們可以根據(jù)這一點來區(qū)分我們當前使用的是Linux操作系統(tǒng)的shell還是我們自己實現(xiàn)的shell。

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

    關(guān)注

    88

    文章

    11581

    瀏覽量

    217154
  • PID
    PID
    +關(guān)注

    關(guān)注

    37

    文章

    1487

    瀏覽量

    89740
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4401

    瀏覽量

    66536
  • 數(shù)據(jù)結(jié)構(gòu)

    關(guān)注

    3

    文章

    573

    瀏覽量

    41231
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點推薦

    Linux開發(fā)_Linux進程編程

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

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

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

    Linux進程是如何創(chuàng)建出來的?

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

    linux內(nèi)核深度剖析,另附有光盤資料

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

    C語言深度剖析

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

    Linux系統(tǒng)中進程如何查看及控制

    Linux系統(tǒng)中進程的查看及控制
    發(fā)表于 06-09 08:34

    Linux系統(tǒng)中的進程控制該怎樣去實現(xiàn)呢

    Linux系統(tǒng)編程、網(wǎng)絡(luò)編程》第5章 進程控制 2008年畢業(yè)于沈陽航空航...
    發(fā)表于 12-23 07:55

    Linux進程控制編程

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

    Linux守護進程詳解

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

    uClinux進程調(diào)度器的實現(xiàn)分析

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

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

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

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

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

    Linux0.11-進程控制塊數(shù)據(jù)結(jié)構(gòu)

    嵌入式Linux中文站收集整理Linux0.11版本內(nèi)核學習筆記,本文分析了Linux進程控制模塊的數(shù)據(jù)結(jié)構(gòu)。
    發(fā)表于 05-15 15:22 ?1122次閱讀

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

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

    Linux中進程、線程和協(xié)程的基礎(chǔ)概念

    進程是計算機中運行的程序的實例,它是操作系統(tǒng)中最基本的執(zhí)行單元之一。每個進程都有自己的獨立內(nèi)存空間、系統(tǒng)資源和代碼執(zhí)行流。這意味著一個進程的崩潰通常不會影響其他進程,
    的頭像 發(fā)表于 12-06 09:22 ?1415次閱讀