長(zhǎng)沙門戶網(wǎng)站地推拉新app推廣平臺(tái)有哪些
????????hello,大家好,這里是bang___bang_,在上兩篇中我們講解了進(jìn)程的概念、狀態(tài)和進(jìn)程地址空間,本篇講解進(jìn)程的控制!!包含內(nèi)容有進(jìn)程創(chuàng)建、進(jìn)程等待、進(jìn)程替換、進(jìn)程終止!!
附上前2篇文章鏈接:
Linux——操作系統(tǒng)進(jìn)程詳解!!(建議收藏細(xì)品!!)_bang___bang_的博客-CSDN博客
[Linux]環(huán)境變量 進(jìn)程地址空間(虛擬內(nèi)存與物理內(nèi)存的關(guān)系)_bang___bang_的博客-CSDN博客
?
目錄
?1??計(jì)算機(jī)四個(gè)重要概念
2??進(jìn)程創(chuàng)建
🍙fork函數(shù)初識(shí)
🍙fork返回值
🍙fork調(diào)用失敗原因
3??進(jìn)程終止
🍙進(jìn)程終止的場(chǎng)景
🍙退出碼
🍥查看退出碼echo
🍥字符串格式查看錯(cuò)誤信息strerror
🍥退出碼講解
🍙進(jìn)程常見退出方法
🍥_exit函數(shù)——系統(tǒng)調(diào)用接口
🍥exit函數(shù)——C庫(kù)函數(shù)
🍥return退出
4??進(jìn)程等待
🍙進(jìn)程等待必要性
🍙進(jìn)程等待的方法
🍥wait方法
🍥waitpid方法
5??進(jìn)程程序替換
🍙替換原理
🍙替換函數(shù)
🍥execl函數(shù)
🍥execv函數(shù)
🍥execlp函數(shù)
🍥execle函數(shù)
🍥execvp函數(shù)
🍥execvpe函數(shù)
🍥execve函數(shù)(系統(tǒng)調(diào)用)
1??計(jì)算機(jī)四個(gè)重要概念
?競(jìng)爭(zhēng)性:系統(tǒng)進(jìn)程數(shù)目眾多,而CPU資源只有少量,甚至1個(gè),所以進(jìn)程之間是具有競(jìng)爭(zhēng)屬性的。為了高效完成任務(wù),更合理競(jìng)爭(zhēng)相關(guān)資源,便具有了優(yōu)先級(jí)
?獨(dú)立性:多進(jìn)程運(yùn)行,需要獨(dú)享各種資源,多進(jìn)程運(yùn)行期間互不干擾
?并行:多個(gè)進(jìn)程在多個(gè)CPU下分別,同時(shí)進(jìn)行運(yùn)行,這稱之為并行
?并發(fā):多個(gè)進(jìn)程在一個(gè)CPU下采用進(jìn)程切換的方式,在一段時(shí)間之內(nèi),讓多個(gè)進(jìn)程都得以推進(jìn),稱之為并發(fā)
2??進(jìn)程創(chuàng)建
fork創(chuàng)建子進(jìn)程,操作系統(tǒng)都做了什么?
????????fork創(chuàng)建子進(jìn)程,系統(tǒng)多了一個(gè)進(jìn)程。
????????進(jìn)程=內(nèi)核數(shù)據(jù)結(jié)構(gòu)+進(jìn)程代碼和數(shù)據(jù)!
🍙fork函數(shù)初識(shí)
- 創(chuàng)建子進(jìn)程:失敗返回-1,成功返回子進(jìn)程PID給父進(jìn)程,0返回給子進(jìn)程
進(jìn)程調(diào)用fork,當(dāng)控制轉(zhuǎn)移到內(nèi)核中的fork代碼后,內(nèi)核做:
????????★分配新的內(nèi)存塊和內(nèi)核數(shù)據(jù)結(jié)構(gòu)給子進(jìn)程
????????★將父進(jìn)程部分?jǐn)?shù)據(jù)結(jié)構(gòu)內(nèi)容拷貝至子進(jìn)程
????????★添加子進(jìn)程到系統(tǒng)進(jìn)程列表當(dāng)中
????????★fork返回,開始調(diào)度器調(diào)度
在進(jìn)程詳解篇,我提到過父子進(jìn)程代碼共享。
那么有個(gè)問題:是父進(jìn)程所有的代碼子進(jìn)程都共享呢?還是在fork函數(shù)之后的代碼才共享?

🌰寫一段fork代碼看看結(jié)果(提示:眼見不一定為實(shí))
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{int num=0;int*p=#printf("Begin:pid:%d,&num:%p\n",getpid(),p);pid_t id=fork();if(id<0){perror("fork");}printf("After:pid:%d,fork return %d,&num:%p\n",getpid(),id,p);return 0;
}

根據(jù)結(jié)果我們發(fā)現(xiàn)Begin在子進(jìn)程并沒有執(zhí)行,但這能表示子進(jìn)程沒有共享父進(jìn)程的Begin代碼嗎?
答案是不是的!!!實(shí)際上子進(jìn)程也共享到了父進(jìn)程的Begin語(yǔ)句,只不過CPU中有個(gè)EPI寄存器,他保存了進(jìn)程的上下文信息,使得子進(jìn)程以為fork是他的開始,從fork語(yǔ)句后開始執(zhí)行!!

創(chuàng)建子進(jìn)程,給子進(jìn)程分配對(duì)應(yīng)的內(nèi)核空間結(jié)構(gòu),必須子進(jìn)程自己獨(dú)有,因?yàn)檫M(jìn)程具有獨(dú)立性!理論上,子進(jìn)程也要有自己的代碼和數(shù)據(jù)!可是一般而言,我們創(chuàng)建的子進(jìn)程沒有加載的過程(代碼和數(shù)據(jù)一般是從磁盤上加載到的程序),也就是說,子進(jìn)程沒有自己的代碼和數(shù)據(jù)!所以,子進(jìn)程只能“使用”父進(jìn)程的代碼和數(shù)據(jù)!
?? ?代碼:都是不可被寫的,只能讀取,所以父子共享!
????數(shù)據(jù):可能被修改的,所以,必須分離!
🍙fork返回值
????????★子進(jìn)程返回0
????????★父進(jìn)程返回子進(jìn)程的pid
????????★出錯(cuò)返回-1
為什么fork會(huì)有2個(gè)返回值?是因?yàn)閷憰r(shí)拷貝!!
在進(jìn)程地址空間中我有講解,這里再簡(jiǎn)單講解一下!
基于進(jìn)程的獨(dú)立性,父子進(jìn)程的數(shù)據(jù)必須分離,但是對(duì)于只讀的數(shù)據(jù)我們也進(jìn)行拷貝的話,對(duì)內(nèi)存太過于浪費(fèi),所以出現(xiàn)了一種新的技術(shù):寫時(shí)拷貝技術(shù)!
寫時(shí)拷貝,是一種延時(shí)申請(qǐng)技術(shù),可以提高整機(jī)內(nèi)存的使用率。

子進(jìn)程執(zhí)行讀權(quán)限的時(shí)候,父子進(jìn)程頁(yè)表映射到同一物理內(nèi)存,當(dāng)執(zhí)行寫權(quán)限時(shí),OS重新拷貝一份數(shù)據(jù)到物理內(nèi)存上,同時(shí)子進(jìn)程的頁(yè)表斷開原來的映射關(guān)系,映射到拷貝數(shù)據(jù)的物理地址。
🍙fork調(diào)用失敗原因
????????★系統(tǒng)中有太多的進(jìn)程
????????★實(shí)際用戶的進(jìn)程數(shù)超過了限制
3??進(jìn)程終止
進(jìn)程終止時(shí),操作系統(tǒng)做了什么?
答:釋放進(jìn)程申請(qǐng)的相關(guān)內(nèi)核數(shù)據(jù)結(jié)構(gòu)和對(duì)應(yīng)的數(shù)據(jù)和代碼。本質(zhì):釋放系統(tǒng)資源。
🍙進(jìn)程終止的場(chǎng)景
?????????代碼運(yùn)行完畢,結(jié)果正確
?????????代碼運(yùn)行完畢,結(jié)果不正確
?????????代碼異常退出
🍙退出碼
🍥查看退出碼echo
//獲取最近一個(gè)進(jìn)程,執(zhí)行完畢的退出碼!
echo $?

