1. 程式人生 > >分析Linux核心啟動過程:從start_kernel到init

分析Linux核心啟動過程:從start_kernel到init

 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號使用者態的程序。然後作業系統就執行起來了。