Linux程序替換方式
創(chuàng)建子進(jìn)程的目的?
目的:為了幫助父進(jìn)程完成一些特定的任務(wù);
子進(jìn)程幫助父進(jìn)程完成任務(wù)的方式有那些?
1、執(zhí)行一段父進(jìn)程的代碼;(這是我們初學(xué)者經(jīng)常使用子進(jìn)程的方式):
2、讓子進(jìn)程執(zhí)行一段與父進(jìn)程完全不一樣、全新的代碼;
那么如何做到讓子進(jìn)程執(zhí)行一段全新的代碼呢?
對子進(jìn)程實現(xiàn)程序替換;
程序替換
如何實現(xiàn)程序替換?
Linux給我們提供了7個接口:
#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 execvpe(const char *file, char *const argv[], char *const envp[]); int execve(const char *path, char *const argv[], char *const envp[]);
這些函數(shù)叫做exec函數(shù)組,我們暫且先不詳細(xì)講解著些函數(shù)的具體用法,我們后文在重點(diǎn)講解;
我們現(xiàn)在只需知道通過調(diào)用這7個函數(shù)中的任意一個就可以完成程序替換;
什么是程序替換?
先見一見單進(jìn)程版本的程序替換
為此我們先從最簡單的ececl()函數(shù)講解著走:
execl函數(shù)第一個參數(shù)path,表示我們要替換的程序在哪里,第二個參數(shù)arg表示我們想用怎么樣的方式運(yùn)行我們的程序!寫完記得在傳個參數(shù)NULL結(jié)尾!
test代碼:
運(yùn)行結(jié)果如下:
現(xiàn)象:我們觀察到了我們的進(jìn)程執(zhí)行了我們begin的打印,然后又立馬執(zhí)行了ls -a -l
命令,隨之整個進(jìn)程就運(yùn)行結(jié)束了,我們發(fā)現(xiàn)并沒有像我們想象的那樣接著運(yùn)行我們所寫的printf(“end…\n”);語句;這是為什么?
想要弄清楚這些現(xiàn)象產(chǎn)生的原因我們就必須清楚execl()接口進(jìn)行程序替換的原理;
程序替換原理
首先根據(jù)上面的代碼,我們自己寫的代碼(比如:打印begin的語句)在運(yùn)行起來的時候就已經(jīng)是一個進(jìn)程,那么這時候該進(jìn)程就已經(jīng)有了自己的內(nèi)核數(shù)據(jù)結(jié)構(gòu)了,比如:pcb、進(jìn)程地址空間、頁表等!現(xiàn)在當(dāng)我們的進(jìn)程運(yùn)行到ececl語句的時候,會發(fā)生程
序替換:
當(dāng)我們自己寫的程序運(yùn)行到execl()語句時,就會根據(jù)ececl()的path參數(shù),將ls命令從磁盤加載進(jìn)物理內(nèi)存!加載到物理內(nèi)存的那個地方呢?加載到我們自己寫的程序?qū)?yīng)的物理內(nèi)存上的位置!
也就是說用ls命令的數(shù)據(jù)和代碼,替換原來老的程序的代碼和數(shù)據(jù),物理空間還是原來的物理空間,頁表的映射關(guān)系也基本不變,如果ls命令數(shù)據(jù)和代碼太多了,os會在頁表增加一些映射關(guān)系!然后該進(jìn)程開始重新運(yùn)行一段新的程序!
明白了上面的原理,我們就能明白為什么我們的老程序在運(yùn)行到execl過后,會執(zhí)行一段新的程序!同時由于新程序的代碼和數(shù)據(jù)替換了老程序的代碼和數(shù)據(jù),當(dāng)新程序運(yùn)行結(jié)束過后,并不會再去運(yùn)行原來老的程序的剩下的語句,比如上面代碼中的打
印end語句,因為打印end語句屬于老程序的代碼,而老程序的代碼在execl接口中就被替換了,新程序運(yùn)行完畢,也就表示這個進(jìn)程終止了!新程序的退出碼也就是該進(jìn)程的退出碼了!
換而言之,就是當(dāng)我們的程序利用execl完成程序替換過后,當(dāng)前進(jìn)程的退出碼就由新程序決定了!
為此我們可以查看一下在程序替換過后,進(jìn)程的退出碼!
測試代碼:
當(dāng)然我們也可以讓我們的程序執(zhí)行一個錯誤的ls命令,再次來觀察當(dāng)前進(jìn)程的退出碼:
站在進(jìn)程的角度的角度來看等待程序替換:
現(xiàn)在我是一個進(jìn)程,我的代碼段和數(shù)據(jù)段都在物理內(nèi)存上有一份映射,現(xiàn)在我調(diào)用了execl接口,execl會將我在物理內(nèi)存上的數(shù)據(jù)和代碼用一個新的程序的數(shù)據(jù)和代碼來替換!然后我(當(dāng)前進(jìn)程)開始重新運(yùn)行這段新程序!并且我(當(dāng)前進(jìn)程)的退出碼由這個新程序的main函數(shù)返回值來確定!在整個程序替換期間,我(當(dāng)前進(jìn)程)并沒由被銷毀,我依舊存在!在完成程序替換過后,依舊是我(當(dāng)前進(jìn)程)來運(yùn)行這段新程序!并不會創(chuàng)建一個新的進(jìn)程來運(yùn)行新的代碼!
站在新程序的角度來看待程序替換:
我是一個程序,我安安靜靜的躺在磁盤看“電視”,突然某一天我被execl加載進(jìn)內(nèi)存,讓后某個進(jìn)程就要求我?guī)退k件事;那么我(新程序)被加載進(jìn)內(nèi)存這個動作是由誰完成的? execl!??!
execl就充當(dāng)著這個加載器的角色!
既然我自己寫的程序都能加載新的程序,那么OS?
當(dāng)我們想要運(yùn)行某段程序的時候,OS會首先為我們的程序建立pcb、進(jìn)程地址空間、頁表等內(nèi)核數(shù)據(jù)結(jié)構(gòu),也就是說這時候進(jìn)程已經(jīng)創(chuàng)建好了,然后在讓當(dāng)前進(jìn)程調(diào)用execl()接口將我們的程序加載進(jìn)內(nèi)存,然后再開始運(yùn)行我們程序!而我們的程序是從main函數(shù)開始的,但是我們的進(jìn)程是先調(diào)用的execl過后我們的程序才開始運(yùn)行起來的,那么換而言之在execl內(nèi)部,幫助我們完成程序替換過后,execl會調(diào)用該程序的main函數(shù),然后讓該程序成功運(yùn)行起來!
多進(jìn)程版本的程序替換
上面我們講解了單進(jìn)程版本的程序替換和程序替換的原理,接下來我們來嘗試一下多進(jìn)程版本的程序替換:
也就是說我們讓我們的子進(jìn)程去執(zhí)行一段與父進(jìn)程完全不一樣的代碼:
測試代碼:
當(dāng)然,我們也可以讓子進(jìn)程去運(yùn)行我們自己寫的程序,無論我們的程序是用什么語言寫的!
比如,現(xiàn)在我用C語言寫一個程序,去運(yùn)行一個C++寫的程序:
測試:
被子進(jìn)程運(yùn)行的程序:
主程序:
運(yùn)行結(jié)果:
接下來我們來講解一下多進(jìn)程進(jìn)行程序替換的原理:
首先我們的父進(jìn)程,也就是mytest利用fork函數(shù)創(chuàng)建了一個子進(jìn)程對吧!
那么剛開使的時候,子進(jìn)程會繼承父進(jìn)程的大多數(shù)信息,包括子進(jìn)程會共享著父進(jìn)程的代碼和數(shù)據(jù),通過前面的學(xué)習(xí)我們知道,當(dāng)我們的子進(jìn)程想要修改與父進(jìn)程共享的數(shù)據(jù)時,會發(fā)生寫時拷貝!在物理內(nèi)存中重新找一塊新空間,讓后將將需要修改
的數(shù)據(jù)拷貝到新空間中去,然后修改子進(jìn)程頁表映射到該物理內(nèi)存的映射關(guān)系,然后再讓子進(jìn)程去修改數(shù)據(jù)!以此達(dá)到進(jìn)程之間的相互獨(dú)立!
那么現(xiàn)在也是這樣:剛開始的時候父子進(jìn)程都共享著同一塊物理內(nèi)存的數(shù)據(jù)和代碼:
當(dāng)我們的子進(jìn)程調(diào)用execl函數(shù)進(jìn)行程序替換時,是會用程序的代碼和數(shù)據(jù)來替換子進(jìn)程原來數(shù)據(jù)段和代碼段存的信息的!如果我們直接在“數(shù)據(jù)”和“代碼”這塊空間進(jìn)行替換的話,我們就會將父進(jìn)程的代碼和數(shù)據(jù)也替換掉!從而影響到了進(jìn)程的獨(dú)立性!我們現(xiàn)在的目的是不想影想父進(jìn)程,而讓子進(jìn)程執(zhí)行一段全新的代碼,為此os也會也會觸發(fā)寫時拷貝,當(dāng)我們的子進(jìn)程嘗試修改代碼段和數(shù)據(jù)段的信息時,os也會去重新找一物理內(nèi)存中重新找一塊空間來存儲子進(jìn)程的代碼和數(shù)據(jù),同時修改子進(jìn)程代碼段和數(shù)據(jù)段映射關(guān)系!
重新理解Shell運(yùn)行原理
明白了上面的過程我們就能更好的理解Shell的運(yùn)行原理了,首先shell從命令行接受到我們的命令后會創(chuàng)建一個子進(jìn)程來執(zhí)行我們的命令,然后在讓該子進(jìn)程調(diào)用execl函數(shù)來進(jìn)程程序替換,替換掉子進(jìn)程從Shell哪里繼承下來的代碼和數(shù)據(jù)!然后讓子進(jìn)程開始運(yùn)行這段程序!
execl函數(shù)組
下面我們來正式介紹一下execl函數(shù)組:
int execl(const char *path, const char *arg, …)
參數(shù): path//用于指定我們執(zhí)行的命令在哪里
arg: 可變參數(shù),可以傳任意個參數(shù),該參數(shù)的作用主要是告訴execl()你想怎樣執(zhí)行這段程序,你在命令行是怎么寫的,在arg參數(shù)就怎么寫,注意分割;比如:我們需要讓execl按照ls -a -l
的格式執(zhí)行l(wèi)s命令,那么我們喂給execl的參數(shù)就是(從
第二個參數(shù)起):“ls”、“-a”、“-l”,NULL;一個選項一個字符串,注意當(dāng)我們確定完程序運(yùn)行的格式過后,必須以再傳遞一個NULL結(jié)尾!表示我們已經(jīng)傳遞完當(dāng)前程序的執(zhí)行的格式;
比如:
返回值:
該函數(shù)只會返回-1;由于execl是進(jìn)行程序替換,當(dāng)execl完成程序替換那一刻開始,execl后續(xù)的代碼都被替換成了新程序的代碼和數(shù)據(jù),根本就運(yùn)行不到后續(xù)的代碼和數(shù)據(jù),因此也就無法返回程序替換成功的返回值;當(dāng)我們的程序替換失敗的時候,我們進(jìn)程的老數(shù)據(jù)和代碼并沒由被替換掉,當(dāng)前進(jìn)程依舊按照順序執(zhí)行剩下的代碼,同時才能向上面返回-1來表示程序替換失??!
也就是說,execl程序替換成功是沒有返回值的!如果execl有返回值那么說明程序替換失敗,當(dāng)前進(jìn)程就會執(zhí)行execl后續(xù)的代碼!
int execv(const char *path, char *const argv[]);
我們可以發(fā)現(xiàn)execv接口與execl接口十分相似,但是在參數(shù)上卻并不是一樣的!
execl的參數(shù)可以是任意個,而execv的參數(shù)只有2個;
同時execv的功能與execl的功能一樣,只是在使用上有點(diǎn)區(qū)別!
我們可以看一看exec+l就表示execl,這個l(list)就代表著列表的意思!表示execl的程序運(yùn)行格式以列表的形式傳遞!
exec+v表示execv,這個v(vector)表示數(shù)組的意思,就表示程序運(yùn)行的格式以數(shù)組的形式傳遞;
具體演示:
程序運(yùn)行結(jié)果:
程序依舊正常運(yùn)行;
有了前面的理解后面我們在認(rèn)識其他exec函數(shù)就輕松了:
int execlp(const char *file, const char *arg, …);
- exec+l+p:l表示以列表的形式傳遞程序如何運(yùn)行這個程序!
- p:表示path,表示我們只需告訴execlp我們要運(yùn)行的程序的名稱也就是傳遞file參數(shù),execlp會自動去PATH環(huán)境變量下搜索!
具體演示:
程序運(yùn)行結(jié)果:
int execle(const char *path, const char *arg, …,char *const envp[]);
- l:如何運(yùn)行程序的參數(shù)以列表的形式傳遞;
- e:env表示自己維護(hù)環(huán)境變量
比如:我們可以將當(dāng)前進(jìn)程的環(huán)境變量表傳遞給我們的新程序!
我們的新程序就可以使用這張環(huán)境變量表:
子進(jìn)程去替換的程序:
主程序:
程序運(yùn)行結(jié)果:
我們也可以向環(huán)境變量里面加一點(diǎn)東西進(jìn)去:
這里我們就需要使用putenv()這個函數(shù)了,putenv()功能是向環(huán)境變量表中導(dǎo)入一個環(huán)境變量!
講解到這里,其他的execl函數(shù)也就依此類推了;
只不過我們需要注意一下,在exec函數(shù)組中
只有int execve(const char *filename, char *const argv[])是真正的系統(tǒng)調(diào)用!
其他的exec函數(shù)是基于該系統(tǒng)調(diào)用進(jìn)行的封裝!
簡易版Shell
#include<stdio.h> #include<stdlib.h> #include<sys/wait.h> #include<sys/types.h> #include<unistd.h> #include<string.h> #include<stdbool.h> #define COMMOD_NUM 256 #define ARGV_NUM 64 bool Strtok(char*commod,char**argv) { //先跳過空格 size_t i=0; size_t len=strlen(commod); size_t k=0; while(i<len&&commod[i]==' ') i++; if(i>=len) return false; size_t begin=i; size_t end=begin; while(begin<len) { while(commod[end]!=' '&&commod[end]!='\0') end++; commod[end]='\0'; argv[k++]=commod+begin; begin=end+1; end=begin; } argv[k]=NULL; return true; } extern char**environ; int main() { while(1) { printf("[cxk@VM-12-16-centos myshell]$ "); char commod[COMMOD_NUM]={0};//用于接受從命令行輸入的命令 char*argv[ARGV_NUM]={NULL};//用于存儲將commod切割成一個一個字符串的指針 fgets(commod,COMMOD_NUM,stdin); commod[strlen(commod)-1]='\0'; //分割字符串 if(Strtok(commod,argv)==false) continue; //創(chuàng)建子進(jìn)程 pid_t id=fork(); if(id==0) { int n= execvp(argv[0],argv); if(n==-1) { printf("-bash: %s: command not found\n",argv[0]); exit(1); } } //父進(jìn)程 waitpid(-1,NULL,0); } return 0; }
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
apache與iis下讓html格式的頁面也同樣具有shtml的動態(tài)解析
apache下讓html格式的頁面也同樣具有shtml的動態(tài)解析,方便有此需要的朋友。2011-03-03CentOS配置本地yum源/阿里云yum源/163yuan源并配置yum源的優(yōu)先級
這篇文章主要介紹了CentOS配置本地yum源/阿里云yum源/163yuan源并配置yum源的優(yōu)先級,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09在Ubuntu 16.10安裝mysql workbench報未安裝軟件包 libpng12-0錯誤的解決方法
這篇文章主要介紹了在Ubuntu 16.10安裝mysql workbench報未安裝軟件包 libpng12-0錯誤的解決方法的相關(guān)資料,需要的朋友可以參考下2016-11-11Centos7.0安裝ceph(JEWEL)及以上版本的實例解析
這篇文章主要介紹了Centos7.0安裝ceph(JEWEL)及以上版本的實例解析,需要的朋友可以參考下2018-02-02Linux使用其他用戶(非root用戶)設(shè)置root權(quán)限及免密(Centos7為例)
這篇文章主要介紹了Linux使用其他用戶(非root用戶)設(shè)置root權(quán)限及免密(Centos7為例),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05Linux在服務(wù)器多節(jié)點(diǎn)下面實現(xiàn)快速查找日志
在多節(jié)點(diǎn)分布式系統(tǒng)中,通過使用find和grep命令組合,可以實現(xiàn)高效的日志搜索,先定位到具體日期的文件夾,再執(zhí)行命令全面掃描各個服務(wù)器節(jié)點(diǎn)下的日志文件,從而簡化日志查詢過程2024-11-11