作者:xujianguo
原创作品转载请注明出处,《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
——————————————————————————————————————————————————————-————
实验目的:
通过gdb调试来加强理解Linux系统启动过程。
实验环境:
实验楼:www.shiyanlou.com
实验步骤:
1.预配置:
- make menuconfig
- kernel hacking—>
- compile-time checks and compile options
-
[*] compile the kernel with debug info
3.启动过程预配置:
- 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选项
4.另外开启一个终端,进入gdb调试器。
5.调试启动过程:
- (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之前,也可以在之后
6.设置相关断点,利用编译命令c和显示命令list来获取启动详情。
start_kernel:
cpu_start_entry:
run_init_process:
7.启动完成。
实验分析:
参考源码:
http://codelab.shiyanlou.com/xref/linux-3.18.6/init/main.c
系统启动进程是从start_kernel函数中开始执行的,详细代码如下:
从上面的代码可以看出,start_kernel函数开始就完成一部分初始化操作(如中断,trap_init,内存管理mm_init等),配置init_task进程的结构的详细信息;随后进行调度的初始化(sched_init)、时钟设置(time_init)、控制台的初始化启动(console_init())、proc_root_init的运行;然后进行bug检测等辅助环节,最后调用rest_init函数。rest_init函数在启动完init进程后并没有退出,而是继续往下执行道了cpu_startup_entry函数。执行流生成init进程,作为idle进程继续运行下去。
idle进程:Linux引导中创建的第一个进程(只在内核中存在),完成加载系统后,演变为进程调度、交换及存储管理进程,又称0号进程。
init 进程:由idle进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程。
总结:
通过本次实验,对linux内核启动过程有了些详细了解。pid号为0的原始进程设置了执行环境,然后原是进程开始执行start_kernel()完成Linux内核的初始化工作。包括初始化页表,初始化中断向量表,初始化系统时间等。继而调用 fork(),创建第一个用户进程:kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);idle 进程优先级为MAX_PRIO,即最低优先级。早先版本中,idle是参与调度的,所以将其优先级设为最低,当没有其他进程可以运行时,才会调度执行 idle。而目前的版本中idle并不在运行队列中参与调度,而是在运行队列结构中含idle指针,指向idle进程,在调度器发现运行队列为空的时候运行,调入运行。主处理器上的idle由原始进程(pid=0)演变而来。从处理器上的idle由init进程fork得到,但是它们的pid都为0。init进程,它是一个由内核启动的用户级进程。内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。所以,init始终是第一个进程(其进程编号始终为1)。
谢谢前辈的努力和奉献:)
参考资料: