Linux系統中的可執行檔有多少種類?bash環境下是如何執行程式的?下面逐一分析。
1 Linux系統中可執行檔種類1.1 二進位可執行檔
這種檔案是最常見的,如/bin/ls,/sbin/ifconfig, /bin/cat等等。
[root@notebook135 ~]# file /bin/ls /bin/cat /sbin/ifconfig
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
/bin/cat: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
/sbin/ifconfig: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
1.2 可執行指令檔
這種檔案是系統管理者常用的,通常用來粘合各種其他的程式,系統中也必不可少,如 命令service,yum等。
[root@notebook135 ~]# file $(which yum) $(which service)
/usr/bin/yum: a /usr/bin/python script text executable
/sbin/service: POSIX shell script text executable
其中,yum是一個python指令碼,service是一個shell指令碼。
當然,還可以有其他類型的指令碼,如ruby,php,awk等等。
1.3 系統載入器對可執行檔的識別驗證與載入
不論何種類型的可執行程式,都是通過統一的execve()系統調用進行載入執行的。具體的載入過程卻會因為可執行程式種類的不同而不同,對於二進位檔案,直接載入執行;對於指令檔,則會負載檔案第一行指定的解譯器,並把指令檔路徑名作為解譯器的參數。execve()是如何區分各種程式類型的呢?其實很簡單,就是位於檔案最開始處的“魔數”。不同類型的檔案的“魔數”是不同的,對於指令檔就是開頭的#!,對於二進位檔案,根據32位和64位的不同,也有所不同,感興趣的同學可以自行研究,這裡不再累述。
2 bash執行命令的過程2.1 bash執行內建命令的過程
bash環境下可以執行的命令有兩類:一類就是前面說過的可執行檔(可執行檔又分成二進位和指令碼兩類);另一類就是bash內建命令。
常見的內建命令有echo,cd,trap等等。內建命令也分成兩類:一類是某些外部可執行檔的同功能替代,目的是提高效率,如echo;第二類是其功能無法通過執行外部檔案完成的必備命令,如cd。
對於內部命令,bash直接執行其自身內部代碼即可,快速無負擔。對於執行外部可執行檔,則相對就麻煩了。
2.2 bash執行外部可執行檔的過程
可以有兩種手段讓bash執行命令,一是在互動模式下,輸入命令名然後按下斷行符號鍵;二是把命令路徑作為參數來執行/bin/bash。兩種手段的作用是相同的。
bash首先會fork出一個子進程,然後:(1)bash自身進程執行wait()等待子進程結束;(2)子進程中執行execve("命令路徑“),剩下的工作就由載入器來完成了。
[root@notebook135 ~]# strace -e trace=process /bin/ls
execve("/bin/ls", ["/bin/ls"], [/* 21 vars */]) = 0
2.3 運行指令檔的三種方式(1)直接執行
前面我們說過,execve()可以自動識別指令檔類型,並自動載入相應的解譯器。如下:
[root@notebook135 ~]# strace -e trace=process ./test1.sh
execve("./test1.sh", ["./test1.sh"], [/* 21 vars */]) = 0
(2)人工指定解譯器執行
當然,這個識別指令碼的過程也可以交給使用者自己來完成,比如對於上面的 test1.sh,我們可以指定ksh93為其解譯器,者通過顯式的執行其解譯器程式來完成。
[root@notebook135 ~]# strace -e trace=process ksh93 ./test1.sh
execve("/bin/ksh93", ["ksh93", "./test1.sh"], [/* 21 vars */]) = 0
此時,指令檔第一行指定的解譯器不起作用了。
這兩種執行指令檔的方式效果是一樣的,只是通過ps查看時進程的名字會有所不同,直接執行時子進程名字為指令檔名,通過ksh93 test1.sh執行時,子進程名為ksh93。
其實第二種方式有時候是必要的,例如某些系統的載入器本身不能直接執行指令檔,又如指令檔本身不具有可執行許可權。
前面例子test1.sh是ksh指令碼,如果是awk,php,python...,過程也是完全一樣的。有一中指令碼也許看起來有點特殊,那就是bash,此時會導致bash啟動一個子bash進程,其實也就是看起來特殊,僅僅是因為子進程的名字和父進程一樣而已,本質上與其他類型的指令碼解譯器沒有任何區別。
(3)使用bash進程本身解釋指令檔(不產生子進程)bash還提供了一種特殊的執行外部命令的方式,那就是使用自身進程去執行,而不是建立子進程。這時,指令檔第一行指定的解譯器不起作用了。這種方式的優點是無需建立子進程,當然效率高。其缺點也是明顯的,因為失去了進程的隔離,所以指令檔會直接破壞bash進程,安全性差。例如如下指令碼:
vim test1.sh
1 #!/bin/ksh93
2 echo $$
3 exit
如果直接執行或者通過bash ./test1.sh執行,結果都是列印子進程號,然後退出子進程,回到互動式bash。而如果通過. ./test.sh執行,則列印bash進程號後,bash自身退出了。
此種方式的主要作用是通過指令碼設定互動式bash自身的環境,如/etc/profile檔案就需要此種方式執行。
需要說明的是,這種方式不同於在父進程中直接execute(),那叫做同一個進程中程式體的替換了。這裡,bash只是把指令檔的內容當成是使用者在終端的輸入而已。