余星光 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
1、实验目的:
跟踪分析一个简单的Linux内核启动过程,理解操作系统是怎么启动的。
2、预备知识:
gdb调试常用参数
r(run) 开始运行程序;
c(continue) 继续运行一直到断点停止
b(break) 设置程序断点
p(print) 打印变量值
s(step) 单步跟踪,进入函数内部
n(next) 单步跟踪,不进入函数
finish 跳出函数调试,并打印返回时的信息
u(until) 跳出循环体
q(quit) 退出gdb
l(list) 显示当前行后面的源程序
bt(backtrace) 查看堆栈信息
info 查看各类gdb信息以及环境信息
clear 清除全部已定义的断点
delete 删除指定的断点
3、实验过程
3.1 使用实验楼虚拟机打开shell,执行如下命令:
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
运行效果如下:
输入help命令,可以看到系统支持三个简单命令:help、version、quit
3.2 使用gdb跟踪调试
1) 另开一个shell窗口,进入LinuxKernel目录,输入如下命令qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s –S
效果如下
# 关于-s和-S选项的说明:
# -S freeze CPU at startup (use ’c’ tostart execution) 在系统启动的时候冻结CPU,使用c键继续执行后续操作
# -s shorthand for -gdb tcp::1234 打开远程调试端口,若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
2) 打开另外一个shell终端,执行以下命令
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)c
运行截图如下:
系统执行到start_kernel()函数,输入list命令可以查看之后的代码;利用break设置断点,c继续执行,list查看函数代码可以方便调试Linux启动过程中用到的任何函数。
4、分析start_kernel()函数
start_kernel()类似C程序中的main函数。在start_kernel()函数之前,内核的代码都是用汇编写的,主要工作是完成一些最基本的初始化与环境设置工作;在start_kernel()中Linux将完成整个系统的内核初始化,在start_kernel的最后,是调用rest_init函数,在rest_init函数中,内核将产生第一个真正的进程,即pid=1的1号进程,而在start_kernel函数中init_task是静态制造出来的,pid=0,我们可以在start_kernel函数的开始处,看到其被初始化的代码,它试图将从最早的汇编代码一直到start_kernel的执行都纳入到init_task进程上下文中,在其初始化工作完成后,就会成为系统的idle进程。事实上在更早前的sched_init函数中,通过init_idle(current, smp_processor_id())函数的调用就已经把init_task初始化成了一个idle task,init_idle函数的第一个参数current就是&init_task,在init_idle中将会把init_task加入到cpu的运行队列中,这样当运行队列中没有别的就绪进程时,init_task(也就是idle task)将会被调用,它的核心是一个while(1)循环,在循环中它将会调用schedule函数以便在运行队列中有新进程加入时切换到该新进程上。