利用gdb分析从start_kernel到init启动的过程

来源:互联网 发布:https协议端口 编辑:程序博客网 时间:2024/05/22 17:41

徐晨 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”


今天我们分析一下Linux Kernel启动时的最后一步,即从init/start_kernel()开始分析第一个用户态进程init是如何启动的。

我们知道init/start_kernel()是内核启动后第一个进程,即PID 0。在这里我们利用了孟宁老师所写的menuOS作为根文件系统进行启动,利用gdb进行跟踪调试,找到kernel启动时的调用关系。

首先我们在这里看一下init的编译过程。


我们编译生成init,然后将其放入rootfs中,然后将其写入rootfs.img,启动的时候我们使用initrd选项,意思是将rootfs.img作为根文件系统启动内核,这样我们的init程序的位置就是在/init。

函数start_kernel进行各种不同内核模块的初始化工作:



在初始化完毕后,调用了函数rest_init,该函数非常重要,因为它创建了1号(用户态的第1个进程)和2号进程(内核态的祖先),包括pid1的kernel_init(line403),以及pid2的kthreadd(line405)。与此同时,该函数在执行后期将自己变为idle进程(line420),该进程是一个while(1)的loop。


kernel_init调用run_init_process,启动文件根目录的/init程序,至此pid1的init开始执行


我做了一个调用关系图来说明这个问题:


然后我们用GDB来看一下这个过程:

首先我们在make menuconfig中开启Debug选项,编译内核 


然后在qemu中使用关于-s和-S选项开机


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和rest_init处观察:


我们在run_init_process处加断点,可以看到,该函数调用了/init函数,该函数正是我们之前编译的init程序。


我们backtrace一下之前的栈帧,可以发现,run_init_process来自kernel_init,而spawn该进程的init/main.c:420正是rest_init。


我们再看一下pid2的kthreadd进程

在这里,我在main.c和kthread.c中添加了一句打印代码,便于调试。



from: GDB


就介绍这么多,希望对大家理解这个过程有所帮助。



1 1
原创粉丝点击