exec執(zhí)行普通文件和解釋器文件的區(qū)別
1. 從一個(gè)問(wèn)題開(kāi)始
首先要從項(xiàng)目中遇到的一個(gè)問(wèn)題說(shuō)起。編寫(xiě)一個(gè)python文件test.py,文件test.py內(nèi)容如下:
#! /usr/bin/python
....
如果在命令行方式執(zhí)行test.py的方式是:
test.py -in inputfile -out outputfile;或python test.py -in inputfile -out outputfile;
但是因?yàn)樾枰?,用exec函數(shù)(這里使用execl)去調(diào)用這個(gè)python文件。在項(xiàng)目中是這樣寫(xiě)的:
execl(”test.py”,”-in”,”inputfile”,”-out”,”outputfile”,(char*)0);
但執(zhí)行結(jié)果并不是預(yù)想的test.py執(zhí)行,而是啟動(dòng)了python交互程序,不知道是什么原因。因?yàn)橐恢币詾槿绻麑?xiě)一個(gè)C程序,比如main。那么在命令行輸入:main arg1 arg2執(zhí)行的效果和execl(”main”,”arg1”,”arg2”,(char*)0)的效果應(yīng)該是一樣的。
當(dāng)然同時(shí)伴隨我有另一個(gè)問(wèn)題:
execl(“usr/bin/python”,”test.py”,(char*)0);和輸入命令”/usr/bin/python test.py”有什么區(qū)別?
為了回答些問(wèn)題,自己通過(guò)再反復(fù)看apue和做實(shí)驗(yàn)測(cè)試,終于一點(diǎn)一點(diǎn)明白了,下面就來(lái)一點(diǎn)一點(diǎn)的分析。
2. 命令行執(zhí)行程序和exec執(zhí)行程序的區(qū)別
首先我們來(lái)分析一下在命令行執(zhí)行一個(gè)程序和通過(guò)exec函數(shù)執(zhí)行程序有什么區(qū)別,或者說(shuō)需要注意的地方(一下所有編寫(xiě)的文件都在/mnt/hgfs/VWShared/目錄下)。
編寫(xiě)程序foo.c如下,并編譯為可執(zhí)行文件foo。它打印參數(shù)列表(argv)的所有參數(shù).
l foo.c
#include
int
main(int argc,char* argv[])
{
int i;
for(i=0;i
printf("argv[%d]: %sn",i,argv[i]);
exit(0);
}
再編寫(xiě)main.c如下,將其編譯為可執(zhí)行文件main,它使用execl調(diào)用foo。
l main.c
#include
#include
int main(int argc,char* argv[])
{
int n=0;
if( (n=execl("/mnt/hgfs/VWShared/foo",(char*)0))==-1 )
{
perror("execl error");
exit(0);
}
exit(1);
}
直接在命令行下運(yùn)行foo,結(jié)果如圖1:
圖1
運(yùn)行main(通過(guò)execl運(yùn)行foo)結(jié)果如圖2:
圖2
可以看出直接在命令行運(yùn)行foo,則”./foo”被當(dāng)做argv[0],但是通過(guò)exec運(yùn)行foo發(fā)現(xiàn)并沒(méi)有參數(shù)傳入foo(程序沒(méi)有任何輸出),也就是說(shuō)argc值為0。這是什么原因呢?我們知道argv存放的是傳遞給main函數(shù)的命令行參數(shù),當(dāng)在命令行鍵入”./foo”時(shí),唯一的命令行參數(shù)”./foo”就被傳入給main的argv了。所以直接在命令行運(yùn)行foo就打印出唯一的參數(shù)”./foo”。
那么execl的情況呢?首先看一下execl的原型:
int execl(const char* pathname,const char* arg0,.../*(char*)0*/);
注意到了吧,第一個(gè)參數(shù)是要執(zhí)行的程序名,第二個(gè)參數(shù)才是要傳入待執(zhí)行程序的第一個(gè)參數(shù),而上述main.c中沒(méi)有第二個(gè)參數(shù)(這里說(shuō)的是execl的第二個(gè)參數(shù)),也就是沒(méi)有給foo傳遞任何參數(shù),foo的參數(shù)表argv當(dāng)然就是空了,或者說(shuō)argc為0。
通過(guò)這個(gè)例子我們要有以下認(rèn)識(shí):
argv[0]不一定就是所執(zhí)行程序的名稱,確切的說(shuō)它只是命令行的第一個(gè)參數(shù),只是通常啟動(dòng)程序是在命令行鍵入程序名稱啟動(dòng)的,所以程序的名稱才成為argv[0]。但是也有情況argv[0]不是程序名稱的,如:
(1) 通過(guò)exec執(zhí)行時(shí),argv[0]是什么要視exec的參數(shù)來(lái)定。
例如:我們將main中的execl語(yǔ)句改為:execl("/mnt/hgfs/VWShared/foo","xxxxx",(char*)0);
再運(yùn)行main,效果如圖3:
圖3
可以看到argv[0]變?yōu)榱宋覀儌魅氲膮?shù)”xxxxx”。
(2) 通過(guò)程序別名啟動(dòng)時(shí),argv[0]就是程序的別名。如我們給foo創(chuàng)建一個(gè)軟連接sfoo,然后執(zhí)行sfoo效果如圖4:
圖4
可以看出輸出的argv[0]是./sfoo 而不是./foo,再次證明argv[0]是什么和程序名稱無(wú)關(guān),只是和傳入的命令行第一個(gè)參數(shù)有關(guān)。
補(bǔ)充:在創(chuàng)建上述軟連接過(guò)程中遇到了一點(diǎn)小問(wèn)題,不妨也在這里寫(xiě)下來(lái):
【問(wèn)題】
在編譯VMware下的Linux系統(tǒng)對(duì)從Windows中共享過(guò)來(lái)的文件,進(jìn)行編譯的時(shí)候,遇到:ln: creating symbolic link XXXXXX : Operation not supported
【解決辦法】
出現(xiàn)這類問(wèn)題,主要是由于在編譯的時(shí)候,要用ln去建立一些軟鏈接,而這些文件是從Windows中,通過(guò)VMWare虛擬機(jī)共享進(jìn)Linux的,而雖然此種操作在Linux系統(tǒng)中很常見(jiàn),但Windows不支持,所以,編譯會(huì)報(bào)錯(cuò)。比較方便的解決辦法是先將文件考到linux的其他目錄,再在其他非共享目錄中創(chuàng)建軟連接。另外還有個(gè)解決辦法就是,在VMWare下的Linux中,建立Samba服務(wù),然后新創(chuàng)建新samba用戶和文件夾,然后在windows中就可以訪問(wèn)到該文件夾了。然后把在Linux中,從共享目錄拷貝到你所要共享的samba目錄中,這樣,也可以實(shí)現(xiàn)我們所要的文件共享。此時(shí)在去編譯這些代碼的時(shí)候,由于是在Linux系統(tǒng)中的,所以就OK了。
3. 解釋器文件和解釋器[!--empirenews.page--]
先解釋兩個(gè)概念;解釋器文件和解釋器。
l 解釋器文件:一種文本文件,開(kāi)頭通常是:#! pathname [option-argument];比較常見(jiàn)的是#! /bin/bash,shell腳本和python腳本都屬于解釋器文件。
l 解釋器:解釋器文件第一行中pathname指定的程序,如bash。
3.1 解釋器文件的執(zhí)行
當(dāng)執(zhí)行(exec)"解釋器"文件時(shí),exec系統(tǒng)調(diào)用會(huì)識(shí)別這種文件,內(nèi)核使調(diào)用exec函數(shù)的進(jìn)程實(shí)際執(zhí)行的并不是該"解釋器文件",而是pathname指定的解釋器。
我們可以自己寫(xiě)一個(gè)解釋器,如之前所寫(xiě)的foo.c:
l foo.c
#include
int
main(int argc,char* argv[])
{
int i;
for(i=0;i
printf("argv[%d]: %sn",i,argv[i]);
exit(0);
}
編譯成為foo然后保存在/mnt/hgfs/VWShared/。
下面我們?cè)谧约簩?xiě)一個(gè)”解釋器文件”——test:
l test
#!/mnt/hgfs/VWShared/foo
3.1.1 通過(guò)命令行執(zhí)行解釋器文件
直接在命令行中鍵入:”./test”,運(yùn)行test,效果如圖5。
圖5
將test內(nèi)容修改為:#!/mnt/hgfs/VWShared/foo argA argB,再次在命令行運(yùn)行test,效果如圖6。
圖6
通過(guò)這兩個(gè)例子,可以看出命令行運(yùn)行時(shí)當(dāng)執(zhí)行文件是”解釋器文件”時(shí),參數(shù)是如何傳遞給解釋器的:
(1) 通過(guò)執(zhí)行”解釋器文件”執(zhí)行解釋器,傳遞給解釋器的第一個(gè)參數(shù)是解釋器文件的pathname,即解釋器的路徑。
(2) “解釋器文件”中pathname后的可選參數(shù)(這里的argA,argB)如果存在的話會(huì)一起作為第二個(gè)參數(shù)傳遞給解釋器。
(3) “解釋器文件”名稱會(huì)作為下一個(gè)參數(shù)傳遞給解釋器。
3.1.2 通過(guò)execl執(zhí)行解釋器文件
接下來(lái)通過(guò)execl執(zhí)行解釋器文件test,修改main中的exec語(yǔ)句如下:
execl("/mnt/hgfs/VWShared/test","arg1",”arg2”,(char*)0));然后執(zhí)行main,效果如圖7。
圖7
從這個(gè)例子可以了解當(dāng)執(zhí)行文件是解釋器文件時(shí),內(nèi)核如何處理exec函數(shù)的參數(shù)及解釋器文件第一行的可選參數(shù)。我們知道執(zhí)行解釋器文件實(shí)際是執(zhí)行解釋器,由解釋器去讀取解釋器文件中的語(yǔ)句執(zhí)行,而第一行的pathname以#開(kāi)頭在執(zhí)行時(shí)會(huì)被當(dāng)做注釋忽略。下面就讓我們分析一下最終傳入解釋器foo的參數(shù)都是什么。
(1) argv[0]是該解釋器文件的pathname;
(2) argv[1]是該解釋文件中的可選參數(shù);
(3) argv[2]是解釋器文件本身名字;
(4) argv[3]是execl出入的第二個(gè)參數(shù)(第一個(gè)參數(shù)是arg1)。
那么問(wèn)題出現(xiàn)了,我們傳入execl的arg1去哪里了呢?其實(shí)這就是exec執(zhí)行”解釋器文件”和執(zhí)行一般程序的不同之處:在執(zhí)行一般程序時(shí),execl(const char* pathname,const char* arg0,...,(char*)0)中的arg0會(huì)被當(dāng)做執(zhí)行程序(pathname)的第一個(gè)參數(shù)argv[0],而在執(zhí)行解釋器文件時(shí),內(nèi)核取execl調(diào)用中的pathname而非第一個(gè)參數(shù)(arg0)作為第一個(gè)參數(shù)傳遞給解釋器,因?yàn)橐话愣?,第一個(gè)參數(shù)arg0通常是解釋器文件的名字,而pathname包含了比arg0更多的信息(解釋器文件的完整路徑)。所以當(dāng)execl執(zhí)行解釋器文件時(shí)第一個(gè)參數(shù)arg0是無(wú)效的。
為了說(shuō)明這個(gè)問(wèn)題,我們?cè)倥e一個(gè)例子,編寫(xiě)python文件pyth.py如下:
l pyth.py:
#! /usr/bin/python
import sys
for i in range(0,len(sys.argv)):
print "argv[%d]: %s"%(i,sys.argv[i])
它的功能和foo一樣同樣是打印每個(gè)命令行參數(shù)。我們分別將main中的execl語(yǔ)句改為:
execl("/mnt/hgfs/VWShared/foo","arg1","arg2",(char*)0))和
execl("/mnt/hgfs/VWShared/pyth.py","arg1","arg2",(char*)0)),對(duì)比execl一般程序(foo)和解釋器文件(pyth.py)的效果如圖8、9。
圖8.execl("/mnt/hgfs/VWShared/foo","arg1","arg2",(char*)0))結(jié)果
圖9.execl("/mnt/hgfs/VWShared/pyth.py","arg1","arg2",(char*)0))結(jié)果
可以看出execl對(duì)于執(zhí)行普通文件和解釋器文件選取第一個(gè)參數(shù)是不同的。
3.2 execl執(zhí)行解釋器文件和命令行執(zhí)行解釋器文件的不同
我們上面已經(jīng)看到execl("/mnt/hgfs/VWShared/pyth.py","arg1","arg2",(char*)0))的結(jié)果(圖9),下面我們?cè)囈幌旅钚蟹绞剑簆yth.py arg1 arg2,結(jié)果圖10:
圖10
可以看到結(jié)果和通過(guò)execl執(zhí)行是有區(qū)別的,通過(guò)命令行執(zhí)行解釋器文件就像通過(guò)命令行執(zhí)行普通程序一樣,程序名稱作為第一個(gè)參數(shù),命令行后面依次作為后續(xù)參數(shù)。正因?yàn)閷?duì)于解釋器文件的execl方式和命令行方式執(zhí)行時(shí)選取第一個(gè)參數(shù)的方式不同,所以對(duì)于解釋器文件a.py:[!--empirenews.page--]
(1) 在命令行輸入:./a.py arg1 arg2;
(2) execl("./a.py","arg1","arg2",(char*)0));
(3) execl("./a.py",”xxx”,"arg1","arg2",(char*)0));
方式(1)和方式(2)不等價(jià),因?yàn)榉绞?1)中arg1會(huì)被當(dāng)做第二個(gè)參數(shù)傳遞給解釋器,而方式(2)中arg2會(huì)被當(dāng)做第二個(gè)參數(shù)傳遞給解釋器。方式(1)和方式(3)是等價(jià)的。
對(duì)于普通文件foo:
(1) 在命令行輸入: ./foo arg1 arg2;
(2) execl("./foo","arg1","arg2",(char*)0))
方式(1)和方式(2)是等價(jià)的。
4. 回答開(kāi)始的問(wèn)題
為了達(dá)到命令行方式:test.py arg1 arg2的效果,使用execl("test.py","arg1","arg2",(char*)0))肯定是不行的,因?yàn)閍rg1會(huì)被忽略,提示缺少參數(shù)。正確的方式是:execl("test.py",”xxx”,"arg1","arg2",(char*)0)),這里”xxx”代表任意字符串,不過(guò)一般會(huì)使用解釋器文件名,即”test.py”。
為了達(dá)到命令行方式:python test.py arg1 arg2的效果,使用execl("python",”test.py”,"arg1","arg2",(char*)0))也是不行的,因?yàn)閠est.py會(huì)被忽略,arg1會(huì)被當(dāng)做第一個(gè)參數(shù)傳給python解釋器。正確方式是:
execl(“python",”xxx”,”test.py”,"arg1","arg2",(char*)0)),這里”xxx”代表任意字符串,不過(guò)一般會(huì)使用解釋器文件名,即”test.py”。