exec族的組成

在Linux中,并不存在一個exec()的函數(shù)形式,exec指的是一組函數(shù),一共有6個,分別是:

#include

extern char **environ;

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[]);

其中只有execve是真正意義上的系統(tǒng)調(diào)用,其它都是在此基礎(chǔ)上經(jīng)過包裝的庫函數(shù)。

exec族函數(shù)的作用

exec函數(shù)族的作用是根據(jù)指定的文件名找到可執(zhí)行文件,并用它來取代調(diào)用進程的內(nèi)容,換句話說,就是在調(diào)用進程內(nèi)部執(zhí)行一個可執(zhí)行文件。這里的可執(zhí)行文件既可以是二進制文件,也可以是任何Linux下可執(zhí)行的腳本文件。

與一般情況不同,exec函數(shù)族的函數(shù)執(zhí)行成功后不會返回,因為調(diào)用進程的實體,包括代碼段,數(shù)據(jù)段和堆棧等都已經(jīng)被新的內(nèi)容取代,只留下進程ID等一些表面上的信息仍保持原樣,頗有些神似"三十六計"中的"金蟬脫殼"。看上去還是舊的軀殼,卻已經(jīng)注入了新的靈魂。只有調(diào)用失敗了,它們才會返回一個-1,從原程序的調(diào)用點接著往下執(zhí)行。

現(xiàn)在我們應(yīng)該明白了,Linux下是如何執(zhí)行新程序的,每當(dāng)有進程認(rèn)為自己不能為系統(tǒng)和用戶做出任何貢獻了,他就可以發(fā)揮最后一點余熱,調(diào)用任何一個exec,讓自己以新的面貌重生;或者,更普遍的情況是,如果一個進程想執(zhí)行另一個程序,它就可以fork出一個新進程,然后調(diào)用任何一個exec,這樣看起來就好像通過執(zhí)行應(yīng)用程序而產(chǎn)生了一個新進程一樣。

事實上第二種情況被應(yīng)用得如此普遍,以至于Linux專門為其作了優(yōu)化,我們已經(jīng)知道,fork會將調(diào)用進程的所有內(nèi)容原封不動的拷貝到新產(chǎn)生的子進程中去,這些拷貝的動作很消耗時間,而如果fork完之后我們馬上就調(diào)用exec,這些辛辛苦苦拷貝來的東西又會被立刻抹掉,這看起來非常不劃算,于是人們設(shè)計了一種"寫時拷貝(copy-on-write)"技術(shù),使得fork結(jié)束后并不立刻復(fù)制父進程的內(nèi)容,而是到了真正實用的時候才復(fù)制,這樣如果下一條語句是exec,它就不會白白作無用功了,也就提高了效率。

對于新程序的命令行參數(shù)和環(huán)境表有長度大小的限制,對于linux來講這個限制是4096個字節(jié)。執(zhí)行了exec函數(shù)的進程不改變以下進程特征:

1>進程ID和父進程ID

2>實際用戶ID和實際組ID

3>進程組ID和附加組ID

4>控制終端

5>會話ID

6>時鐘預(yù)留著時間

7>當(dāng)前工作目錄和根目錄

8>文件創(chuàng)建屏蔽字和文件鎖

9>信號屏蔽字和未處理信號集

10>資源限制

返回值

如果執(zhí)行成功則函數(shù)不會返回,執(zhí)行失敗則直接返回-1,失敗原因存于errno 中。

應(yīng)用時注意

大家在平時的編程中,如果用到了exec函數(shù)族,一定記得要加錯誤判斷語句。因為與其他系統(tǒng)調(diào)用比起來,exec很容易受傷,被執(zhí)行文件的位置,權(quán)限等很多因素都能導(dǎo)致該調(diào)用的失敗。最常見的錯誤是:

1.找不到文件或路徑,此時errno被設(shè)置為ENOENT;

2.數(shù)組argv和envp忘記用NULL結(jié)束,此時errno被設(shè)置為EFAULT;

3.沒有對要執(zhí)行文件的運行權(quán)限,此時errno被設(shè)置為EACCES。

l表示以參數(shù)列表的形式調(diào)用

v表示以參數(shù)數(shù)組的方式調(diào)用

e表示可傳遞環(huán)境變量

p表示PATH中搜索執(zhí)行的文件,如果給出的不是絕對路徑就會去PATH搜索相應(yīng)名字的文件,如PATH沒有設(shè)置,則會默認(rèn)在/bin,/usr/bin下搜索。

另:調(diào)用時參數(shù)必須以NULL結(jié)束。原進程打開的文件描述符是不會在exec中關(guān)閉的,除非用fcntl設(shè)置它們的“執(zhí)行時關(guān)閉標(biāo)志(close on exec)”而原進程打開的目錄流都將在新進程中關(guān)閉。