問題:main函數(shù)返回值的意義是什么?為什么總是0?
并不是總是0,返回值是進(jìn)程的退出碼!0表示進(jìn)程運(yùn)行結(jié)果正確,非0表示進(jìn)程運(yùn)行結(jié)果錯(cuò)誤。
返回值的意義:返回給上一級(jí)進(jìn)程,用來評(píng)判該進(jìn)程執(zhí)行結(jié)果用的,可以忽略。讓上層能根據(jù)程序的退出碼定位代碼出錯(cuò)的原因。非0值有無數(shù)個(gè),不同的非0值就可以標(biāo)識(shí)不同的錯(cuò)誤原因!!從而給我們的程序在運(yùn)行結(jié)束之后,結(jié)果不正確時(shí),方便定位錯(cuò)誤的原因細(xì)節(jié)!
🍥字符串格式查看錯(cuò)誤信息strerror
為了方便我們查看對(duì)應(yīng)的錯(cuò)誤是什么,C庫(kù)提供了一個(gè)函數(shù)strerror,將錯(cuò)誤以字符串的形式打印。

🍥退出碼講解
退出碼在status參數(shù)中!
status并不是按照整數(shù)來整體使用的!而是按照bit位的方式,將32個(gè)bit位進(jìn)行劃分(位圖)

上圖是status的低16位示意圖。
系統(tǒng)提供了2個(gè)宏來獲取退出碼和退出信號(hào):
WIFEXITED(status) //是否正常退出
WEXITSTATUS(status) //若正常退出,獲取退出碼
使用:grep -ER 'xxxx' /usr/include 進(jìn)行查找?
進(jìn)程異常退出,或者崩潰,本質(zhì)是操作系統(tǒng)通過發(fā)送信號(hào)殺掉了你的進(jìn)程!!
🍙進(jìn)程常見退出方法
?
🍥_exit函數(shù)——系統(tǒng)調(diào)用接口
參數(shù):status 定義了進(jìn)程的終止?fàn)顟B(tài),父進(jìn)程通過wait來獲取該值。
#include<stdio.h>
#include<unistd.h> int main()
{ _exit(-1);
}

將status設(shè)為-1,補(bǔ)碼為全1,但是只有8位,所以退出碼顯示為255(1111 1111)
🍥exit函數(shù)——C庫(kù)函數(shù)
exit最后也會(huì)調(diào)用_exit,但在調(diào)用前還刷新了緩沖區(qū)。
🌰觀察_exit和exit的區(qū)別:
/*_exit測(cè)試*/
#include<stdio.h>
#include<unistd.h> int main()
{ printf("hello"); _exit(0);
}/*exit測(cè)試*/
#include<stdio.h>
#include<stdlib.h> int main()
{ printf("hello"); exit(0);
}

printf輸出是對(duì)stdout標(biāo)準(zhǔn)輸出文件寫入,stdout文件的緩沖區(qū)刷新策略是行刷新,即遇到\n刷新!
測(cè)試中沒有\(zhòng)n,也就不會(huì)刷新緩沖區(qū),也就不會(huì)顯示hello。
????????但是通過結(jié)果圖我們可以看到系統(tǒng)調(diào)用_exit沒有打印,C庫(kù)函數(shù)exit有打印,也就是說exit還刷新了緩沖區(qū)。
🍥return退出
🌰return等同于執(zhí)行exit
#include<stdio.h> int main()
{ printf("hello"); return 0;
}

現(xiàn)象和exit一樣,main返回值當(dāng)做exit的參數(shù)。
4??進(jìn)程等待
🍙進(jìn)程等待必要性
🍙進(jìn)程等待的方法
🍥wait方法
參數(shù):輸出型參數(shù),獲取子進(jìn)程退出狀態(tài),不關(guān)心則可以設(shè)置為NULL
返回值:成功,返回被等待進(jìn)程的pid;失敗,則返回-1。
🌰wait系統(tǒng)接口的阻塞式測(cè)試:


