跟踪Linux启动——从start_kernel到init进程
在线学习的MOOC的《Linux内核分析》在线课程,感受颇多,现记录实现过程于下:
本次试验主要是研究从start_kernel到init进程的过程。
首先要知道start_kernel是什么函数,start_kernel相当于C语言中的main函数,都是程序的入口,在start_kernel函数中式对一些模块的初始化,如文件系统初始化函数fs_init和存储器管理模块的初始化函数mm_init;start_kernel函数之前的一些初始化启动过程都是通过更低级的语言(汇编)编写的,从start_kernel开始,内核代码都是C语言实现。
init进程是第一个用户进程,由内核的0号进程所创建,该进程所所有用户态进程的父进程;再来了解一下init进程的启动过程,在调用kernel_thread(init,NULL,0)函数时,会调用main.c中的另外一个函数(init())。注意init()函数和init进程是不同的概念。通过执行inin()函数,系统完成了下述的工作:
建立dbflush、kswapd两个新的内核线程。
初始化tty1设备。该设备对应了多个终端(concole),用户登录时,就是登录在这些终端上的。
启动init进程。Linux首先寻找“/etc/init”文件,如果找不到,就接着找“/bin/init”文件,若仍找不到,再去找“/sbin/init”。如果仍无法找到的话,启动将无法进行下去。否则,便执行init文件,从而建立init进程。
接下来我们通过实验来观察一些具体的过程:
首先在终端输入命令:
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
对命令中的参数作一下解释:
-S : 在内核开始(QEMU)执行处冻结CPU
-s : 运行时将1234端口开放为调试端口
接着打开另外一个终端,输入命令:
gdb
gdb调试器开始运行,初始化完成后输入命令:
(gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
(gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
设置远程调试端口为1234
(gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后
在start_kernel函数入口处设置断点
在试验中我对操作中的两个镜像文件产生疑惑,随即查资料了解到:
bzImage : vmlinuz经过gzip压缩后的文件,适用于大内核;
vmlinux : 未压缩的内核,vmlinuz是vmlinux的压缩文件,vmlinuz是可引导的、压缩的内核。vmlinuz位于/boot/vmlinuz,它一般是一个软链接;
我们在终端输入命令:
c
内核执行到如下图所示位置:
从图中显示的信息可以看出,最先是从ROM上开始引导,ROM上的代码即和硬件相关的BIOS代码,计算机上电,程序运行寄存器CS:IP的值为0xFFFF:0000(实际地址为0x0000FFF0,即CS<<4 + IP),计算机硬件将BIOS代码映射到内存的0x0000FFF0处,计算机开始执行这段代码。
接下来的信息显示Linux的内核镜像正在被解压缩,因为内核镜像bzImage是一个gzip的压缩文件,而且在这镜像文件的开头部分内嵌有gzip解压缩代码。接下来是对解压缩文件进行语法分析,分析是ELF文件(编译出来最原始的文件),接下来信息显示不需要重定位。
一切准备工作做完,接下来就是正式加载Linux内核了。
我们再在rest_init函数处设置断点:
输入命令c继续执行到该函数处:
从输出信息可以看到,这一阶段完成的都是一些模块的初始化工作,比如Console和SELinux的初始化。
接下来我们继续执行到结束:
下面我们对init进程的父进程ideo进程做一个深入的解释:
ideo进程是系统执行后创建的第一个进程,进程号为0,也是系统中唯一一个没有使用fork函数创建的进程,在一个CMP(多处理器)系统中,每一个处理器单元都有一个独立的运行队列,每个处理器队列上都有一个ideo进程(即多少处理器单元就有多少ideo进程),处理器队列空闲的时候就是ideo进程工作的时候,系统不让cpu处于空闲的状态。
ideo进程的创建和操作系统的启动过程息息相关,系统是从BIOS加电自检,载入MBR中的引导程序(LILO/GRUB),再加载Linux内核开始运行的,一直到指定shell开始运行告一段落,这时用户开始操作Linux。而大致是在vmlinux的入口startup_32(head.S)中为pid号为0的原始进程设置了执行环境,然后原始进程开始执行start_kernel()完成Linux内核的初始化工作。包括初始化页表,初始化中断向量表,初始化系统时间等。继而调用 fork(),创建第一个用户进程 : kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); 这个进程就是着名的pid为1的init进程,它会继续完成剩下的初始化工作,然后execve(/sbin/init), 成为系统中的其他所有进程的祖先。
整个过程简单的说就是,原始进程(pid=0)创建init进程(pid=1),然后演化成idle进程(pid=0)。init进程为每个从处理器(运行队列)创建出一个idle进程(pid=0),然后演化成/sbin/init。
最近忙着毕业设计,时间很紧,对Linux系统的启动掌握的不是很好,有待以后加强。
天下难事,必作于易;天下大事,必作于细。
端正心态,认真学习,切忌浮躁。
Allen 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
posted on 2015-03-22 21:00 lingzshen 阅读(2229) 评论(0) 编辑 收藏 举报