例子

#include

int main(int argc, char *argv[])

{

char *envp[]={"PATH=/tmp", "USER=lei", "STATUS=testing", NULL};

char *argv_execv[]={"echo", "excuted by execv", NULL};

char *argv_execvp[]={"echo", "executed by execvp", NULL};

char *argv_execve[]={"env", NULL};

if(fork()==0) {

if(execl("/bin/echo", "echo", "executed by execl", NULL)<0)

perror("Err on execl");

}

if(fork()==0) {

if(execlp("echo", "echo", "executed by execlp", NULL)<0)

perror("Err on execlp");

}

if(fork()==0) {

if(execle("/usr/bin/env", "env", NULL, envp)<0)

perror("Err on execle");

}

if(fork()==0) {

if(execv("/bin/echo", argv_execv)<0)

perror("Err on execv");

}

if(fork()==0) {

if(execvp("echo", argv_execvp)<0)

perror("Err on execvp");

}

if(fork()==0) {

if(execve("/usr/bin/env", argv_execve, envp)<0)

perror("Err on execve");

}

}

/* execv example */

#include

#include

#include

void main(int argc, char *argv[])

{

int i;

printf("Command line arguments: ");

for (i=0; i

printf("[-] : %s ", i, argv);

printf("About to exec child with arg1 arg2 ... ");

execv("CHILD.EXE", argv);

perror("exec error");

exit(1);

}

Windows XP SP3網(wǎng)絡(luò)診斷工具文件中的命令

Exec:xpnetdiag.exe是Windows XP SP3網(wǎng)絡(luò)診斷工具文件,用于診斷當(dāng)前網(wǎng)絡(luò)的連接狀況。

fscommand("exec");

flash 中在放映文件內(nèi)執(zhí)行應(yīng)用程序用。

CISCO中的EXEC

在Cisco 路由器中,命令解釋器稱為EXEC,EXEC解釋用戶鍵入的命令并執(zhí)行相應(yīng)

的操作,在輸入EXEC命令前必須先登錄到路由器上。基于安全原因,EXEC設(shè)置了

兩個訪問權(quán)限:用戶級和特權(quán)級,用戶級可執(zhí)執(zhí)行的命令是特權(quán)級命令的子集。

在特權(quán)級,可以使用:configuration,interface,subinterface,line,rout

er,router-map等命令。

docker中的EXEC

我們在應(yīng)用容器的過程中,無論是在通過Dockerfile在調(diào)試構(gòu)建鏡像的過程,還是容器運行一段時間想查看內(nèi)部結(jié)構(gòu),我們還是希望能像操作本地機器一樣,實時的查看容器內(nèi)部文件,代碼或者日志。或是修改文件,拷貝文件目錄等等。

- 訪問容器內(nèi)部,目前有兩種方法

1. Docker自帶的exec命令

2. Nsenter工具

- 來說說Docker exec 命令方式訪問

- 如圖所示,簡單的ls命令。Linux系統(tǒng)自帶的命令都可以通過這種方式運行。文件放錯位置了,mv一下,查看log,就cat log.log一下,等等。

Exec加點料

- 簡單的操作不能滿足我們對他的好奇...

- 我們運行一下docker exec -ti 61f ps -ef

- 發(fā)現(xiàn)只有3個進程,進程1是CMD命令啟動的腳本;進程2是腳本啟動的程序;進程3是我們運行ps -ef的進程。

- 出于好奇,我又docker exec 9fe0 kill 15

- 容器從此就停止了...

- 原因是殺掉進程后,Dockerfile指定的CMD["/run.sh"]腳本運行結(jié)束,CMD入口已經(jīng)退出,導(dǎo)致容器退出。

問題還沒結(jié)束

- Kubernetes是一個基于docker的容器集群管理系統(tǒng),它的一大特點是擁有Replication Controllers,他的作用主要是保持所起動的Pod數(shù)量不變(pod里面裝的是container)。我猜想如果類似的kill掉容器內(nèi)部的進程,那么kubernetes應(yīng)該會讓這個container重新啟動。于是就來動手試試。

- 時速云他們應(yīng)用了kubernetes,并且提供了客戶端tce可以使用exec功能。運行 tce exec bbb-145fv-zkdqz ps -ef

- tce exec bbb-145fv-zkdqz kill 15

沒有重啟??

- 再次運行 tce exec bbb-145fv-zkdqz ps -ef

- 又出現(xiàn)了。

- 由此可見Kubernentes的Replication Controllers還是很強大的。保證了集群中有指定數(shù)量的pod副本在運行。