- 问题描述
通过这一周的学习,我们进一步学习了linux内核源代码的目录结构,并基于linux内核源代码构造了一个简单的操作系统MenuOS,同时通过分析MenuOS的启动过程,使用gdb跟踪调试内核从start_kernel到init进程启动,并分析linux内核启动部分函数的代码,进一步深入了解linux内核的工作原理。
2. 解决步骤
2.1 linux内核源码介绍
Linux内核源码的目录结构如下图所示
下面介绍部分比较重要的目录
-
arch:arch是architecture的缩写。内核所支持的每种CPU体系,在该目录下都有对应的子目录。每个CPU的子目录,又进一步分解为boot,mm,kernel等子目录,分别包含控制系统引导,内存管理,系统调用等。
-
block:部分块设备驱动程序。
-
crypto:加密、压缩、CRC校验算法。
-
documentation:存放内核的一些文档。
-
drivers:驱动目录,里面分门别类的存放了Linux内核支持的所有硬件设备的驱动源代码。
-
fs:存放各种文件系统的实现代码。每个子目录对应一种文件系统的实现。
-
include:内核所需要的头文件,存放公共的(各种CPU体系结构公用的)头文件。
-
init:内核初始化代码。
-
ipc:进程间通信的实现代码。
-
kernel:linux大多数关键的核心功能都是在这个目录实现。
-
lib:库文件代码。
-
mm:mm目录中的文件用于实现内存管理中与体系结构无关的部分。
-
net:网络协议的实现代码。
-
samples:一些内核编程的范例。
-
scripts:配置内核的脚本。
2.2 构造一个简单的Linux内核
这里采用“实验楼”环境,实验楼环境的linux内核版本为3.18.6。使用如下命令构建一个linux内核。
cd LinuxKernel/
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
qemu仿真kernel;bzImage是vmLinux经过gzip压缩后的文件,是压缩的内核映像,“b”代表的是“big”(bzImage适用于大内核,zImage适用于小内核)。根文件系统一般包括内存根文件系统和磁盘文件系统。这里的根文件系统也比较简单,只是创建了一个rootfs.img。
2.3 使用gdb跟踪调试
使用gdb跟踪调试内核,加两个参数,一个是-s(在1234端口上创建了一个gdb-server,可以打开另一个窗口,用gdb把带有符合表的内核镜像加载进来,然后连接gdb server,设置断点跟踪内核。),另一个是-S(CPU初始之前就冻结起来)。
内核启动效果如下
打开一个窗口,启动gdb,把内核加载进来,建立连接
使用如下命令
#加载符号表
file linux-3.18.6/vmlinux
#用1234这个端口进行连接
target remote:1234
在start_kernel处设置断点,刚才是stop状态,如果按“c”继续执行,那么系统开始启动执行,启动到start_kernel函数的位置停在断点处
再设置一个断点rest_init,继续执行,停在断点处。
2.4 分析内核启动过程中start_kernel函数作用
init目录中的main.cc源文件是整个linux内核启动的起点,但它的起点不是main函数,而是start_kernel函数,在start_kernel函数中Linux将完成整个系统的内核初始化。内核初始化的最后一步rest_init函数就是启动init进程这个所有进程的祖先。
2.4.1 start_kernel函数
首先执行start_kernel函数,完成一系列的模块、时钟、进程等初始化工作。
asmlinkage __visible void __init start_kernel(void)
{
…
set_task_stack_end_magic(&init_task);
printk(KERN_NOTICE"%s", linux_banner); /* 输出linux版本信息 */
setup_arch(&command_line); /* 设置与初始化硬件体系相关的环境并调用 */
sched_init() /* 初始化调度器,先于中断开始前 */
printk(boot_command_line); /* 提取分析核心启动参数过程(从bootloader中传递) */
parse_early_param();
parse_args
trap_init();
return
early_irq_init(); /* 中断初始化过程 */
init_IRQ();
init_timers(); /* 初始化定时器 */
timekeeping_init();
time_init(); /* 设置定时器及返回当前时间 */
console_init() /* 初步的初始化控制台 */
vmalloc_init();
vfs_caches_init_early();
mem_init(); /* 初始化内存并计算可用内存大小 */
kmem_cache_init(); /* 初始化SLAB缓存分配器 */
calibrate_delay(); /* 延迟校准 */
fork_init(num_physpages); /* 初始化max_threads,init_task参数为fork()提供参考 */
buffer_init(); /* 初始化块设备读写缓冲区 */
vfs_caches_init(num_physpages);
signals_init(); /* 初始化内核信号队列 */
rest_init(); /* 最后实际进入reset_init()函数,包括所有剩下的硬件驱动,线程初始化等过程,这也最终完成start_kernel的启动过程 */
}
2.4.2 rest_init函数
通过rest_init函数新建kernel_init和kthreadd内核进程
kernel_thread(kernel_init, NULL, CLONE_FS); #创建一个内核线程,实际上是一个内核进程,其中pid=1。其中kernel_init只是一个函数
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
#在kthreadd函数中kthread_create_list全局链表中维护的内核线程。当调用kthread_create时,会创建一个kthread,并被添加到kthread_create_list链表中。当进程执行完毕后,就会被从链表中删除。
3.总结
通过这周的学习,我初步了解了linux内核启动过程的大概步骤,在接下来的学习中,将更进一步学习linux系统内核的更多功能。