linux内核启动分析 三,Linux内核分析 (三) 使用gdb调试跟踪内核启动过程

程峰 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

今天我们来探索一下内核的启动过程,使用的是 linux-3.18.6 的源码,可以在这里在线阅读。

内核目录结构

打开内核源码,你会看到很多目录,是不是有点眼花缭乱,这些目录到底都是干什么用的呢?

be0d5ba8cfe2e9c67ef5e23ad9082bb8.png

arch。arch 目录包括了所有和体系结构相关的核心代码。它下面的每一个子目录都代表一种 Linux 支持的体系结构.

block。block 层的实现。最初 block 层的代码一部分位于 drivers 目录,一部分位于 fs 目录,从2.6.15开始,block 层的核心代码被提取出来放在了顶层的 block 目录。

crypto。内核本身所用的加密 API,实现了常用的加密和散列算法,还有一些压缩和 CRC 校验算法。

Documentation。这个目录下面没有内核代码,只有很多质量参差不齐的文档,但往往能够给我们提供很多的帮助。

drivers。这个目录是内核中最庞大的一个目录,显卡、网卡、SCSI 适配器、PCI 总线、 USB 总线和其他任何 Linux 支持的外围设备或总线的驱动程序都可以在这里找到。

firmware。固件。什么是固件?固件其实是软件,不过这个软件是固化到 IC 里面运行的,就像 S5PV210 里的 iROM 代码。

fs。虚拟文件系统(VFS,Virtual File System)的代码,和各个不同文件系统的代码都在这个目录中。Linux 支持的所有文件系统在 fs 目录下面都有一个对应的子目录。比如 ext2 文件系统对应的是 fs/ext2 目录。

include。include 目录包括编译核心所需要的大部分头文件,例如与平台无关的头文件在 include/linux 子目录下。

init。内核的初始化代码。包括main.c、创建早期用户空间的代码以及其他初始化代码。

IPC。即进程间通信(interprocess communication)。它包含了共享内存、信号量以及其他形式 IPC 的代码。

kernel。内核中最核心的部分,包括进程的调度(kernel/sched.c),以及进程的创建和撤销(kernel/fork.c和kernel/exit.c)等,和平台相关的另外一部分核心的代码在 arch/*/kernel 目录。

lib。库代码,实现了一个标准 C 库的通用子集,包括字符串和内存操作的函数(strlen、mmcpy和其他类似的函数)以及有关sprintf和atoi的系列函数。与 arch/lib 下的代码不同,这里的库代码都是使用C编写的,在内核新的移植版本中可以直接使用。

mm。包含了体系结构无关部分的内存管理代码,体系相关的部分位于 arch/*/mm 目录下。

net。网络相关代码,实现了各种常见的网络协议,如 TCP/IP、IPX 等。

scripts。该目录下没有内核代码,只包含了用来配置内核的脚本文件。当运行 make menuconfig 或者 make xconfig 之类的命令配置内核时,用户就是和位于这个目录下的脚本进行交互的。

security。这个目录包括了不同的 Linux 安全模型的代码,比如 NSA Security-Enhanced Linux。

sound。声卡驱动以及其他声音相关的代码。

tools。linux 中用到的一些有用工具。

usr。实现了用于打包和压缩的的 cpio 等。

virt。内核虚拟机相关的,暂时不用管。

哎呀,好多好乱,我自己也搞晕了...其实这里我们只需要重点关注arch\x86、init和kernel三个目录,其它的可以暂时忽略,免得把自己看晕了。

调试

这里我们使用实验楼上已经搭建好的实验环境。(不知道是不是网络原因,我用的时候实验楼好卡好卡,所以大家也可以参照这张图片在自己电脑上搭建实验环境。)

进入实验目录cd LinuxKernel/,使用如下命令启动内核:

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

注意后面的-s和-S选项:

-s是-gdb tcp::1234的缩写,是指在 tcp 的1234端口上创建了一个gdb server。

-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 之前,也可以在之后

这里的 start_kernel 函数就相当于我们平时写的 C 程序的 main 函数,是内核初始化的入口函数。到这一步,我们便可以进行调试了。(因为实验环境太卡太卡...所以后面就没有贴 gdb 调试的截图了,我们直接在源码上分析,还请大家谅解。)

b276b0f75214b12c9930df381ffcdb2f.png

启动过程分析

我们从start_kernel函数开始分析内核的启动过程。因为start_kernel内容比较多,我们选择其中的几个重要的初始化部分进行解释。

首先是init_task,它是一个全局变量,是0号进程的PCB,在此初始化。0号进程是一个特殊的进程,它是内核开发者人为制造出来的,而不是其他进程通过do_fork来完成。

180221e0a605b918e07efe471feaa979.png

然后是561行的trap_init()函数,对应源码在/linux-3.18.6/arch/x86/kernel/traps.c的792行。下面是它的部分代码:

197d4f327aca6c038092ca8664f87930.png

该函数完成对一些中断向量的初始化,其中设置了很多中断向量门,其中比较重要的是839行的set_system_trap_gate,设置系统陷阱门,即系统调用。

初始化完中断向量后,开始初始化内存模块mm_init()和进程调度模块sched_init,在sched_init中初始化了0号进程,即idle进程,但是并没有设置idle进程的NEED_RESCHED标志,所以还会继续完成内核初始化剩下的事情。

在完成大部分模块的初始化后,start_kernel函数的最后还有一个rest_init初始化函数,这个函数到底初始化了哪些东西呢?我们来看看。

38283788bdf0b75eedf59c1d8496b6c8.png

rest_init函数做了一件非常重要的事,它调用系统函数kernel_init创建了1号用户态进程init,它是所有用户态进程的祖先。同时它还创建了一个内核线程kthreadd用来管理系统的资源。最后调用cpu_startup_entry函数启动0号进程,当系统没有进程需要执行时就调度到idle进程。

总结

idle进程从内核启动后就会一直存在,相当于 windows 系统上的 systemidle 进程,只不过在 linux 系统下我们看不到,idle进程创建了1号进程init,还创建了一些内核线程。这样,系统就启动起来了。

参考

http://book.51cto.com/art/201007/213543.htm

http://www.cnblogs.com/sirsunny/archive/2004/12/15/77506.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值