第三章
这一章接触内核源代码,对内核源码进行编译和调试跟踪
一、预备知识:
内核:整个操作系统的最底层,它负责了整个硬件的驱动以及提供各种系统所需的内核功能。内核实质上是系统上面的一个文件而已,这个文件包含了驱动主机各项硬件的检测程序与驱动模块。当系统读完BIOS并加载MBR内的引导装载程序后,就能够加载内核到内存当中。然后内核开始检测硬件,挂载根目录并取得内核模块来驱动所有的硬件,之后调用/sbin/init就能依序启动多有系统所需要的服务了。
Qemu :以GPL许可证分发源码的模拟处理器,能启动那些为不同中央处理器编译的Linux程序。
二、构造一个简单的Linux内核
构造的MenuOS系统是由Linux内核镜像和根文件系统集成起来的。
Linux内核在PC上以文件的形式存在(保存成磁盘文件形式),就是所谓的“映像文件”。内核编译(make)之后会生成两个文件,一个Image,一个zImage,其中Image为内核映像文件,而zImage为内核的一种映像压缩文件,Image大约为4M,而zImage不到2M。为了能使用zImage这个压缩版本,必须在它的开头加上解压缩的代码,将zImage 解压缩之后才能执行,因此它的执行速度比Image要慢。但考虑到嵌入式系统的存储空容量一般都比较小,内核要常驻内存,采用zImage可以占用较少的存储空间,因此牺牲一点性能上的代价也是值得的,所以一般嵌入式系统均采用压缩的内核映像文件,即zImage。
根文件系统首先是一种文件系统,该文件系统不仅具有普通文件系统的存储数据文件的功能,但是相对于普通的文件系统,它的特殊之处在于,它是内核启动时所挂载(mount)的第一个文件系统,内核代码的映像文件保存在根文件系统中,系统引导启动程序会在根文件系统挂载之后从中把一些初始化脚本(如rcS,inittab)和服务加载到内存中去运行。先将/dev/ram0挂载,而后执行/linuxrc.等其执行完后。切换根目录,再挂载具体的根文件系统.根文件系统执行完之后,也就是到了Start_kernel()函数的最后,执行init的进程,也就第一个用户进程。对系统进行各种初始化的操作。
在实验楼中的虚拟机里,直接进入LinuxKernel目录使用如下命令就可以运行Linux内核源码和跟文件系统
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rotes.img
其中bzImage是vmlinux经过gzip压缩后的文件,适用于大内核;vmLinux是编译出来的最原始的内核ELF文件;initrd是内存根文件系统,只创建了一个rootfs.img,其中只有一个init的功能,用menu程序替代init, 内核启动完成后进入menu程序。
三、跟踪调试Linux内核的启动过程
Qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rotes.img -s -S
-S CPU初始化之前将其冻结起来
-s 是默认在tcp::1234端口上创建了一个gdb-server
再打开一个窗口,启动gdb,把内核加载进来,建立连接
file linux-3.18.6/vmlinux #把带有符号表的内核镜像加载进来
target remote:1234 #用tcp:1234端口来链接gdb server
然后在start_kernel函数和rest_init处设置端点,调试运行
四、对于start_kernel( )和rest_init( )的分析
start_kernel( )相当于main.c中的main函数,是首先运行的,在此函数调用之前,内核代码主要是汇编语言编写的,用于完成硬件系统的初始化工作,为C代码的运行设置环境。这个函数主要进行各个模块初始化工作,trap_init()(中断向量的初始化)、mm_init()(内存管理的初始化)sched_init()(调度模块的初始化)等。
init_task() :0号进程,初始化的起点
struct task_struct init_task = INIT_TASK(init_task);
可以看出 init_task(0号进程)是 task_struct 类型,是进程描述符,使用宏INIT_TASK对其进行初始化,并且init_task是唯一没有通过fork方式产生的进程。
rest_init( ):
在此函数中调用kernel_thread进程来创建kernel_init和kthreadd内核线程
kernel_init()内核线程为1号内核线程,负责执行内核的部分初始化工作及进行系统配置,最后调用do_execve加载init程序,最后演变成用户态1号进程——init进程。
kthreadd()函数的任务是管理和调度其他内核线程 kernel_thread。for 循环中运行 kthread_create_list 全局链表中维护的 kthread, 在create_kthread()函数中,会调用 kernel_thread 来生成一个新的进程并被加入到此链表中,因此所有的内核线程都是直接或者间接的以 kthreadd 为父进程。
小结:
本周学习了Linux内核源代码,对于内核的源代码目录,编译过程有了进一步的了解,但在学习中总会遇到新的问题,例如kernel_thread进程是由init零号进程fork出来的,以及idle进程与0号进程的关系,内核线程与用户进程的区别,这些问题在CSDN论坛中大部分都以解决,但发现内核内部的知识非常高深,还需多多查阅资料