🍥waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:當(dāng)正常返回的時(shí)候waitpid返回收集到的子進(jìn)程的進(jìn)程ID;如果設(shè)置了選項(xiàng)WNOHANG,而調(diào)用中waitpid發(fā)現(xiàn)沒有已退出的子進(jìn)程可收集,則返回0;如果調(diào)用中出錯(cuò),則返回-1,這時(shí)errno會(huì)被設(shè)置成相應(yīng)的值以指示錯(cuò)誤所在;
pid:pid=-1,等待任一個(gè)子進(jìn)程。與wait等效。pid>0.等待其進(jìn)程ID與pid相等的子進(jìn)程。
status:輸出型參數(shù)!WIFEXITED(status): 若為正常終止子進(jìn)程返回的狀態(tài),則為真。(查看進(jìn)程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子進(jìn)程退出碼。(查看進(jìn)程的退出碼)
options:默認(rèn)為0,表示阻塞等待WNOHANG(非阻塞等待): 若pid指定的子進(jìn)程沒有結(jié)束,則waitpid()函數(shù)返回0,不予以等待。若正常結(jié)束,則返回該子進(jìn)程的ID。waitpid(pid,NULL,0)==wait(NULL)
🌰waitpid系統(tǒng)接口的阻塞式測(cè)試:

?結(jié)果與wait一致。
🌰?等待回收僵尸進(jìn)程:


🌰waitpid非阻塞式輪詢檢測(cè)測(cè)試:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>//非阻塞等待測(cè)試代碼int main()
{pid_t id=fork();if(id==0){//子進(jìn)程int cnt=5;while(cnt){printf("我是子進(jìn)程:%d\n",cnt--);sleep(1);}exit(11); //退出碼11,僅用來測(cè)試}else{int quit=0;while(!quit){int status=0;pid_t res=waitpid(-1,&status,WNOHANG);//以非阻塞方式等待if(res>0){//等待成功&&子進(jìn)程退出printf("等待子進(jìn)程退出成功,退出碼:%d\n",WEXITSTATUS(status));quit=1;}else if(res==0){//等待成功&&但子進(jìn)程并未退出printf("子進(jìn)程還在運(yùn)行中,暫時(shí)還沒有退出,父進(jìn)程可以再等一等,處理其他事情\n");}else {//等待失敗printf("wait失敗!\n");quit=1;}sleep(1);}}
}
問題:父進(jìn)程通過wait/waitpid可以拿到子進(jìn)程的退出結(jié)果,為什么要用wait/waitpid函數(shù)呢?直接全局變量不行嗎?
答:不行,因?yàn)檫M(jìn)程具有獨(dú)立性,數(shù)據(jù)就要發(fā)生寫實(shí)拷貝,父進(jìn)程無法拿到正確的退出結(jié)果。
問題:既然進(jìn)程是具有獨(dú)立性的,進(jìn)程退出碼,不也是子進(jìn)程的數(shù)據(jù)嗎?父進(jìn)程又憑什么拿到呢?
答:僵尸進(jìn)程:至少要保留該進(jìn)程的PCB信息!task_struct里面保留了任何進(jìn)程退出時(shí)的退出結(jié)果信息!!
wait/waitpid本質(zhì)是讀取子進(jìn)程的task_struct結(jié)構(gòu)中的(退出碼:exit_code,退出信號(hào)exit_signal)
5??進(jìn)程程序替換
之前:fork()之后,父子各自執(zhí)行父進(jìn)程代碼的一部分;父子代碼共享,數(shù)據(jù)寫時(shí)拷貝各自一份!
現(xiàn)在:如果子進(jìn)程就想執(zhí)行一個(gè)全新的程序呢?
想有自己的代碼!就需要進(jìn)程的程序替換,來完成這個(gè)功能。
🍙替換原理
?

🍙替換函數(shù)
函數(shù)名 | 參數(shù)格式 | 是否帶路徑 | 是否使用當(dāng)前環(huán)境變量 |
execl | 列表 | 不是 | 是 |
execlp | 列表 | 是 | 是 |
execle | 列表 | 不是 | 不是,需自己組裝環(huán)境變量 |
execv | 數(shù)組 | 不是 | 是 |
execvp | 數(shù)組 | 是 | 是 |
execvpe | 數(shù)組 | 是 | 不是,需自己組織環(huán)境變量 |
下面我們對(duì)各函數(shù)進(jìn)行測(cè)試,測(cè)試使用ls命令替換程序。模板如下:

🍥execl函數(shù)
🌰execl的使用測(cè)試:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>int main(int argc,char* argv[],char* env[])
{pid_t id=fork();if(id==0){//子進(jìn)程printf("子進(jìn)程開始運(yùn)行,pid:%d\n",getpid());sleep(3);char* const _argv[]={(char*) "ls",(char*) "-a",(char*) "-l",NULL};//execl函數(shù),傳遞參數(shù)列表execl("/usr/bin/ls","ls","-a","-l",NULL);exit(1);}else{//父進(jìn)程printf("父進(jìn)程開始運(yùn)行,pid:%d\n",getpid());int status=0;pid_t id=waitpid(-1,&status,0);//阻塞等待if(id>0){printf("wait seccess,exit code:%d\n",WEXITSTATUS(status));}}return 0;
}

execl是程序替換,調(diào)用該函數(shù)成功后,會(huì)將當(dāng)前進(jìn)程的所有的代碼和數(shù)據(jù)都進(jìn)行替換,包括以及執(zhí)行的和沒有執(zhí)行的!(一旦調(diào)用成功,后面的所有代碼都不會(huì)被執(zhí)行!)?
🍥execv函數(shù)
🌰execv的使用測(cè)試:
//execv函數(shù),傳遞數(shù)組
execv("/usr/bin/ls",_argv);

🍥execlp函數(shù)
🌰execlp的使用測(cè)試:
//execlp函數(shù),傳遞文件名(無需路徑)和參數(shù)列表
execlp("ls","ls","-a","-l",NULL);

🍥execle函數(shù)
🌰execle的使用測(cè)試:
//execle函數(shù),傳遞參數(shù)列表和組裝的環(huán)境變量
execle("/usr/bin/ls","ls","-a","-l",NULL,env);

🍥execvp函數(shù)
🌰execvp的使用測(cè)試:
//使用execvp函數(shù),傳遞文件名和數(shù)組
execvp("ls",_argv);

🍥execvpe函數(shù)
🌰execvpe的使用測(cè)試:
//execvpe函數(shù),傳遞文件名,數(shù)組,需組織環(huán)境變量
execvpe("ls",_argv,env);

?
🍥execve函數(shù)(系統(tǒng)調(diào)用)
上面是exec系列的函數(shù),事實(shí)上他們都調(diào)用execve,只有execve是真正的系統(tǒng)調(diào)用
函數(shù)名 | 參數(shù)格式 | 是否帶路徑 | 是否使用當(dāng)前環(huán)境變量 |
execve | 數(shù)組 | 不是 | 不是,需自己組織環(huán)境變量 |

🌰execve的使用測(cè)試:
exec.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>int main(int argc,char* argv[],char* env[])
{pid_t id=fork();if(id==0){//子進(jìn)程printf("子進(jìn)程開始運(yùn)行,pid:%d\n",getpid());sleep(3);char* const _argv[]={(char*) "ls",(char*) "-a",(char*) "-l",NULL};char* const _argv_mycmd[]={(char*)"mycmd",(char*)"-a",NULL};char* const _env_mycmd[]={(char*)"My_Path=11111",NULL};//系統(tǒng)調(diào)用execveexecve("./mycmd",_argv_mycmd,_env_mycmd);exit(1);}else{//父進(jìn)程printf("父進(jìn)程開始運(yùn)行,pid:%d\n",getpid());int status=0;pid_t id=waitpid(-1,&status,0);//阻塞等待if(id>0){printf("wait seccess,exit code:%d\n",WEXITSTATUS(status));}}return 0;
}
?mycmd.c:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>int main(int argc,char* argv[])
{if(argc!=2){printf("can not execute!\n");exit(1);}printf("獲取環(huán)境變量:My_Path:%s\n",getenv("My_Path"));if(strcmp(argv[1],"-a")==0){printf("hello a!\n");}else if(strcmp(argv[1],"-b")==0){printf("hello b!\n");}else{printf("default!\n");}return 0;
}

成功將子進(jìn)程程序替換為mycmd。
問題:為什么要?jiǎng)?chuàng)建子進(jìn)程,在子進(jìn)程中進(jìn)行進(jìn)程替換?
——如果不創(chuàng)建,那么我們替換的進(jìn)程只能是父進(jìn)程,如果創(chuàng)建了,替換的進(jìn)程就是子進(jìn)程,而不影響父進(jìn)程。為了不想影響父進(jìn)程,我們想讓父進(jìn)程聚焦在讀取數(shù)據(jù),解析數(shù)據(jù),指派進(jìn)程執(zhí)行代碼的功能!
文末結(jié)語(yǔ),本篇結(jié)合前2篇內(nèi)容詳細(xì)講解了進(jìn)程控制,包含:進(jìn)程創(chuàng)建,進(jìn)程終止,終止碼,_exit,進(jìn)程等待的必要性以及方法(wait,waitpid)阻塞式等待和非阻塞式等待,進(jìn)程程序替換的替換原理以及6大替換函數(shù)和系統(tǒng)調(diào)用execve替換子進(jìn)程程序,圖文并茂,使用例子測(cè)試代碼。