王雪 原创作品转载请注明出处 《Linux内核分析》MOOC课程 http://mooc.study.163.com/course/USTC-1000029000
一、基础知识
(1)Linux内核中常用的目录
1.arch/:与体系结构相关的代码,(内容庞大,支持不同cpu的源代码)。
2.Documentation/:文档文件
3.fs/:file system 文件系统
4.drivers/:与驱动相关的代码
5.lib/:链接库文件
6.ipc/:与进程间通信相关的文件
7.mm/:memory managment 内存管理
8.kernel/:Linux内核的核心代码
9.init/:与内核启动相关的代码,其中有main.c,main.c中的start_kernel()函数是初始化Linux的起点
(2)使用gdb调试工具的基本指令
1.gdb 可执行文件名:进入gdb调试
2.(gdb)l:相当于list,从第一行开始列出源码
3.(gdb)回车:重复上一次指令
4.(gdb)break 函数名/行数:在某一行或者某个函数处设置断点
5.(gdb)info break:查看断点信息
6.在断点内可以使用:r(run运行),n(next单句执行),c(contiune继续执行)
7.(gdb)bt:查看函数堆栈
8.(gdb)finis:退出函数
9.(gdb)q:退出gdb
(3)Linux内核启动过程
Linux系统的启动分为4个部分:引导加载程序(bootloader),Linux内核,文件系统,应用程序,其中,在Linux内核启动时我们完成了系统的重要初始化,并创建了init进程。
内核启动主要可以分为两个阶段:第一阶段主要是与硬件相关的初始化工作,这次我们讨论的主要是第二部分。
在内核源码的init/main.c中,定义了内核启动的入口函数start_kernel(),在start _kernel()中调用相关处理程序函数,
创建了0号进程(set _task _stack _end _magic(&init _task)),
设置体系结构的相关环境(setup _arch),
初始化内存结构(bootmem _init),
开启mmu,
建立页表(paging _init),
初始化串口(console _init),
0号进程是所有进程的父进程,0号进程创建了1号进程init,又创建了其他服务例程。
二、实验执行——通过gdb调试,分析Linux的内核启动过程
(1)实验步骤:
使用实验楼已配置好的环境,
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
进入了菜单内核:
可以执行help、version和quit命令
进入gdb调试:
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
其中:
-S 是指在初始化cpu之前将cpu冻结
-s 是指在-gdb tcp ::1234,创建一个gdb server
已将cpu冻结
另开一个shell窗口:
(gdb)file linux-3.18.6/vmlinux
(gdb)target remote:1234
(gdb)break start_kernel
(gdb)c
在start_kernel处设置断点,执行c调试,冻结的系统开始启动,断点执行到start _kernel
若设置断点在rest_init,
利用list命令查看,发现rest_ init在start _kernel的尾部,几乎所有重要的内核模块初始化都在start _kernel完成,这是内核启动的关键。
(2)实验分析
进入并分析start_kernel:
1.init_task全局变量:即手工创建的PCB,0号进程即最终的idle进程.
在start_ kernel()中set _ task_ stack_ end_ magic (&init_task)中进行设置和初始化。0号进程是所有进程的父进程
2.trap_init():初始化中断向量
在trap_ init()中set_ intr_ gate(…)设置了很多中断门,执行不同的硬件中断。set _ system _ trap _ gate(SYSCALL_ VECTOR,&system_call),设置系统陷阱门,就是系统调用!
3.mm_init()内存管理的初始化,sched _ init()调度模块的初始化
4.在start_ kernel的最后执行rest_init():
在rest_ init()中调用了kernel_thread(kernel _init,NULL,CLONE _FS),在kernel _init中,调用了run _init _process (),默认的init是根目录下的init,如果在根目录下没有,则找/sbin/init,/etc/init,/bin/init,/bin/sh作为1号进程,则kernel _init创建了一个1号进程,
执行kernel _ thread(kernel _ threaddd,NULL,CLONE _ FS|CLONE _ FILES),创建内核线程管理系统的运行资源,在rest init()尾部,进入了cpu startup _ entry(…),进入cpu _ idle,执行cpu _ idle _ loop(),在cpu idle loop()进行while(1)的循环处理,这就是0号进程。当系统没有进程需要执行时就调度到idle进程
start_kernel()启动时rest _init就一直存在,也就是0号进程,0号进程创建了1号进程,还创建了其他的服务线程,这样内核就启动起来了。
三、实验总结
本次实验主要分析了在内核启动过程中的第一个函数:start_kernel的执行。
start_kernel()是init/main.c第一个启动的函数,主要完成了很多重要的与硬件平台相关和内核相关的初始化,并创建了init进程。
在init的start_ kernel()中调用了setup_ arch()进行与体系结构相关的第一个初始化,
通过bottom_init()函数根据系统定义的meminfo结构进行内存初始化,
最后paging_init()开启mmu,创建内核页表,映射所有的物理内存和I/O空间,
完成了创建异常向量表和初始化中断处理函数,初始化系统核心进程调度器和时钟中断处理机制,初始化串口控制台(serial_console)等等,
当所有的相关操作结束后,start_ kernel()会调用rest_ init()进行最后初始化,包括创建系统的第一个进程——init进程,init进程首先进行一系列的硬件初始化,然后通过命令行传递过来的参数挂在根文件系统,init进程会执行用户传递过来的参数执行用户指定的命令或者执行/sbin/init,/etc/init,/bin/init,/bin/sh 之一完成初始化工作,cpu_idle会被调用使系统处于闲置状态并等待用户输入。
到此,Linux内核启动完毕。
注意:
(1)内核启动过程包括start_kernel之前和之后,之前全部是做初始化的汇编指令,之后开始C代码的操作系统初始化,最后执行第一个用户态进程init
(2)从rest_ init开始,Linux开始产生进程,因为init_ task是静态制造出来的,pid=0,它试图将从最早的汇编代码一直到start_ kernel的执行都纳入到init_ task进程上下文中。在rest _init函数中,内核将通过下面的代码产生第一个真正的进程(pid=1):
道生一(start_ kernel….cpu_ idle),一生二(kernel_init和kthreadd),二生三(即前面0、1和2三个进程),三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先)
通过这次实验,要重点理解内核启动时各个函数的功能以及调用顺序等,Linux启动过程很复杂,有很多硬件软件等初始化,应好好理解!