exec族函式的作用
exec函式族的作用是根據指定的檔案名稱找到執行檔,並用它來取代調用進程的內容,換句話說,就是在調用進程內部執行一個執行檔。這裡的執行檔既可以是二進制檔案,也可以是任何Linux下可執行的腳本檔案。
與一般情況不同,exec函式族的函式執行成功後不會返回,因為調用進程的實體,包括代碼段,數據段和堆疊等都已經被新的內容取代,只留下進程ID等一些表面上的信息仍保持原樣,頗有些神似"三十六計"中的"金蟬脫殼"。看上去還是舊的軀殼,卻已經注入了新的靈魂。只有調用失敗了,它們才會返回一個-1,從原程式的調用點接著往下執行。
現在我們應該明白了,Linux下是如何執行新程式的,每當有進程認為自己不能為系統和用戶做出任何貢獻了,他就可以發揮最後一點餘熱,調用任何一個exec,讓自己以新的面貌重生;或者,更普遍的情況是,如果一個進程想執行另一個程式,它就可以fork出一個新進程,然後調用任何一個exec,這樣看起來就好像通過執行應用程式而產生了一個新進程一樣。
事實上第二種情況被套用得如此普遍,以至於Linux專門為其作了最佳化,我們已經知道,fork會將調用進程的所有內容原封不動的拷貝到新產生的子進程中去,這些拷貝的動作很消耗時間,而如果fork完之後我們馬上就調用exec,這些辛辛苦苦拷貝來的東西又會被立刻抹掉,這看起來非常不划算,於是人們設計了一種"寫時拷貝(copy-on-write)"技術,使得fork結束後並不立刻複製父進程的內容,而是到了真正實用的時候才複製,這樣如果下一條語句是exec,它就不會白白作無用功了,也就提高了效率。
對於新程式的命令行參數和環境表有長度大小的限制,對於linux來講這個限制是4096個位元組。執行了exec函式的進程不改變以下進程特徵:
1>進程ID和父進程ID
2>實際用戶ID和實際組ID
3>進程組ID和附加組ID
4>控制終端
5>會話ID
6>時鐘預留著時間
7>當前工作目錄和根目錄
8>檔案創建禁止字和檔案鎖
9>信號禁止字和未處理信號集
10>資源限制
返回值
如果執行成功則函式不會返回,執行失敗則直接返回-1,失敗原因存於errno 中。
套用時注意
大家在平時的編程中,如果用到了exec函式族,一定記得要加錯誤判斷語句。因為與其他系統調用比起來,exec很容易受傷,被執行檔案的位置,許可權等很多因素都能導致該調用的失敗。最常見的錯誤是:
1.找不到檔案或路徑,此時errno被設定為ENOENT;
2.數組argv和envp忘記用NULL結束,此時errno被設定為EFAULT;
3.沒有對要執行檔案的運行許可權,此時errno被設定為EACCES。
l表示以參數列表的形式調用
v表示以參數數組的方式調用
e表示可傳遞環境變數
p表示PATH中搜尋執行的檔案,如果給出的不是絕對路徑就會去PATH搜尋相應名字的檔案,如PATH沒有設定, 則會默認在/bin,/usr/bin下搜尋。
另:調用時參數必須以NULL結束。原進程打開的檔案描述符是不會在exec中關閉的,除非用fcntl設定它們的“執行時關閉標誌(close on exec)”而原進程打開的目錄流都將在新進程中關閉。
例子
#include <unistd.h>
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 <process.h>
#include <stdio.h>
#include <errno.h>
void main(int argc, char *argv[])
{
int i;
printf("Command line arguments:\n");
for (i=0; i<argc; i++)
printf("[-] : %s\n", i, argv );
printf("About to exec child with arg1 arg2 ...\n");
execv("CHILD.EXE", argv);
perror("exec error");
exit(1);
}
Windows XP SP3網路診斷工具檔案中的命令
Exec:xpnetdiag.exe是Windows XP SP3網路診斷工具檔案,用於診斷當前網路的連線狀況。
fscommand("exec");
flash 中在放映檔案內執行應用程式用。
CISCO中的EXEC
在Cisco 路由器中,命令解釋器稱為EXEC,EXEC解釋用戶鍵入的命令並執行相應
的操作,在輸入EXEC命令前必須先登錄到路由器上。基於安全原因,EXEC設定了
兩個訪問許可權:用戶級和特權級,用戶級可執執行的命令是特權級命令的子集。
在特權級,可以使用:configuration,interface,subinterface,line,rout
er,router-map等命令。
docker中的EXEC
我們在套用容器的過程中,無論是在通過Dockerfile在調試構建鏡像的過程,還是容器運行一段時間想查看內部結構,我們還是希望能像操作本地機器一樣,實時的查看容器內部檔案,代碼或者日誌。或是修改檔案,拷貝檔案目錄等等。
- 訪問容器內部,目前有兩種方法
1. Docker自帶的exec命令
2. Nsenter工具
- 來說說Docker exec 命令方式訪問
- 如圖所示,簡單的ls命令。Linux系統自帶的命令都可以通過這種方式運行。檔案放錯位置了,mv一下,查看log,就cat log.log一下,等等。
Exec加點料
- 簡單的操作不能滿足我們對他的好奇...
- 我們運行一下 docker exec -ti 61f ps -ef
- 發現只有3個進程,進程1是CMD命令啟動的腳本;進程2是腳本啟動的程式;進程3是我們運行ps -ef的進程。
- 出於好奇,我又docker exec 9fe0 kill 15
- 容器從此就停止了...
- 原因是殺掉進程後,Dockerfile指定的CMD["/run.sh"]腳本運行結束,CMD入口已經退出,導致容器退出。
問題還沒結束
- Kubernetes是一個基於docker的容器集群管理系統,它的一大特點是擁有Replication Controllers,他的作用主要是保持所起動的Pod數量不變(pod裡面裝的是container)。我猜想如果類似的kill掉容器內部的進程,那么kubernetes應該會讓這個container重新啟動。於是就來動手試試。
- 時速雲他們套用了kubernetes,並且提供了客戶端tce可以使用exec功能。運行 tce exec bbb-145fv-zkdqz ps -ef
- tce exec bbb-145fv-zkdqz kill 15
沒有重啟??
- 再次運行 tce exec bbb-145fv-zkdqz ps -ef
- 又出現了。
- 由此可見Kubernentes的Replication Controllers還是很強大的。保證了集群中有指定數量的pod副本在運行。