int main(int argc ATTRIBUTE_UNUSED, char **argv) { applet_name = argv[0]; if (applet_name[0] == '-') applet_name++; applet_name = bb_basename(applet_name); parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */ run_applet_and_exit(applet_name, argv); /*bb_error_msg_and_die("applet not found"); - sucks in printf */ full_write2_str(applet_name); full_write2_str(": applet not found\n"); xfunc_die(); }很顯然,周遊於自訂的函式中,這行 "run_applet_and_exit(applet_name, argv)" 讓我們感到興趣,這就攸關 busybox 的原理,所以咱們看看此函式如何將 argv[0] 作處置,在同一檔案的實做碼如下:
void FAST_FUNC run_applet_and_exit(const char *name, char **argv) { int applet = find_applet_by_name(name); if (applet >= 0) run_applet_no_and_exit(applet, argv); if (!strncmp(name, "busybox", 7)) exit(busybox_main(argv)); }所以重點就是 find_applet_by_name() 函式,顧名思義,就是解析的動作,繼續看實做碼:
int FAST_FUNC find_applet_by_name(const char *name) { /* Do a binary search to find the applet entry given the name. */ const char *p; p = bsearch(name, applet_names, ARRAY_SIZE(applet_main), 1, applet_name_compare); if (!p) return -1; return p - applet_names; }看到這裡,其實就不必再追下去了,都使出標準函式庫的 binary search 函式,顯然就是從內建的 applet 列表中比對,抓出合適的 applet 實做的索引值,內部勢必有對應的執行機制。所以,以 "ls" 來說,在 busybox 的設計中,無論我們在 shell 下執行 "../bin/ls"、"/bin/ls"、"./ls" 等等,只要能在檔案系統中找到 symbolic link 的 "ls" symbol name 與 busybox 執行檔主體,當執行時,argv[0] 就被賦予絕對或相對路徑的字串,busybox 程式透過以上機制,解析名稱,找到 (精簡的) ls applet 實做並將參數代入執行,而,「取得執行時期的執行檔路徑」對於實做 shell 本身,如 ash, msh 來說,就相當重要,因為在 shell (當然,也是由 busybox 提供實做) 下執行程式,其實就是做了 fork()/vfork() 一類的系統呼叫。
# ls -l /proc/1/exe lrwxrwxrwx 1 root root 0 2008-07-03 22:10 /proc/1/exe -> /sbin/init當然,目錄 /proc/1/ 底下有很多檔案可探索,筆者就不詳述了,只要知曉 Linux /proc 如此的設計即可。再者,當一個執行中的行程存取目錄 /proc/self/ 時,其效力等同於 /proc/目前行程的 pid/,這也就是說,"/proc/self/exe" 就是對應到「目前行程對應的執行檔」。看來 /proc/self/exe 似乎可解決前述議題,但注意,這僅適用於應用程式本身,不包含其動態函式庫,對於後者,一個普遍的作法是,查閱 /proc/self/maps 的內容,即可依據位址找尋對應的函式庫名稱,或者,透過 GNU/Linux 專有的 DL_info + dladdr 組合,可取得 DL_info::dli_fname 的值,即可指出到底位於動態函式庫或應用程式主體,熟悉 Win32 API 的朋友,大概就會想到等效的 GetModuleFileName()。
#include <sys/types.h> #include <unistd.h> #define MSG_1 "child ends.\n" #define MSG_2 "parent about to fork self.\n" extern char **environ; int main(int argc, char *argv[]) { char *fn[] = { "/proc/self/exe", 0 }; pid_t pid = fork(); if (0 == pid) { write(1, MSG_1, sizeof(MSG_1)); } else if (-1 == pid) { /* unable to fork */ _exit(-1); } else { write(1, MSG_2, sizeof(MSG_2)); execve(*fn, fn, environ); } return 0; }上述程式碼列表有若干重點,如下:
$ gcc -o fork-self fork-self.c $ ./fork-self child ends. parent about to fork self. child ends. parent about to fork self. child ends. parent about to fork self. ... (重複) ...這過程中,若打開 top 或 htop 一類的工具查看記憶體,會發現可用的記憶體很快會被消耗,不過別擔心,最後失敗就作 _exit 以離開。在未結束前,用 pstree 觀察看看 fork-self 的呈現:
init-+-NetworkManager---{NetworkManager} |-NetworkManagerD |-acpid ... |-rxvt-unicode--bash---exe-+-11599*[exe] | `-fork-self ...前述論及 argv[0] 在 exec 系列系統呼叫的限制即在此,上述 pid 是會跳動的,而名稱又貌似一致,不過,Linux 內部會有機制將 /proc/self/exe 找出,是此,取得 GNU/Linux 行程的執行檔路徑,大致如此。不過,我們還是會有疑惑,既然 argv[0] 的限制這麼多、處理複雜又可能不正確,那為何 busybox 經歷多次改版,為何還保留 argv[0] 複雜的處理呢?要注意的是,busybox 可在許多不同硬體的 GNU/Linux 上運作,包含沒有 MMU 的平台,也就是 ucLinux,而,後者 (預設) 沒有 /proc/self/exe 的機制,所以,一般的作法,就是寫死執行檔路徑,當然,這樣的嵌入式系統設計就得相當小心。
看糊涂了,保留argv[0]不就是为了能够当用户通过symlink执行busybox的时候,能够知道symlink的名字,然后执行相应的操作么?这个和/proc/self/exe有什么关系么?
由 xiaosuo 發表於 July 4, 2008 12:49 AM@xiaosuo
假設 busybox 執行檔本體不放在 /bin 路徑下,而在於 /opt/embedix/bin 一類的路徑,僅由 $PATH 環境變數,讓 shell 得以找到並執行其中的 applet,然後,又啟動 busybox 內建的 ash 以執行 shell script,這意味著 fork + exec,那麼,若無法確知 busybox 的絕對路徑前,該如何解析 argv[0] 呢?這就是陷阱。
由 jserv 發表於 July 4, 2008 03:45 AMbusybox这样就可以了
name = strrchr(argv[0], '/');
if (name == NULL)
name = argv[0];
else
name++;
applet_func = find_applet_func_by_name(name);
if (applet_func != NULL)
return applet_func(argc - 1, argv + 1);
怎么会有$PATH和fork + exec什么事情?
由 xiaosuo 發表於 July 4, 2008 04:17 PM@xiaosuo
若您追蹤到 busybox 的 ash 實現,就會發現,其實這個 shell 給了諸多限制,而且還得依據 hard-coded busybox exec path 來配合某些 rules 來找到真正的 exec path (shell 支援 $PATH 的) 。之前提過「argv[0] 的值可為其他:當透過 exec 系列的系統呼叫時,什麼名稱都有可能,端視呼叫的形式,這時候就得找出方法 (沒有一定準則)」,或許陳述不清,讓您誤解了,
總之,這個狀況要想辦法解決,有 /proc/self/exe 的支持,是最方便的。
由 jserv 發表於 July 4, 2008 04:29 PM最后应该是
return applet_func(argc, argv);
刚刚和busybox的代码对了一下,如我所言。
您提到ash的实现,我总算是搞明白了,原来是busybox需要exec自己,耽误您功夫了,不好意思。
另外,我看的busybox 1.8.2已经支持/proc/self/exec选项了。
│ Symbol: FEATURE_PREFER_APPLETS [=n] │
│ Prompt: exec prefers applets │
│ Defined at Config.in:225 │
│ Location: │
│ -> Busybox Settings │
│ -> General Configuration
@xiaosuo
是的,感謝補充,就是 shell 的自我呼叫的個案。最近想減少程序代碼列表,就偷懶只陳述概念,沒想到語焉未詳,耽誤好多時間,看來還是少不了 What You See Is What You Get 的 C source code :-)
由 jserv 發表於 July 4, 2008 04:54 PM想到一个问题,如果exec self的目的最终也是为了调用self中的某个函数,为什么不直接调用,然后exit呢?
由 xiaosuo 發表於 July 4, 2008 10:59 PM参考了busybox的代码,果真有这样的applet:
a = find_applet_by_name(cmd);
if (a) {
if (a->noexec)
run_appletstruct_and_exit(a, argv);