linux核心啟動過程追蹤
一、使用自己的Linux系統內容搭建MenuOS的過程
# 下載核心原始碼編譯核心
cd ~/LinuxKernel/
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz
xz -d linux-3.18.6.tar.xz
tar -xvf linux-3.18.6.tar
cd linux-3.18.6
make i386_defconfig
make # 一般要編譯很長時間,少則20分鐘多則數小時
# 製作根檔案系統
cd ~/LinuxKernel/
mkdir rootfs
git clone https://github.com/mengning/menu.git # 如果被牆,可以使用附件menu.zip
cd menu
gcc -o init linktable.c menu.c test.c -m32 -static –lpthread
cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
# 啟動MenuOS系統
cd ~/LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
// -initrd file Use file as initial ram disk.
二、重新設定編譯Linux使之攜帶調試資訊
在原來配置的基礎上,make menuconfig選中如下選項重新設定Linux,使之攜帶調試資訊
kernel hacking—>[*] compile the kernel with debug info
make重新編譯(時間較長)
三、使用gdb跟蹤調試核心
1、qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S # 關於-s和-S選項的說明:
// # -S freeze CPU at startup (use ’c’ to start execution)
//# -s shorthand for -gdb tcp::1234 若不想使用1234連接埠,則可以使用-gdb tcp:xxxx來取代-s選項
2、另開一個shell視窗
gdb
(gdb)file linux-3.18.6/vmlinux # 在gdb介面中targe remote之前載入符號表
(gdb)target remote:1234 # 建立gdb和gdbserver之間的串連,按c 讓qemu上的Linux繼續運行
(gdb)break start_kernel # 斷點的設定可以在target remote之前,也可以在之後
四、過程圖示:
1、可以看到QEMU 已經運行而且被“凍結”。(有幾個檔案沒有,不影響後續操作)
2、另外開啟一個視窗,進入linuxkernel目錄,輸入gdb 斷行符號
3、輸入:(gdb)file linux-3.18.6/vmlinux
(gdb)target remote:1234
(gdb)break start_kernel
4、輸入c 斷行符號,可以看到核心繼續啟動,最後停在了start_kernel處:
更多gdb指令:
顯示和尋找程式原始碼
(1)list :顯示10行代碼,但是我為什麼沒有顯示成功呢?
(2)list 5,10:顯示源檔案第五行到第十行的代碼
(3)list t4.c:5,10:顯示源檔案中第五行到第十行的代碼,在跳是含有多個源檔案的次序時使用;
(4)list get_sum:顯示get_sum函數周圍的代碼//什麼叫周圍的代碼呢?
(5)list t4.c :get_sum:顯示源檔案t4.c中第五行到第十行的代碼,在跳是含有多個源檔案的次序時使用;
(6)如果在調試中運行linux命令,則可以在gdb的提示符下輸入shell命令. (gdb)shell ls
(7)search forward用來從當前行向前尋找第一個匹配的字串;
search get_sum forward get_sum
(8)reverse_search 用來從當前行想前尋找第一個匹配的字串: Example: reverse_search main
設定和管理斷點:
(1)以行號設定斷點:(gdb)break 7
(2)以函數名設定斷點:(gdb)break get_sum
(3)以條件運算式設定斷點:方法一:break 行號或者函數名 if 條件. Example: (gdb)break 7 if i==99
方法二:watch 條件運算式,下面是具體的舉例:
方法三:awatch;用來給運算式設定斷點,在運算式的值發生改變或者運算式的值杯讀取的時候,程式暫時停止;
(4).查看當前設定的斷點:info breakpoints
(5)使用“disable 斷點編號”命令可以是某個斷點失效,程式運行到該段點時不會停下來而是繼續運行。
(6)使用“enable 斷點編號”命令可以是某個斷點恢複有效。
徹底的刪除某個斷點,可以使用clear或者delete命令。
(1)clear:刪除程式中所有的斷點;
(2)clear 行號:刪除此行中的斷點
(3)clear 函數名:刪除該函數的斷點
(4)delete 斷點編號:刪除指定編號的斷點。如果一次要刪除多個斷點,各個斷點編號以空格隔開。
控製程序的執行:
(1)continue命令:讓程式繼續運行,直到下一個斷點或者運行完為止。格式:continue
(2)kill命令:用於結束當前程式的調試
(3)next和step命令
區別:如果遇到函數,next會把函數調用當作一條語句來執行,再次輸入next會執行函數調用後的語句;
而step則會跟蹤進入函數,一次一條的執行函數內的代碼,直到函數內的代碼執行完,在進行函數調用後的語句;
(4)nexti和stepi命令:用來逐步執行一條機器指令,注意不是逐步執行一條魚據。逐步執行一條語句使用next和step命令。通常一條語句有多條機器指令構成的。
注意的是:gdb的一些命令可以簡寫,比如list可以用li來代替,continue命令可以用cont來代替。
5、繼續輸入 list start_kernel 查看start_kernel函數為中心上下10行的代碼
然後接著輸入list 查看後續的代碼(每輸入一次list,向後顯示10行)
我們繼續輸入list跟蹤start_kernel以後的代碼,看看後面的執行過程:
可以看到,在start_kernel之後是一大堆的初始化操作,初始化外設、時鐘、寄存器等。後面還有很多操作,這裡不作過多的展示,
6、後面進入rest_init函數:
四、總結:
在start_kernel中調用了一系列的初始化函數,已完成核心本身的設定:設定與體繫結構相關的環境、進程調度器初始化、控制台初始化、系統IRQ初始化、記憶體初始化等。在Start_kernel函數的最後調用了rest_init()函數,在rest_init中建立了init線程,並在最後調用cpu_idle()函數。
可以這樣理解:start_kernel最後clone出一個新的進程,也就是init進程,然後原來的進程就去執行cpu_idle()函數了,也就變成了idle進程,當發生一次進程調度後,init進程被調度運行。
核心進程init()主要進行一些外設初始化的工作包括SMP(Symmetric Multi-Processing 對稱式多處理)的初始化,以及調用do_basic_setup()完成外設及其驅動程式的載入和初始化,當do_basic_setup()函數返回init() ,init() 又開啟了、dev/console裝置,重新導向輸出檔案到控制台,最後通過kernel——execve載入執行init程式。
追蹤到init後可以修改檔案系統,比如把顯示的MENUOS修改為MYOS,修改quit指令,讓其退出檔案系統等:
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
// -initrd file Use file as initial ram disk.
這裡將-initrd 後面的參數rootfs.img載入到記憶體中運行,所以,這裡的rootfs.img 可以隨便改名字,因為核心代碼中不會直接用這個名字而是通過initrd來傳遞的。再者rootfs.img是由tootfs檔案夾打包得來的,而rootfs檔案夾中只有一個init可執行檔,而init可執行檔來自於menu檔案夾,menu檔案夾中執行make指令會發現產生了一個test可執行檔,運行menu檔案夾中的test和init後會發現效果是一模一樣的,所以顯然init是由test通過(cp test init) 拷貝、重新命名得來的,所以最終修改檔案系統可以歸結為修改menu檔案夾中的main.c、test.c檔案。
在init/initramfs.c檔案中: