網(wǎng)易云課堂《Linux內(nèi)核分析》作業(yè)
實(shí)驗(yàn)?zāi)康模?br>
跟蹤分析一個(gè)簡(jiǎn)單的Linux內(nèi)核啟動(dòng)過(guò)程,理解操作系統(tǒng)是怎么啟動(dòng)的
實(shí)驗(yàn)過(guò)程:
登陸實(shí)驗(yàn)樓虛擬機(jī)http://www.shiyanlou.com/courses/195
打開(kāi)shell終端,執(zhí)行以下命令:
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage-initrd rootfs.img
執(zhí)行完畢后會(huì)彈出QEMU窗口芹务,輸出Linux內(nèi)核啟動(dòng)信息
啟動(dòng)成功后界面顯示MenuOS命令提示符
系統(tǒng)支持三個(gè)命令:help份名、version葵擎、quit
使用gdb跟蹤調(diào)試內(nèi)核
打開(kāi)shell終端贯卦,執(zhí)行以下命令:
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage-initrd rootfs.img -s -S
關(guān)于-s和-S選項(xiàng)的說(shuō)明:
-S freeze CPU at startup (use ’c’ to start execution) 在系統(tǒng)啟動(dòng)的時(shí)候凍結(jié)CPU,使用c鍵繼續(xù)執(zhí)行后續(xù)操作
-s shorthand for -gdb tcp::1234 打開(kāi)遠(yuǎn)程調(diào)試端口蛮放,默認(rèn)使用tcp協(xié)議1234端口,若不想使用1234端口奠宜,則可以使用-gdb tcp:xxxx來(lái)取代-s選項(xiàng)
打開(kāi)另外一個(gè)shell終端包颁,執(zhí)行以下命令
gdb
(gdb)filelinux-3.18.6/vmlinux # 在gdb界面中targe remote之前加載符號(hào)表
(gdb)target?remote:1234 #?建立gdb和gdbserver之間的連接,按c?讓qemu上的Linux繼續(xù)運(yùn)行
(gdb)breakstart_kernel #?斷點(diǎn)的設(shè)置可以在target?remote之前压真,也可以在之后
按c鍵繼續(xù)執(zhí)行到start_kernel()函數(shù)
輸入list命令查看start_kernel()函數(shù)代碼
再設(shè)置一個(gè)斷點(diǎn)rest_init
查看rest_init()函數(shù)代碼
利用break設(shè)置斷點(diǎn)娩嚼,c繼續(xù)執(zhí)行,list查看函數(shù)代碼可以方便調(diào)試Linux啟動(dòng)過(guò)程中用到的任何函數(shù)滴肿。
實(shí)驗(yàn)分析:
本文以Linux內(nèi)核官方網(wǎng)站(The Linux Kernel Archives)2015-03-06發(fā)布的穩(wěn)定版3.19.1源代碼為準(zhǔn)分析岳悟。
分析./init/main.c源碼文件start_kernel()函數(shù)
Linux內(nèi)核啟動(dòng)代碼大致分2部分:
一部分是硬件平臺(tái)相關(guān)的,存放在./arch/目錄下,以平臺(tái)區(qū)分不同目錄贵少,比如x86平臺(tái)就在./arch/x86/目錄下呵俏,由匯編語(yǔ)言編寫(xiě)而成。
另一部分是硬件平臺(tái)無(wú)關(guān)的滔灶,由C語(yǔ)言編寫(xiě)而成普碎。
./init/main.c中的start_kernel()函數(shù)即是Linux內(nèi)核啟動(dòng)過(guò)程由平臺(tái)相關(guān)轉(zhuǎn)為平臺(tái)無(wú)關(guān)代碼后第一個(gè)執(zhí)行的函數(shù),在這個(gè)函數(shù)中录平,Linux內(nèi)核開(kāi)始真正進(jìn)入初始化階段麻车。
下面簡(jiǎn)單介紹其中的幾個(gè)函數(shù)。(斜體字為源碼斗这,粗體字為解釋)
/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();
lockdep是一個(gè)內(nèi)核調(diào)試模塊动猬,用來(lái)檢查內(nèi)核互斥機(jī)制(尤其是自旋鎖)潛在的死鎖問(wèn)題。
set_task_stack_end_magic(&init_task);
手工創(chuàng)建的PCB涝影,0號(hào)進(jìn)程即最終的idle進(jìn)程枣察。
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
canary值的是用于防止棧溢出攻擊的堆棧的保護(hù)字。
trap_init();
對(duì)內(nèi)核陷阱異常進(jìn)行初始化燃逻。
mm_init();
初始化內(nèi)核內(nèi)存分配器序目。
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init();
初始化調(diào)度器數(shù)據(jù)結(jié)構(gòu),并創(chuàng)建運(yùn)行隊(duì)列伯襟。(一道作業(yè)題的答案:Linux源碼start_kernel函數(shù)中調(diào)用進(jìn)程調(diào)度初始化的是哪個(gè)函數(shù)猿涨?)
/* Do the rest non-__init'ed, we're now alive */
rest_init();
start_kernel()函數(shù)中調(diào)用的最后一個(gè)函數(shù)。
分析./init/main.c源碼文件rest_init()函數(shù)
rest_init()函數(shù)的主要功能是創(chuàng)建并啟動(dòng)內(nèi)核進(jìn)程init姆怪,即第一個(gè)用戶態(tài)進(jìn)程叛赚。
int pid;
定義pid變量存放進(jìn)程號(hào)
rcu_scheduler_starting();
RCU(Read-Copy Update)鎖機(jī)制啟動(dòng)。
參考:Linux 2.6內(nèi)核中新的鎖機(jī)制--RCU
/*
* We need to spawn init first so that it obtains pid 1, however
* the init task will end up wanting to create kthreads, which, if
* we schedule it before we create kthreadd, will OOPS.
*/
kernel_thread(kernel_init, NULL, CLONE_FS);
init進(jìn)程在此時(shí)創(chuàng)建好了稽揭,但是現(xiàn)在還不能調(diào)度它俺附。
numa_default_policy();
設(shè)定NUMA(Non-Uniform Memory Access Architecture)系統(tǒng)的內(nèi)存訪問(wèn)策略為默認(rèn)。
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
創(chuàng)建kthreadd內(nèi)核線程溪掀,它的作用是管理和調(diào)度其它內(nèi)核線程事镣。
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
獲取kthreadd的線程信息,獲取完成說(shuō)明kthreadd已經(jīng)創(chuàng)建成功揪胃。
rcu_read_unlock();
complete(&kthreadd_done);
通過(guò)一個(gè)complete變量(kthreadd_done)來(lái)通知kernel_init線程璃哟。
實(shí)驗(yàn)總結(jié):
當(dāng)計(jì)算機(jī)系統(tǒng)加電(Power on PC)后,BIOS代碼被調(diào)用執(zhí)行喊递,然后開(kāi)始調(diào)用執(zhí)行Linux內(nèi)核初始化代碼随闪,在平臺(tái)相關(guān)的匯編代碼執(zhí)行完畢后會(huì)跳轉(zhuǎn)到start_kernel()函數(shù),開(kāi)始真正的內(nèi)核初始化骚勘,其中init_task創(chuàng)建了0號(hào)進(jìn)程铐伴,即最終的idle進(jìn)程,隨后rest_init()函數(shù)創(chuàng)建了init進(jìn)程,即1號(hào)進(jìn)程盛杰,以及kthreadd進(jìn)程挽荡,即2號(hào)進(jìn)程,系統(tǒng)開(kāi)始正式對(duì)外工作了即供。
參考:
Linux內(nèi)核源碼分析--內(nèi)核啟動(dòng)之(3)Image內(nèi)核啟動(dòng)(C語(yǔ)言部分)(Linux-3.0 ARMv7)
Linux內(nèi)核源碼分析--內(nèi)核啟動(dòng)之(5)Image內(nèi)核啟動(dòng)(rest_init函數(shù))(Linux-3.0 ARMv7)
aapu原創(chuàng)作品轉(zhuǎn)載請(qǐng)注明出處《Linux內(nèi)核分析》MOOC課