实验三:跟踪分析 Linux 内核的启动过程
实验过程
执行
cd ~/LinuxKernel/ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
结果如下所示。
执行$ qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
另开一个 shell 窗口
$ gdb
(gdb)file linux-3.18.6/vmlinux
(gdb)target remote:1234
(gdb)break start_kernel
执行结果如下所示
输入c,执行到断点处为止
在rest_init处设置断点
用list查看代码
实验总结
分析start_kernal函数
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函数以便在运行队列中有新进程加入时切换到该新进程上。
系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。整个linux系统的所有进程也是一个树形结构。Linux下有3个特殊的进程,idle进程(PID = 0), init进程(PID = 1)和kthreadd(PID = 2)。
最初执行的进程即是0号进程init_task,它是被静态产生的,内存栈的位置固定,执行一些初始化的工作。一直到start_kernel开始调用执行sched_init(),0号进程被init_idle(current, smp_processor_id())进程初始化成为一个idle task,变成上一次实验中的进程一样的,通过一个while循环不断执行,只要运行栈里没有别的进程它就执行,循环中不断检测运行栈里是否有其他进程并通过schedule函数进行调度。
init进程由idle通过kernel_thread创建,在内核空间完成初始化后, 加载init程序,是系统中所有其它用户进程的祖先进程Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。
kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理,它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthread的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程。