安徽城鄉(xiāng)建設(shè)廳網(wǎng)站一鍵關(guān)鍵詞優(yōu)化
0.引入
創(chuàng)建子進(jìn)程的目的是什么?
就是為了讓子進(jìn)程幫我執(zhí)行特定的任務(wù)
讓子進(jìn)程執(zhí)行父進(jìn)程的一部分代碼
如果子進(jìn)程想執(zhí)行一個(gè)全新的程序代碼呢? 那么就要使用進(jìn)程的程序替換
為什么要有程序替換?
也就是說子進(jìn)程想執(zhí)行一個(gè)全新的程序代碼!

這份代碼看似是子進(jìn)程的代碼,其實(shí)也是父進(jìn)程的代碼,只是父進(jìn)程通過id的值進(jìn)行判斷,讓子進(jìn)程運(yùn)行。
1.替換原理
用fork創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支),子進(jìn)程往往要調(diào)用一種exec函數(shù) 以執(zhí)行另一個(gè)程序。
當(dāng)進(jìn)程調(diào)用一種exec函數(shù)時(shí),該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,從新程序的啟動 例程開始執(zhí)行。調(diào)用exec并不創(chuàng)建新進(jìn)程,所以調(diào)用exec前后該進(jìn)程的id并未改變。

2.替換函數(shù)
其實(shí)有六種以exec開頭的函數(shù),統(tǒng)稱exec函數(shù):
使用man手冊查看相關(guān)exec函數(shù)


execl最后的...參數(shù),表示可變參數(shù)列表,可以給C函數(shù)傳遞任意的參數(shù)
execl
int execl(const char *path, const char *arg, ...);
path:新程序的路徑和名稱。
arg0 ~ argn:新程序的命令行參數(shù)列表,以 NULL 結(jié)尾。arg0 表示新程序的名稱,arg1 ~ argn 表示新程序的各個(gè)參數(shù)。


為什么只看到了begin()... end...為什么不顯示呢?
執(zhí)行程序替換,新的代碼和數(shù)據(jù)都被加載了,后續(xù)的代碼屬于老代碼,直接被替換了,沒機(jī)會執(zhí)行了
并且程序替換是整體替換,不能局部替換!!!
ls是磁盤上的可執(zhí)行程序,execl調(diào)用了ls-la
把當(dāng)前的代碼和數(shù)據(jù),從execl到磁盤中進(jìn)行替換
問題:進(jìn)程的程序替換,有沒有創(chuàng)建新的進(jìn)程呢?
沒有!!
為什么呢?很簡單,因?yàn)槲抑皇前岩粋€(gè)新的程序加載到我們當(dāng)前進(jìn)程所對應(yīng)的代碼和數(shù)據(jù)段,我就讓CPU去調(diào)度當(dāng)前進(jìn)程,就可以跑起來了。其中,我們并沒有創(chuàng)建新的進(jìn)程。當(dāng)前的進(jìn)程PID沒有變化
這是站在進(jìn)程的角度來看的
那么站在程序的角度呢? -- 這個(gè)程序被加載到內(nèi)存里了
所以,我們也可以說execl是加載器
問題:當(dāng)創(chuàng)建進(jìn)程的時(shí)候,先加載進(jìn)程數(shù)據(jù)結(jié)構(gòu),還是先加載代碼和數(shù)據(jù)?
在創(chuàng)建進(jìn)程時(shí),操作系統(tǒng)通常會先創(chuàng)建進(jìn)程數(shù)據(jù)結(jié)構(gòu),然后再加載代碼和數(shù)據(jù)。

這里代碼,子進(jìn)程里的execl替換代碼,會影響父進(jìn)程嗎?

不會,程序替換只會影響調(diào)用進(jìn)程,進(jìn)程具有獨(dú)立性
子進(jìn)程加載新程序的時(shí)候,是需要進(jìn)行程序替換的,發(fā)生寫時(shí)拷貝(子進(jìn)程執(zhí)行的可是全新的程序,新的代碼,寫時(shí)拷貝在代碼區(qū)也可以發(fā)生)
execl函數(shù)調(diào)用失敗,會發(fā)生什么?
我們寫一個(gè)代碼,讓程序故意發(fā)生錯(cuò)誤


