鄭德倫 原創作品轉載請註明出處《Linux內核分析》MOOC課程
http://mooc.study.163.com/course/USTC-1000029000
STEP1:在自己的linux系統中搭建實驗環境。
1.下載linux-3.18.6的內核源碼,並且編譯
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分鐘多則數小時
2.製作根文件系統
cd ~/LinuxKernel/
mkdir rootfs
git clone https://github.com/mengning/menu.git
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
3.啓動MenuOS
cd ~/LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
然後就啓動了MenuOS。
STEP2:使用GDB調試內核跟蹤啓動過程。
在使用gdb跟蹤調試內核之前需要先重新配置編譯Linux使其攜帶調試信息。
由於make menuconfig需要ncurses-dev和tk4-dev庫。
所以我們先輸入命令sudo apt-get install ncurses-dev
和sudo apt-get install tk4-dev
然後輸入make menuconfig進入Kernel Configuration界面
選擇Kernel hacking進入
選擇Compile-time checks and compilr options —>進入
按Y選擇Compile the kernel with debug info
然後執行make重新編譯內核。
編譯完成之後輸入以下的命令,讓CPU凍結在開始的時候。
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選項
然後打開GDB遠程調試,另外開啓一個終端
輸入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之前,也可以在之後
在start_kernel設上斷點然後
(gdb)c 繼續執行到達斷點處
輸出(gdb)list顯示出上下文
我們繼續設置斷點, break rest_init()
然後輸入c執行到斷點處
然後輸入list顯示出上下文。
STEP3:分析start_kernel的代碼
內核幾乎所有模塊的初始化都會經過start_kernel來進行,
asmlinkage __visible void __init start_kernel(void)
{
.......
/*init_task即手工創建的PCB,0號進程就是最終的idle進程*/
set_task_stack_end_magic(&init_task);
........
/*初始化中斷向量*/
trap_init();
/*內存管理模塊初始化*/
mm_init();
/*調度模塊初始化*/
sched_init();
....
/*其他初始化*/
rest_init()
}
我們再來看下最後的rest_init()
/*rest_init()會一直存在,是0號進程,並且創建了1號進程,並創建了一些其他的服務進程*/
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
/*
* 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.
*/
/*初始化了第一個用戶態的進程1號進程*/
kernel_thread(kernel_init, NULL, CLONE_FS);
numa_default_policy();
/*創建內核線程管理系統資源*/
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
/*執行cpu_idle_loop, cpu_idle_loop是一個while(1)循環,當系統沒有任何需要執行的進程的時候就調度到idle進程*/
cpu_startup_entry(CPUHP_ONLINE);
}
由此可見,rest_init()最後執行cpu_startup_entry();cpu_startup_entry會調用cpu_idle_loop(), 在cpu_idle_loop()裏面有個while(1)的循環一直執行,作爲idle進程,pid是0號,此進程會一直執行下去,並且在系統沒有任何需要執行的進程時,調度到此進程。
Linux內核的啓動在宏觀上來看,就是start_kernel()來進行各種初始化工作,最終執行到rest_init()來初始化0號進程和1號用戶態的進程。然後操作系統就運行起來了。