2019-2020-1 20199311《Linux内核原理与分析》第四周作业

  1. 问题描述

通过这一周的学习,我们进一步学习了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系统内核的更多功能。

posted @ 2019-10-11 12:14  buguoliujibugaiming  阅读(188)  评论(0编辑  收藏  举报