講真,因為啟動過程太復雜,這個博客很難寫,想了幾天,不知道從哪里開始講起。不過,不開始,永遠不知道有多難寫,那么就試試看。
***一般的學習主線是:Start_kernel(); –> rest_init(); -> kernel_init(); ***
在寫的過程中,感覺到自己文字的生硬,完全是硬解,而不能算得上是真的理解。
內核代碼交叉引用鏈接
在本地制作Menu OS,成功。
慣例,實驗步驟如下:
- 啟動Linux內核,但是在啟動的時候使得CPU進入freeze狀態,因為我們等下要用gdb單步調試。
cd LinuxKernel/
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選項
- 另外需要一個terminal窗口,建立gdb和之前我們在啟動內核時qemu -s選項所啟動的gdb server之間的連接。
gdb
(gdb)file linux-3.18.6/vmlinux # 加載符號表
(gdb)target remote:1234 # 建立gdb和gdbserver之間的連接
(gdb)b start_kernel # 在start_kernel處設置斷點
(gdb)c # 繼續運行
如下:
從上圖可以看得出來,系統已經停止在了start_kernel處,接下來便是我們要分析的地方。
首先是lockdep_init,只是初始化一次hash表。
緊接著的是[set_task_stack_end_magic]((http://lxr.free-electrons.com/ident?v=3.18;i=set_task_stack_end_magic)(&init_task);從下圖看得到,意圖很簡單,僅僅是為init_task設置堆棧的邊界點,所謂的魔數,用來防止堆棧溢出。
對于單CPU,smp_setup_processor_id無作用。
接下來debug_objects_early_init,初始化buckets,即obj_hash。把static object pool數組的元素初始化成鏈表;
boot_init_stack_canary,初始化帶防止棧溢出攻擊保護的堆棧;
cgroup_init_early,初始化cgroup以及需要盡早啟動的子系統;
local_irq_disable,關閉當前CPU的所有中斷響應;
early_boot_irqs_disabled = true;
告訴我們,在‘early bootup code’階段,boot processor只能運行在中斷禁止模式。只有當這個標志位為false的時候,才能運行一些被禁的操作。緊接著下面的一大堆代碼都是各種系統必要的初始化。這些初始化步驟很是復雜,每個都值得去深挖進去折騰好久。然而,我們目前的目的是搞清楚這個過程,要分清主干和枝葉。不然看得越深,陷得越深。
每個函數都夠自己吃一壺的,哎。
WARN(!irqs_disabled(), "Interrupts were enabled early\n"); early_boot_irqs_disabled = false; local_irq_enable();
這兩句代碼和我們之前的early_boot_irqs_disabled = true;
相呼應,查詢是否中斷已經被提前打開,是的話發出警告。同時告訴系統,現在中斷已經被使能了,之前不能做的事情現在可以做了。此處產生idle進程
- 從代碼中可以看到,idle進程產生之后,立刻將其狀態改變
idle->state = TASK_RUNNING;
- 當console_init運行的時候,系統會設置控制臺tty,把和控制臺相關的東西都初始化,控制臺初始化完畢,通過一系列函數指針的調用,系統就會打印出很多東西。
- 最引人注意的是從內核態進入用戶態的rest_init函數 了。
可以看到,系統是在kernel_thread中調用kernel_init,kernel_init 調用do_fork來產生1號進程的。
- 通過跟蹤,我們發現,在schedule_preempt_disabled()函數中執行上下文切換,執行完schedule()之后立刻調度到kernet_init執行。
- 可以看到,在kernel_init中會調用run_init_process
跟蹤調試的時候,發現總是不執行到下面的這一段語句:
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
run_init_process又調用do_execve。
跟蹤之后,發現每次都是ramdisk_execute_command為真的時候,執行run_init_process,然后返回值ret為0(在gdb中打印的時候,總是無法打印出來之,提示optimized out,原因在于編譯代碼的過程中采用了-Ox的優化等級,不過可以猜測出來ret是0)。
顯然函數返回了之后應該是進入用戶態了吧。代碼太龐雜。
到匯編了,就跟不下去了。可以用ni,si。
先到這里吧,慢慢品味。