作者:张卓
原创作品转载请注明出处:《Linux操作系统分析》MOOC课程 http://www.xuetangx.com/courses/course-v1:ustcX+USTC001+_/about
#####################################
一 Linux源代码目录介绍
现在,我们研究Linux内核,在x86平台上,重点需要我们关注的是下面的三个目录:
1. arch/x86
与x86系统架构相关代码,是我们研究的重点
2. init
init目录下存放内核启动相关的代码
main.c中的start_kernel函数相当于普通的C程序的main函数
3. kernel
Linux内核核心代码都在kernel目录下
二 使用gdb跟踪调试内核从start_kernel到init进程启动过程
1 使用gdb跟踪调试内核
进入 实验楼环境,执行下面的命令即可开始跟踪查看Linux启动过程:1. qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S # 关于-s和-S选项的说明:
2. # -S freeze CPU at startup (use ’c’ to start execution)
3. # -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
另开一个shell窗口
1. gdb
2. (gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
3. (gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
4. (gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后
Note:设置断点是时候,即可以指定函数名,也可以指定行号。
2. 内核启动分析
内核启动从init/main.c开始执行代码,于是可以设置断点:break start_kernelstart_kernel()函数相当于C语言中的mian函数,程序总是从main函数开始执行,
设置一个断点在系统设置PCB的时候,break 510
smp_task_stack_end_magic(&init_task)
init_task是一个全局变量,即手工创建的PCB,0号进程即最终的idle进程。
接下来执行一些列的初始化,在这里我们需要关注一下下面的几个初始化,在后面我们会继续分析他们。
trap_init()
与中断向量相关的初始化
mm_init()
与存储管理相关的初始化
接下来有一个特别重要的初始化,我们下一个断点看一下:break 569
sched_init()与进程调度相关的初始化
再接下来,还是一些列的初始化,我们直接跳到start_kernel函数的最后,下一个断点在680行:break 680
start_kernel函数调用rest_init,于是进入rest_init函数执行:
为了更直观看到进入rest_init()函数中执行,我们在403行下一个断点:break 403
kernel_thread(kernel_init,NULL,CLOSE_FS)
在此处创建一个进程去执行kernel_init,然后在文件系统中寻找并执行init程序, 这个就是系统的1号进程。
接下来执行:
pid = kernel_thread(kthreadd,NULL,CLONE_FS| CLONE_FILES)
这个函数是创建一个内核线程去管理系统资源, 也是就系统的2号进程。
在rest_init函数的末尾下一个断点:break 418
cpu_startup_entry(CPUHP_ONLINE)执行这个函数后系统启动完毕, 进入cpu_idle_loop()(函数位于linux-3.18.6/kernel/sched/idle.c)当系统没有进程需要执行时就调度到idle进程, 这个就是0号进程。
1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先。
三 总结
Linux系统启动,从start_kernel开始执行,中间执行一系列的初始化动作,为后面系统启动开始运行作准备。start_kernel函数的最后会调用rest_init, 然后在rest_init里创建一个进程去寻找文件系统中init程序并执行,这个就是系统的1号进程。1,2号进程后,系统会进入cpu_idle_loop()死循环,这个就是原始的启动进程,也就是0号进程;当系统没有进程需要执行的时候,就会调用这个idle进程。
总之,Linux系统启动过程,可以想象成一个超大型程序的执行过程,按顺序执行所有的初始化;当完成所有的工作时,程序不能退出,而要进人一个死循环。
关于更详细的内核启动函数的分析,可以参考一下的链接
http://blog.csdn.net/xichangbao/article/details/52938240
Linux内核启动之后,文件系统启动过程,可以参考下面的链接
http://blog.csdn.net/cl11010/article/details/24484979