城建設(shè)投資公司網(wǎng)站最近國(guó)內(nèi)新聞
文章目錄
- 進(jìn)程程序替換
- 替換原理
- 替換函數(shù)
- 函數(shù)返回值
- 函數(shù)命名理解
- 在makefile文件中一次生成兩個(gè)可執(zhí)行文件
- 總結(jié):
- 程序替換時(shí)運(yùn)行其它語(yǔ)言程序
進(jìn)程程序替換
程序要運(yùn)行要先加載到內(nèi)存當(dāng)中 , 如何做到? 加載器加載進(jìn)來(lái),然后程序替換
為什么? ->馮諾依曼 因?yàn)镃PU讀取數(shù)據(jù)的時(shí)候只能和內(nèi)存打交道 CPU執(zhí)行程序的時(shí)候離內(nèi)存最近.CPU要從內(nèi)存拿數(shù)據(jù)和代碼,前提條件是外設(shè)當(dāng)中的可執(zhí)行程序加載到內(nèi)存中
替換原理
用fork創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支), 那如果我們想讓子進(jìn)程執(zhí)行一個(gè)“全新”的程序要怎么做呢? 我們就需要通過(guò)程序替換
實(shí)現(xiàn)
子進(jìn)程往往要調(diào)用一種exec*函數(shù)以執(zhí)行另一個(gè)程序
- 當(dāng)進(jìn)程調(diào)用一種exec*函數(shù)時(shí)**,該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動(dòng)例程開(kāi)始執(zhí)行**
- 調(diào)用exec并不創(chuàng)建新進(jìn)程,所以調(diào)用exec前后該進(jìn)程的id并未改變
什么叫做進(jìn)程程序替換?
進(jìn)程不變,僅僅替換當(dāng)前進(jìn)程的代碼和數(shù)據(jù)的技術(shù),并沒(méi)有創(chuàng)建新的進(jìn)程(所以進(jìn)程的id沒(méi)有改變) 叫做進(jìn)程程序替換
老程序的殼子不變,把新程序的代碼和數(shù)據(jù)替換進(jìn)去, 進(jìn)程替換是把磁盤(pán)上的程序加載到內(nèi)存中
進(jìn)程程序替換時(shí)有沒(méi)有創(chuàng)建新的進(jìn)程?
進(jìn)程程序替換之后,該進(jìn)程對(duì)應(yīng)的PCB.進(jìn)程地址空間以及頁(yè)表等數(shù)據(jù)結(jié)構(gòu)都沒(méi)有發(fā)生改變,只是進(jìn)程在物理內(nèi)存當(dāng)中的數(shù)據(jù)和代碼發(fā)生了改變,所以并沒(méi)有創(chuàng)建新的進(jìn)程,而且進(jìn)程程序替換前后該進(jìn)程的pid并沒(méi)有改變
程序替換的本質(zhì)是什么?
本質(zhì)是把程序的代碼+數(shù)據(jù)加載到特定進(jìn)程的上下文中, C/C++程序要運(yùn)行,必須要先加載到內(nèi)存中
程序運(yùn)行是如何加載到內(nèi)存中的呢?
通過(guò)加載器,加載器的底層原理就是一系列的exec*程序替換函數(shù)
直接打開(kāi)和程序替換打開(kāi)有什么區(qū)別?
直接打開(kāi)是要形成新的進(jìn)程,而進(jìn)程替換不形成新進(jìn)程, ->沒(méi)有PCB的創(chuàng)建,先有的進(jìn)程然后才能執(zhí)行程序替換
我們fork子進(jìn)程,然后讓子進(jìn)程進(jìn)行程序替換,會(huì)影響父進(jìn)程嗎? 父子代碼是共享的嗎?
父進(jìn)程不會(huì)受影響,因?yàn)檫M(jìn)程是具有獨(dú)立性的!沒(méi)有修改數(shù)據(jù)的時(shí)候,父子共享代碼,修改就會(huì)發(fā)生寫(xiě)時(shí)拷貝 進(jìn)程替換會(huì)更改代碼區(qū)的代碼,要發(fā)生寫(xiě)時(shí)拷貝,這樣就可以讓子進(jìn)程執(zhí)行全新的程序
子進(jìn)程剛被創(chuàng)建時(shí),與父進(jìn)程共享代碼和數(shù)據(jù),但當(dāng)子進(jìn)程需要進(jìn)行進(jìn)程程序替換時(shí),也就意味著子進(jìn)程需要對(duì)其數(shù)據(jù)和代碼進(jìn)行寫(xiě)入操作,這時(shí)便需要將父子進(jìn)程共享的代碼和數(shù)據(jù)進(jìn)行寫(xiě)時(shí)拷貝,此后父子進(jìn)程的代碼和數(shù)據(jù)也就分離了,因此子進(jìn)程進(jìn)行程序替換后不會(huì)影響父進(jìn)程的代碼和數(shù)據(jù)
替換函數(shù)
其實(shí)有六種以ex為ec開(kāi)頭的函數(shù),統(tǒng)稱(chēng)exec函數(shù)
#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[]);
函數(shù)返回值
- 這些函數(shù)如果調(diào)用成功 則加載新的程序從啟動(dòng)代碼開(kāi)始執(zhí)行,不再返回.
- 如果調(diào)用出錯(cuò)則返回-1
- 所以exec函數(shù)只有出錯(cuò)的返回值而沒(méi)有成功的返回值, exec系列的函數(shù),只要返回了,就一定是調(diào)用失敗
關(guān)于exec*
系列函數(shù)的返回值:
只要進(jìn)程的程序替換成功,就不會(huì)執(zhí)行后序的代碼, 所以,exec*函數(shù)成功是不需要進(jìn)行返回值檢測(cè),只要返回了,就一定是因?yàn)檎{(diào)用失敗了,直接退出程序就好
調(diào)用失敗的例子:
如何證明它是調(diào)用失敗呢?
函數(shù)命名理解
上述函數(shù)名看起來(lái)很容易混亂,我們理解他們的命名含義就好記了
替換函數(shù)接口后后綴 | 含義 |
---|---|
l(list) | 參數(shù)采用列表方式 |
v(vector) | 參數(shù)采用數(shù)組方式 |
p(path) | 自動(dòng)搜索環(huán)境變量PATH,進(jìn)行程序查找 |
e(env) | 自己維護(hù)環(huán)境變量,或者說(shuō)自定義環(huán)境變量,可以傳入自己設(shè)置的環(huán)境變量 |
函數(shù)名 | 參數(shù)格式 | 是否帶路徑 | 是否使用當(dāng)前環(huán)境變量 |
---|---|---|---|
execl | 列表 | 否 | 是 |
execlp | 列表 | 是 | 是 |
execle | 列表 | 否 | 否,需自己組裝環(huán)境變量 |
execv | 數(shù)組 | 否 | 是 |
execvp | 數(shù)組 | 是 | 是 |
execve | 數(shù)組 | 否 | 否,需自己組裝環(huán)境變量 |
事實(shí)上,只有execve才是真正的系統(tǒng)調(diào)用,其它五個(gè)函數(shù)最終都是調(diào)用的execve,所以execve在man手冊(cè)的第2節(jié),而其它五個(gè)函數(shù)在man手冊(cè)的第3節(jié),也就是說(shuō)其他五個(gè)函數(shù)實(shí)際上是對(duì)系統(tǒng)調(diào)用execve進(jìn)行了封裝,以滿足不同用戶的不同調(diào)用場(chǎng)景的
- 手冊(cè)3:代表庫(kù)函數(shù) 手冊(cè)2:代表系統(tǒng)調(diào)用
用一張圖描述exec系列函數(shù)之間的關(guān)系:
execl
int execl(const char *path, const char *arg, ...)
第一個(gè)參數(shù)是要執(zhí)行程序的路徑,第二個(gè)后面的是可變參數(shù)列表,表示你要如何執(zhí)行這個(gè)程序, 注意以NULL為參數(shù)傳遞的結(jié)尾
后綴為l
:即參數(shù)用列表傳遞
例子:
execl("/usr/bin/ls","ls","-a","-l",NULL);//相當(dāng)于執(zhí)行l(wèi)s -a -l
執(zhí)行結(jié)果:
![]()
execv
int execv(const char *path, char *const argv[])
第一個(gè)參數(shù)是要執(zhí)行程序的路徑,第二個(gè)參數(shù)是一個(gè)指針數(shù)組,數(shù)組當(dāng)中的內(nèi)容表示你要如何執(zhí)行這個(gè)程序,數(shù)組以NULL結(jié)尾
后綴為v
:即參數(shù)用數(shù)組傳遞
//例子
char* argv[] = {"ls","-a","-l",NULL};
execv("/usr/bin/ls",argv);//相當(dāng)于執(zhí)行l(wèi)s -a -l
使用例子:
main函數(shù)是可以攜帶參數(shù)的,argv是一個(gè)指針數(shù)組,指針指向命令行參數(shù)字符串,我們可以理解為:通過(guò)exec函數(shù),把a(bǔ)rgv給了ls程序
execlp && execvp
后綴為p
:表示會(huì)自動(dòng)在環(huán)境變量PATH中搜索,只需要直到程序名即可
**int execlp(const char file, const char arg, …)
第一個(gè)參數(shù)是要執(zhí)行程序的名字(只需要寫(xiě)名稱(chēng),不需要帶路徑,會(huì)自動(dòng)找),第二個(gè)參數(shù)是可變參數(shù)列表,表示你要如何執(zhí)行這個(gè)程序,并以NULL結(jié)尾
//例子:
execlp("ls","ls","-a","-l",NULL); //相當(dāng)于執(zhí)行l(wèi)s -a -l
//第一個(gè)ls表示你要執(zhí)行誰(shuí),execlp會(huì)自動(dòng)在環(huán)境變量PATH中根據(jù)這個(gè)程序名搜索這個(gè)程序在什么位置
//后面的ls表示我們要如何執(zhí)行它
例子:
**int execvp(const char file, char const argv[])
第一個(gè)參數(shù)是要執(zhí)行程序的名字,第二個(gè)參數(shù)是一個(gè)指針數(shù)組,數(shù)組當(dāng)中的內(nèi)容表示你要如何執(zhí)行這個(gè)程序,數(shù)組以NULL結(jié)尾
//例子
char* argv[] = {"ls","-a","-l",NULL};
execvp("ls",argv);//相當(dāng)于執(zhí)行l(wèi)s -a -l
例子:
execle && execve
后綴為e
:表示會(huì)自己維護(hù)環(huán)境變量,用自己設(shè)置的環(huán)境變量
**int execle(const char *path, const char arg, …,char const envp[])
第一個(gè)參數(shù)是要執(zhí)行程序的路徑,第二個(gè)參數(shù)是可變參數(shù)列表,表示你要如何執(zhí)行這個(gè)程序,并以NULL結(jié)尾,第三個(gè)參數(shù)是你自己設(shè)置的環(huán)境變量
//例如有兩個(gè)文件:myload 和myexe,如果我們?cè)趍yload中設(shè)置了環(huán)境變量,在myexe文件就可以使用該環(huán)境變量//myload:
char* env[] = {"MYPATH:hello world",NULL};
execle("./myexe","myexe",NULL,env) //注意這里先傳NULL結(jié)尾 再傳env
// 因?yàn)橐獔?zhí)行程序所在的位置已經(jīng)找到了,所以第二個(gè)參數(shù)可以寫(xiě)成:./myexe 也可以直接寫(xiě)成myexe
//myexe
可以使用myload文件中的環(huán)境變量MYPATH
如果我們直接執(zhí)行myexe: 默認(rèn)使用的是系統(tǒng)的環(huán)境變量
我們運(yùn)行myload去運(yùn)行myexe,執(zhí)行的就是我們導(dǎo)入的環(huán)境變量
**int execve(const char *path, char const argv[], char const envp[])
第一個(gè)參數(shù)是要執(zhí)行程序的路徑,第二個(gè)參數(shù)傳你要怎么執(zhí)行的,是一個(gè)指針數(shù)組,數(shù)組當(dāng)中的內(nèi)容表示你要如何執(zhí)行這個(gè)程序,數(shù)組以NULL結(jié)尾,第三個(gè)參數(shù)是你自己設(shè)置的環(huán)境變量
//例如有兩個(gè)文件:myload 和myexe,如果我們?cè)趍yload中設(shè)置了環(huán)境變量,在myexe文件就可以使用該環(huán)境變量//myload:
char* env[] = {"MYPATH:hello world",NULL};
char* argv[]={"myexe",NULL};
execve("./myexe",argv,env);//myexe
可以使用myload文件中的環(huán)境變量MYPATH,MYVAL
exec*也可也調(diào)用我們自己的程序
在makefile文件中一次生成兩個(gè)可執(zhí)行文件
makefile默認(rèn)會(huì)形成在依賴(lài)關(guān)系當(dāng)中形成第一個(gè)它所碰到的依賴(lài)文件 (makefile默認(rèn)會(huì)形成第一個(gè)碰到的目標(biāo)文件)
那么如何在一個(gè)makefile文件中一次形成兩個(gè)可執(zhí)行文件呢?
.PHONY:all
all:myload myexemyload:myload.cgcc -0 $@ $^ -std=c99
myexe:myexe.cgcc -0 $@ $^ -std=c99.PHONY:clear
clear:
rm -f myload myexe
解析:利用偽目標(biāo)總是被執(zhí)行的特點(diǎn), all依賴(lài)的是myexe和myload, 因?yàn)閍ll 沒(méi)有依賴(lài)方法,所以不會(huì)生成all. 但是因?yàn)橛幸蕾?lài)文件,所以makefile在執(zhí)行的時(shí)候,一定想先形成的是all,形成all就得先形成myexe和myload. 所以會(huì)分別執(zhí)行myexe和myload的gcc代碼 而因?yàn)閍ll沒(méi)有依賴(lài)方法,所以形成myexe和myload之后,并不會(huì)再形成all
如何我們想形成5個(gè)呢?
只需要把依賴(lài)文件往all后面不斷去添加, all : my1 my2 my3 然后后面再給依賴(lài)關(guān)系和依賴(lài)方法
總結(jié):
所以的接口,看起來(lái)沒(méi)有太大的差別,只有一個(gè)就是參數(shù)的不同,為什么會(huì)有這么多接口?是為了滿足不同的應(yīng)用場(chǎng)景
程序替換時(shí)運(yùn)行其它語(yǔ)言程序
例子:
想要執(zhí)行的是/usr/bin/python3
路徑下的python程序, 如何執(zhí)行呢? python test.py