父進(jìn)程獲取子進(jìn)程退出碼



接下來開始熟悉所有的接口

execv
int execv(const char *path, char *const argv[]);
execv() 函數(shù)接受兩個(gè)參數(shù)。第一個(gè)參數(shù)是要執(zhí)行的程序的路徑,第二個(gè)參數(shù)是傳遞給新程序的命令行參數(shù)。這些參數(shù)以一個(gè)字符串?dāng)?shù)組的形式傳遞給函數(shù)。

注意:當(dāng)execv調(diào)用失敗的話,程序繼續(xù)執(zhí)行
execlp
int execlp(const char *file, const char *arg, ...);
其中,file 參數(shù)表示要執(zhí)行的可執(zhí)行文件的名稱,arg 參數(shù)表示傳遞給該可執(zhí)行文件的命令行參數(shù),最后的參數(shù)為可變參數(shù)列表,表示該命令行參數(shù)以 NULL 結(jié)束。
execlp 函數(shù)會在 PATH 環(huán)境變量指定的路徑中查找可執(zhí)行文件,因此無需指定可執(zhí)行文件的完整路徑。如果 PATH 環(huán)境變量中有多個(gè)路徑,則 execlp 函數(shù)會按照路徑的順序查找可執(zhí)行文件。
execlp("ls","ls","-a","-l","-n",NULL);
這里的兩個(gè)ls分別是什么意思?
第一個(gè)是系統(tǒng)環(huán)境變量路徑ls,第二個(gè)是ls指令
等同于以下代碼

execle
int execle(const char *path, const char *arg,..., char * const envp[]);
該函數(shù)接收以下參數(shù):
path:要執(zhí)行的程序的路徑名。
arg0:新程序的第一個(gè)參數(shù),通常是新程序的名稱。
arg1~argn:新程序的參數(shù)列表。
envp:新程序的環(huán)境變量數(shù)組。
該函數(shù)的返回值是一個(gè)整數(shù),如果成功執(zhí)行,則永遠(yuǎn)不會返回。如果出現(xiàn)錯(cuò)誤,則會返回-1,并設(shè)置errno來指示錯(cuò)誤的類型。
envp[]數(shù)組是自定義環(huán)境變量
execvp
函數(shù)原型如下:
int execvp(const char *file, char *const argv[]);
該函數(shù)會在PATH環(huán)境變量指定的路徑中搜索指定的可執(zhí)行文件,并在找到文件后將當(dāng)前進(jìn)程替換為該可執(zhí)行文件。
其中,file參數(shù)是一個(gè)字符串,指定要執(zhí)行的可執(zhí)行文件的路徑和文件名。argv參數(shù)是一個(gè)指向字符串?dāng)?shù)組的指針,其中第一個(gè)字符串表示可執(zhí)行文件的名稱,后面的字符串表示傳遞給可執(zhí)行文件的命令行參數(shù)。
execvpe
函數(shù)原型如下:
int execvpe(const char *file, char *const argv[], char *const envp[]);
file 參數(shù)是要執(zhí)行的可執(zhí)行文件的路徑,argv 參數(shù)是一個(gè)指向參數(shù)列表的指針數(shù)組,envp 參數(shù)是一個(gè)指向環(huán)境變量列表的指針數(shù)組。
execvpe 函數(shù)首先會搜索 PATH 環(huán)境變量中指定的目錄,找到可執(zhí)行文件后,它會用新的進(jìn)程替換當(dāng)前進(jìn)程,并開始執(zhí)行新的程序。在新的進(jìn)程中,參數(shù)和環(huán)境變量都被設(shè)置成 argv 和 envp 中指定的值。
execvpe 函數(shù)和其他 exec 系列函數(shù)的主要區(qū)別在于它會搜索 PATH 環(huán)境變量中指定的目錄來查找可執(zhí)行文件,并且可以設(shè)置環(huán)境變量。
execve
函數(shù)原型如下:
int execve(const char *filename, char *const argv[], char *const envp[]);
filename 參數(shù)是要執(zhí)行的可執(zhí)行文件的路徑,argv 參數(shù)是一個(gè)指向參數(shù)列表的指針數(shù)組,envp 參數(shù)是一個(gè)指向環(huán)境變量列表的指針數(shù)組。
execve 函數(shù)會用新的進(jìn)程替換當(dāng)前進(jìn)程,并開始執(zhí)行新的程序。在新的進(jìn)程中,參數(shù)和環(huán)境變量都被設(shè)置成 argv 和 envp 中指定的值。
execve 函數(shù)不會搜索 PATH 環(huán)境變量中指定的目錄來查找可執(zhí)行文件,它只會使用 filename 參數(shù)中指定的路徑來查找可執(zhí)行文件。如果指定的文件路徑不是一個(gè)可執(zhí)行文件,那么該函數(shù)會返回一個(gè)錯(cuò)誤。
事實(shí)上,只有execve是真正的系統(tǒng)調(diào)用,其它五個(gè)函數(shù)最終都調(diào)用 execve,所以execve在man手冊第2節(jié),其它函數(shù)在man手冊第3節(jié)。這些函數(shù)之間的關(guān)系如下圖所示。下圖exec函數(shù)族 一個(gè)完整的例子:

