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