1. 程式人生 > >讀Kernel感悟-Linux核心啟動-從hello world說起

讀Kernel感悟-Linux核心啟動-從hello world說起

核心是從哪裡開始執行的呢?幾乎任何一本Linux核心原始碼分析的書都會給出詳細的答案。不過,我試圖從一個不同的角度(一個初學者的角度)來敘述,而不是一上來就給出答案。從熟悉的事物入手,慢慢接近陌生的事物,這是比較常見的思路。既然都是二進位制程式碼,那麼不妨從最簡單的使用者態C程式,hello world開始。說不定能找到共同點。恰好我是一個喜歡尋根究底的人。也許,理解了hello world程式的啟動過程,有助於更好地理解核心的啟動。

好,開始尋根究底吧。從普通的C語言使用者態程式開始寫。先寫一個簡單的hello world程式。

/*helloworld.c*/

#include <stdio.h>

int main()

{

    printf("hello world/n");

    return 0;

}

然後gcc helloworld.c -o helloworld,一個最簡單的hello world程式出現了。

它是從哪裡開始執行的呢?這還不簡單?main函式麼。地球人都知道。

為什麼一定要從main函式開始呢?於是,我開始琢磨這個hello world程式。

file helloworld可知,它是一個elf可執行檔案。

反彙編試試。

objdump -d helloworld

反彙編的結果令人吃驚,因為出現了_start()等一堆函式。一定是gcc編譯時預設連結了一些庫函式。

其實,只要執行gcc -v helloworld.c -o helloworld就會顯示gcc詳細的編譯連結過程。其中包括連結/usr/lib/下的crti.o crt1.o crtn.o等等檔案。用objdump檢視,_start()函式就定義在crt1.o檔案中。

那麼helloworld的真正執行的入口在哪裡呢?我們可以使用readelf來檢視,看有沒有有用資訊。

readelf -a helloworld

helloworld作為一個elf檔案,有elf檔案頭,section table和各個section等等。有興趣可以去看看elf檔案格式的文件。

用readelf可知,在helloworld的elf檔案頭的資訊中,有這麼一項資訊:

入口點地址:               0x80482c0

可見,helloworld程式的入口地址在0x80482c0處,而由objdump得:

080482c0 <_start>:

可見,_start()是helloworld程式首先執行的函式。_start()執行完一些初始化工作後,經過層層呼叫,最終呼叫main().可以設想,如果_start()裡最終呼叫的是foo(),那麼C程式的主函式就不再是main(),而是foo()了。

再進一步:helloworld程式具體是如何執行的呢。我們只能猜測是由bash負責執行的。然而具體看bash程式碼就太複雜了。我們可以用strace跟蹤helloworld的執行。

strace ./helloworld

出來一大堆函式呼叫。其中第一個是execve().這是一個關鍵的系統呼叫,它負責載入helloworld可執行檔案並執行。其中有很關鍵的一步,就是把使用者態的eip暫存器(實際上是它在記憶體中對應的值)設定為elf檔案中的入口點地址,也就是_start()。具體可見核心中的sys_execve()函式。

由此可見,程式從哪裡開始執行,取決於在剛開始執行的那一刻的eip暫存器的值。而這個eip是由其它程式設定的,在這裡,eip是由Linux核心設定的。具體過程如下:

1.使用者在shell裡執行./helloworld。

2.shell(這裡是bash)呼叫系統呼叫execve()。

3.execve陷入到核心裡執行sys_execve(),把使用者態的eip設定為_start()。

4.當系統呼叫執行完畢,helloworld程序開始執行時,就從_start()開始執行

5.helloworld程序最後才執行到main()。

參考:elf檔案格式

http://www.skyfree.org/linux/references/ELF_Format.pdf