姓名:江軍linux
ID:fuchen1994git
實驗日期:2016.3.13github
實驗指導shell
使用實驗樓的虛擬機打開shellbash
內核啓動完成後進入menu程序(《軟件工程C編碼實踐篇》的課程項目),支持三個命令help、version和quit,您也能夠添加更多的命令,對選修過《軟件工程C編碼實踐篇》的童鞋應該是a piece of cake.網絡
使用gdb跟蹤調試內核tcp
另開一個shell窗口ide
實驗要求:函數
使用gdb跟蹤調試內核從start_kernel到init進程啓動工具
詳細分析從start_kernel到init進程啓動的過程並結合實驗截圖撰寫一篇署名博客,並在博客文章中註明「真實姓名(與最後申請證書的姓名務必一致) + 原創做品轉載請註明出處 + 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000 」,博客內容的具體要求以下:
題目自擬,內容圍繞Linux內核的啓動過程,即從start_kernel到init進程啓動;
博客中須要使用實驗截圖
博客內容中須要仔細分析start_kernel函數的執行過程
總結部分須要闡明本身對「Linux系統啓動過程」的理解,尤爲是idle進程、1號進程是怎麼來的。
3)請提交博客文章URL到網易雲課堂MOOC平臺Linux內核分析MOOC課程,編輯成一個連接能夠直接點擊打開。
Linux內核目錄:
arch目錄包括了全部和體系結構相關的核心代碼。它下面的每個子目錄都表明一種Linux支持的體系結構,例如i386就是Intel CPU及與之相兼容體系結構的子目錄。PC機通常都基於此目錄。
COPYING目錄下是GPL版權申明。對具備GPL版權的源代碼改動而造成的程序,或使用GPL工具產生的程序,具備使用GPL發表的義務,如公開源代碼。
CREDITS目錄下是光榮榜。對Linux作出過很大貢獻的一些人的信息。
documentation目錄下是一些文檔,沒有內核代碼,惋惜都是English的,是對每一個目錄做用的具體說明。
drivers目錄中是系統中全部的設備驅動程序。它又進一步劃分紅幾類設備驅動,每一種有對應的子目錄,如聲卡的驅動對應於drivers/sound; block 下爲塊設備驅動程序,好比ide(ide.c)。若是你但願查看全部可能包含文件系統的設備是如何初始化的,你能夠看drivers/block/genhd.c中的device_setup()。它不只初始化硬盤,也初始化,由於安裝nfs文件系統的時候須要網絡其餘: 如, Lib放置核心的庫代碼; Net,核心與網絡相關的代碼; Ipc,這個目錄包含核心的進程間通信的代碼; Fs,全部的文件系統代碼和各類類型的文件操做代碼,它的每個子目錄支持一個文件系統,例如fat和ext2。
fs目錄存放Linux支持的文件系統代碼和各類類型的文件操做代碼。每個子目錄支持一個文件系統,如ext3文件系統對應的就是ext3子目錄。
include目錄包括編譯核心所須要的大部分頭文件,例如與平臺無關的頭文件在include/linux子目錄下,與 intel cpu相關的頭文件在include/asm-i386子目錄下,而include/scsi目錄則是有關scsi設備的頭文件目錄。
init目錄包含核心的初始化代碼(不是系統的引導代碼),有main.c和Version.c兩個文件。這是研究核心如何工做的好起點。
ipc目錄包含了核心進程間的通訊代碼。
Kernel內核管理的核心代碼,此目錄下的文件實現了大多數linux系統的內核函數,其中最重要的文件當屬sched.c;同時與處理器結構相關代碼都放在archlib/目錄下。
MAINTAINERS目錄存放了維護人員列表,對當前版本的內核各部分都有誰負責。
Makefile目錄第一個Makefile文件。用來組織內核的各模塊,記錄了個模塊間的相互這間的聯繫和依託關係,編譯時使用;仔細閱讀各子目錄下的Makefile文件對弄清各個文件這間的聯繫和依託關係頗有幫助。
mm目錄包含了全部獨立於 cpu 體系結構的內存管理代碼,如頁式存儲管理內存的分配和釋放等。與具體硬件體系結構相關的內存管理代碼位於arch/*/mm目錄下,例如arch/i386/mm/Fault.c 。
modules目錄存放了已建好的、可動態加載的模塊文件目錄,是個空目錄,用於存放編譯時產生的模塊目標文件。
net目錄裏是核心的網絡部分代碼,其每一個子目錄對應於網絡的一個方面。
ReadMe目錄裏是核心及其編譯配置方法簡單介紹
REPORTING-BUGS目錄裏是有關報告Bug 的一些內容
Rules.make目錄裏是各類Makefilemake所使用的一些共同規則
scripts目錄包含用於配置核心的腳本文件等。
通常在每一個目錄下都有一個.depend文件和一個Makefile文件。這兩個文件都是編譯時使用的輔助文件。仔細閱讀這兩個文件對弄清各個文件之間的聯繫和依託關係頗有幫助。另外有的目錄下還有Readme文件,它是對該目錄下文件的一些說明,一樣有利於對內核源碼的理解。
命令說明:
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) //在CPU啓動以前凍結CPU # -s shorthand for -gdb tcp::1234 若不想使用1234端口,則可使用-gdb tcp:xxxx來取代-s選項 //在TCP1234這個端口上建立了一個gdp 服務
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以前,也能夠在以後
實驗過程:
第一步:啓動並凍結
如今咱們開始啓動gdb服務
加載符號表
連接gdbserver
設置一個斷點
按c系統開始執行到斷點處暫停
使用list命令,能夠看到start_kernel函數的上下文
在rest_init處設置斷點
start_kernel()是內核的彙編代碼和c代碼的交接處,在此以前,全是由彙編代碼完成各類環境的初始化工做,包括將內核代碼載入內存,設置C運行環境等等。
當運行到start_kernel()的時候,咱們能夠大體分析以下:
1.手工建立0號進程init_task(),他最終變成idle進程
set_task_stack_end_magic(&init_task);
init_idle()函數會把init_task加入到cpu的運行隊列中去,在沒有其餘進程加入cpu隊列的時候,init_task會一直運行,當其餘進程加入進來的時候,init_task就會被設置成idle,並使用調度函數將切換到新加入進來的進程上。
2.初始化各個模塊
模塊以下:內存管理模塊 中斷 調度模塊等等
3.運行到rest_init(),初始化進程
其中rest_init()是內核初始化的最後一步,它也是全部進程的祖先。
下面咱們分析這個函數
注意這段代碼:
kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND); //初始化第一個用戶態進程,也就是1號進程
OK,接下來咱們繼續分析
static void noinline rest_init(void) 417 __releases(kernel_lock) 418 { 419 kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND); 420 numa_default_policy(); 421 unlock_kernel();423 /* 424 * The boot idle thread must execute schedule() 425 * at least one to get things moving: 426 */ 427 preempt_enable_no_resched(); 428 schedule(); 429 preempt_disable(); 430 431 /* Call into cpu_idle with preempt disabled */ 432 cpu_idle(); 433 }
在這段代碼中,咱們只須要分析
kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
cpu_idle(); //cpu隊列進程的切換,將0號進程設置idle
這兩句就好了,其餘的能夠暫時忽略
kernel_thread中傳入的函數init須要進行分析一下,咱們截取部分代碼以下:
run_init_process("/sbin/init"); run_init_process()其實是經過嵌入彙編構建一個相似用戶態代碼
run_init_process("/etc/init"); 同樣的 sys_execve()調用,其參數就是要執行的可執行文件名,也就
run_init_process("/bin/init"); 是這裏的 init process 在磁盤上的文件。
run_init_process("/bin/sh");
由於實驗樓的爛環境總是卡,因此咱們就不截圖分析了。
在run_init_procrss()處斷點調試,run_init_process 就是經過 execve()來運行 init 程序。
到這裏idle_task()的任務完成; 將會被調度函數設置爲空閒的進程。
總結:Linux在start_kernel執行以前都是彙編代碼,在他執行後,各類環境初始化後,執行c代碼。0號進程是做者手工建立的,它的任務就是在CPU的隊列中沒有進程的時候一直執行,在有進程的時候切換到新進程,然後被設置爲空閒狀態。start_kernel最後一部分是第一個用戶態進程PID=1的正式生成,就是rest_init(),這個進程是系統的1號進程,這個時候0號進程會被設置成idle進程。1號進程執行,生成系統所需的全部進程,其實就是調用了run_init_process()函數加載文件,生成進程