3.調(diào)用自定義程序
上面都是執(zhí)行命令。能否執(zhí)行我自己寫的程序呢?
用C語言調(diào)用C++寫的可執(zhí)行程序



可以發(fā)現(xiàn)PID是一模一樣的,替換了C++程序,沒有創(chuàng)建新的進(jìn)程
接下來嘗試在C++程序中獲取環(huán)境變量

獲取環(huán)境變量。C++的cout如果環(huán)境變量不存在,就什么都不會打印,所以要進(jìn)行判斷,如果沒有就打印NULL

修改下C程序,在C程序中自定義環(huán)境變量表,使用execle函數(shù)


問題:C語言調(diào)用C++可以調(diào)用到環(huán)境變量嗎?
修改下C++代碼,獲取環(huán)境變量


C++自己寫的里有PATH,但沒有環(huán)境變量
觀察下C語言的

C語言調(diào)用的有MYENV 但是沒有PATH
調(diào)用的環(huán)境變量是覆蓋式寫入,會覆蓋老的環(huán)境變量PATH,所以看不到PATH
傳系統(tǒng)環(huán)境變量
C語言提供的環(huán)境變量表,二級指針,environ


此時(shí)MYENV就沒有了,系統(tǒng)的環(huán)境變量出現(xiàn)了
系統(tǒng)環(huán)境變量和自定義環(huán)境變量同時(shí)傳遞
利用putenv添加到系統(tǒng)環(huán)境變量里,依賴頭文件#include<stdlib.h>

putenv將MYENV傳到系統(tǒng)變量里,這樣調(diào)用系統(tǒng)變量,就可以將自定義和系統(tǒng)變量一塊傳遞給替換程序

環(huán)境變量具有全局屬性,可以被子進(jìn)程繼承下去!
就是通過execle函數(shù)傳遞的環(huán)境變量!
使用export MYENV = You can see me 也可以
4.exec函數(shù)解釋
1.這些函數(shù)如果調(diào)用成功則加載新的程序從啟動代碼開始執(zhí)行,不再返回。
2.如果調(diào)用出錯(cuò)則返回-1
3.所以exec函數(shù)只有出錯(cuò)的返回值而沒有成功的返回值。
5.命名理解
這些函數(shù)原型看起來很容易混,但只要掌握了規(guī)律就很好記。
l(list) : 表示參數(shù)采用列表
v(vector) : 參數(shù)用數(shù)組
p(path) : 有p自動搜索環(huán)境變量PATH
e(env) : 表示自己維護(hù)環(huán)境變量

exec調(diào)用舉例如下:
#include <unistd.h>
int main()
{char *const argv[] = {"ps", "-ef", NULL};char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};execl("/bin/ps", "ps", "-ef", NULL);// 帶p的,可以使用環(huán)境變量PATH,無需寫全路徑execlp("ps", "ps", "-ef", NULL);// 帶e的,需要自己組裝環(huán)境變量execle("ps", "ps", "-ef", NULL, envp);execv("/bin/ps", argv);// 帶p的,可以使用環(huán)境變量PATH,無需寫全路徑execvp("ps", argv);// 帶e的,需要自己組裝環(huán)境變量execve("/bin/ps", argv, envp);exit(0);
}