朱宇轲 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
在本次的实验中,我们将通过简单分析Linux内核代码来探讨操作系统的启动过程。
计算机启动的过程其实在Andrew S.Tanenbaum所著的《现代操作系统》(中文版第18页)中就有大略的描述:
1.计算机启动时,存储在RAM中的BIOS程序检查计算机的所有设备,包括RAM、键盘、鼠标、ISA及PCI总线上的设备等,这些设备将被记录下来。
2.BIOS寻找可引导介质,从软盘、CD或硬盘中加载操作系统,将控制权交给操作系统中的引导程序。
3.操作系统询问BIOS,获得各个设备的配置信息,然后开始进行初始化工作。
而我们今天主要是分析linux内核在进行初始化工作时的步骤,首先是实验代码和截图:
进入实验楼的系统中,首先进入linux内核文件,并启动内核镜像:
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
然后另开一个shell来利用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之前,也可以在之后
通过输入"c"来启动内核初始化,当执行到break设定的函数时会自动停止。如图所示。
下面我们来对内核初始化过程进行简单分析。
内核的初始化过程由start_kernel函数开始,至第一个用户进程init结束,调用了一系列的初始化函数对所有的内核组件进行初始化。其中,start_kernel、rest_init、kernel_init、init_post等4个函数构成了整个初始化过程的主线。如下图所示:
首先我们分析start_kernel:
注意第510行,set_task_stack_end_magic设置了第0号进程的进程管理块,也就是init_task。它是一个比较特殊的进程,其他进程一般要通过fork命令来完成,只有它是由内核开发者人为制造出来的,它的初始化由arch/x86/kernel/init_task.c中的代码来完成。
接下来则进行了一系列的初始化工作,直到执行到最后一行,进入rest_init函数。
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. */ 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_startup_entry(CPUHP_ONLINE); }
在rest_init中,系统开始真正地产生进程。第11行代码“kernel_thread(kernel_init, NULL, CLONE_FS);”就是具体的创建进程的语句。
kernel_init函数将完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。
而此时,我们第一个创建的进程init_task已经完成了自己的使命,它被start_kernel函数中的shed_init()函数转化为一个idle task,当运行队列中没有别的进程时,它便会进入不断的循环,直到运行队列中加入新的进程时才将控制权切换到新进程上。由此内核就启动完毕了。
参考资料
《现代操作系统》 Andrew S. Tanenbaum 机械工业出版社
http://book.51cto.com/art/201007/213598.htm