《Linux 内核完全注释》阅读笔记

2023-05-16

在阅读源代码之前,有必要对Linux内核的体系结构、源代码的目录结构有个宏观地了解,《Linux内核完全注释》非常详细地介绍了这方面的内容,所以 这里仅仅进行概述性的讨论,以便让所有的笔记构成一个整体。
    这里主要介绍四部分内容。

1. Monolithic kernel vs. Microkernel

    操作系统为用户提供了利用各种计算机硬件的接口和抽象,为了让计算机稳定工作,它具有两个不同的工作空间,一个叫用户空间,一个是内核空间,后者有更高的 特权级别。内核作为操作系统的核心,处在内核空间,它的体系结构跟操作系统的性能、可扩展性和可维护性、安全和稳定性等各方面关系很大。内核体系结构主要 有三类:Monolithic kernel, Microkernel, hybrid kernel.
    资料[1]给出了前两者的大体结构,见附图1和2,而hybrid内核则介于两者之间。
    从附图中可以看出,Microkernel仅保障基本的进程通信和I/O控制,而把其他的服务,诸如内存管理、进程管理、文件系统等作为服务“模块”在操 作系统的用户空间提供;而Monolithic kernel则把这些服务都在内核空间实现,这一设计结构的区别对两者的诸多方面带来了影响,而影响较大的是内核大小、性能和可扩展性三个方面。
    从内核大小上看,对于Microkernel,因为它仅提供最基本的服务,所以可以很小,而Monolithic kernel把所有重要服务都放在内核里,所以相对更大。内核大小对存储设备的存储空间、内核调试、内核移植都有很大影响。
    从内核执行效率上看,因为Microkernel把一些服务移到了用户空间,所以会增加用户空间和内核空间之间的context switch,对Microkernel的性能造成了一定的影响;而Monolithic kernel则没有这样的问题;不过,因为Microkernel可以小到直接放到处理器的一级缓存中,并可以更方便地针对不同的处理器进行优化,所以从 这方面来说,它的性能可能就Monolithic kernel有一些优势,因而,结合这两方面,目前Microkernel的性能可能并不会比Monolithic kernel差。
    从可扩展性来说,从图中可以看出,Microkernel的主要服务模块是相互独立的,仅跟底层的Microkernel相关,而Microkernel 提供的服务是最基本的,接口有限,可以更加方便地添加或者移除各种服务,而且添加新服务后仅需要编译该服务本身;对于Monolithic Kernel,各个服务之间成层次结构,依赖关系较大,而且都处于内核空间,添加和移除服务会触动其他部分,而且添加服务后需要重新编译整个内核,会带来 一些麻烦。
    实际上,对两者的比较还有很多值得讨论的问题,更多信息建议仔细阅读参考资料[1]。  

    Linux-0.11是Monolithic Kernel,因此在阅读它的源代码时需要把握这个方面,它提供了进程调度、内存管理、进程通信和文件系统几个基本服务。下面进一步介绍Linux- 0.11的体系结构。

2. linux-0.11内核的体系结构
   
    Linux-0.11的内核还没有实现网络接口,仅实现了进程调度、内存管理、进程通信和文件系统几部分,《Linux内核完全注释》用一个图描述了这几 部分的关系,见附图3。
    如图所示,进程调度部分作为“核心”模块,用于控制进程对CPU资源的使用。内存管理部分用于确保所有进程能够安全地共享内存;文件系统部分用于支持对外 部设备的驱动和存储,虚拟文件系统部分通过向所有外部存储设备提供一个通用的文件接口,隐藏了各种硬件设备的不同细节。进程通信部分用于支持多种进程间的 信息交换方式。而网络接口部分提供了对多种网络通信标准的访问并支持许多网络硬件。需要提到的是,在linux-0.11中,虚拟文件系统和网络接口部分 并没有实现。
    《Linux内核完全注释》对上述各个部分进行了详细的说明,包括一些原理和实现细节,这里不再深入讨论,而是通过后面的阅读笔记逐个进行深入分析。

3. linux-0.11内核的源代码目录结构
   
    在具体阅读各部分源代码之前,我们了解一下linux-0.11内核的源代码目录结构,内核源代码的目录结构基本上对照内核体系结构的各个部分。

$ tree -d linux-0.11
linux-0.11
|-- boot            系统引导汇编程序
|-- fs                文件系统
|-- include        头文件(*.h)
|   |-- asm         与CPU体系结构相关的部分
|   |-- linux        Linux内核专用部分
|   `-- sys         系统数据结构部分
|-- init              内核初始化程序
|-- kernel         内核进程调度、信号处理、系统调用等程序
|   |-- blk_drv   块设备驱动程序
|   |-- chr_drv  字符设备驱动程序
|   `-- math     数学协处理仿真处理程序
|-- lib              内核库函数
|-- mm            内存管理程序
`-- tools         生成内核Image文件的工具程序



    需要注意的是,除了那些用于C语言(.c和.h)或者汇编语言(.S和.s)写的源代码外,还有一些Makefile文件,这类文件用于make这个项目 管理工具。关于make工具和Makefile文件的用法,可以参考资料[5],在具体阅读源代码之前,强烈建议你先学会使用make工具。
    下面我们就以内核源代码主目录下的Makefile文件为入口开始阅读linux-0.11的源代码。

4. 开始阅读源代码:linux-0.11内核源代码主目录下的Makefile文件
   
    linux-0.11内核源代码主目录下的Makefile文件用于管理整个linux-0.11项目,它的主要作用是可以让我们利用make工具来产生 linux内核的Image文件。

4.1 make工具简介

    该Makefile文件主要包括两大部分:第一部分是第1到28行,是一些变量;而后面的所有行构成第二部分,是一些"目标:源  动作“段,它告知make工具根据输入参数执行不同的目标动作。
    两个比较重要的目标是all和clean,前者是make工具的默认动作,如果没有任何参数时,就执行它,后者用于删除各类目标文件。

    进入linux-0.11的源代码主目录,可以执行如下操作:

$ cd linux-0.11
$ make  //直接进入Makefile文件中的all目标,以便编译产生linux-0.11内核
$ make clean //清除之前产生的所有目标文件


   
4.2 利用cscope & ctags辅助源代码阅读

    在具体阅读Makefile文件之前,我们通过cscope和ctags文件生成必要的索引,以方便源代码的阅读。

$ cscope -Rbq
$ ctags -R



4.3 配置源代码阅读器和编辑器

    另外,建议通过资料[6],配置一下vim编辑器,比如让vim显示行号,支持语法加亮等。
   
4.4 了解一些基本工具

    如果要读懂每一行,这些工具是有必要了解甚至熟悉的。

// bin86软件包中用于x86语法的汇编语言的汇编和连接工具
as86     汇编工具
ld86      链接工具
// gnu的一些项目开发工具
make    项目管理
gcc        编译器
as         汇编器
ld          链接器
ar         archives(.a)文件处理工具
nm        用于列出目标文件的符号
objcopy  用于复制或者转换目标文件格式
strip     用于删除目标文件中的某些段
// 文件操作工作
mv        文件重命名
rm        删除文件
cp         复制文件
// 字符串操作
grep     字符串查询
sort      字符串匹配
// 把文件系统缓冲区的内容立即写回到磁盘
sync


   
4.5 开始阅读

    因为原书对各个部分的讨论都已经非常详细了,这里仅仅介绍几个原书可能没有介绍到的部分。在具体阅读之前,需要明确的是:这里阅读的linux-0.11 内核源代码是上一节(《Linux内核完全注释》阅读笔记——搭建实验环境)中打过ubuntu 8.04下的所有补丁后的源代码,而不是最原始的源代码,否则,下面提到的行号会对不上,从而影响阅读效果。

    通过vim打开Makefile文件,开始阅读,确保打开了语法加亮(:syn on)、显示行号(:set number)。

    Makefile的第13行原来有-mcpu这个参数,不过当前gcc版本不再支持该参数,而是使用-march参数。

13 CC      =gcc-3.4 -march=i386 $(RAMDISK)


    上面一行引用了第5行,该行的目的是通过gcc的-D参数定义一个宏。

 5 RAMDISK =  #-DRAMDISK=512


    通过在vim文件中链接cscope.out数据库,并执行":cs find t RAMDISK"容易找到在init/main.c文件中引用了该宏,在具体讨论ramdsik时,我们再详细讨论它。

123 #ifdef RAMDISK
124         main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
125 #endif


    按下CTRL+T返回Makefile文件。
   
    Makefile文件的第23行用于指定linux-0.11内核使用的根文件系统所在的设备,作为tools/build工具的第4个参数,在 Makefile文件的第45行被引用到。

23 ROOT_DEV= #FLOPPY
...
45         tools/build boot/bootsect boot/setup tools/kernel $(ROOT_DEV) > Image


    我们发现,这里的$(ROOT_DEV)参数为空,说明传递给toos/build的参数仅有3个,对应地,因为main函数参数argv的第一个项是程 序名本身,加上3个参数,所以argc为4。通过":cs find f tools/build.c",并找到main函数,我们发现。

 90         if (argc == 5) {
...
102         } else {
103                 major_root = DEFAULT_MAJOR_ROOT;
104                 minor_root = DEFAULT_MINOR_ROOT;
105         }


    到argc为4时,根文件系统的主设备号和次设备号分别对应宏DEFAULT_MAJOR_ROOT和DEFAULT_MINOR_ROOT。这两个宏在 哪里定义的呢?通过":cs find g DEFAULT_MAJOR_ROOT"和":cs find g DEFAULT_MINOR_ROOT"找到,在tools/build.c的61和62行。

 61 #define DEFAULT_MAJOR_ROOT 3
 62 #define DEFAULT_MINOR_ROOT 1


    通过阅读原书第14章的表14-3,我们发现,

0x301       /dev/hd1   表示第一个硬盘的第一个分区


    所以说这里指定了linux-0.11内核启动时的根文件系统所在的设备为/dev/hd1,即硬盘的第一个分区。

    下面同样按下CTRL+T直到回到Makefile文件,阅读如下部分:

 41 Image: boot/bootsect boot/setup tools/system tools/build
 42         cp -f tools/system system.tmp
 43         strip system.tmp
 44         objcopy -O binary -R .note -R .comment system.tmp tools/kernel
 45         tools/build boot/bootsect boot/setup tools/kernel $(ROOT_DEV) > Image
 46         rm system.tmp
 47         rm tools/kernel -f
 48         sync


    该部分的各行作用如下:
    第41行表示Image目标依赖boot/bootsect boot/setup tools/system tools/build四个子目标,因此make工具会先确保这四个子目标完成以后再执行Image目标,即执行Image所在行之后的所有以一个TAB 键开头的所有行。
    第42行通过cp把内核文件tools/system复制一份为system.tmp,-f会确保system.tmp是tools/system的最新 拷贝。
    第43行用strip把system.tmp中的所有符号信息删除,因为目标文件中的所有符号在链接以后都已经重定位为内存地址,所以符号本身对程序的运 行就没有了任何影响,可以删除掉(这里仅仅对不使用动态链接库的程序而言,关于动态链接库的更多细节,可以参考资料[7])。这里为什么不删除 tools/system中的符号信息呢,因为tools/system需要提供这些符号信息给gdb调试器。而system.tmp用于产生最终的 Image文件,仅仅需要执行代码就可以。
    第44行利用objcopy把system.tmp文件中对程序运行没有影响的内容给删除掉,生成新的内核文件tools/kernel。关于这部分,建 议大家熟悉一下工具readelf,objdump等工具的用法,并了解一下ELF文件的格式。
    第45行利用内核自带的tools/build工具把内核引导和安装工具boot/bootsect和boot/setup以及内核文件tools /kernel合并,产生一个Image文件。
    第46和47行删除两个临时文件system.tmp和tools/kernel。
    第48行用于刷新文件系统缓冲区,把刚才生成的文件Image和tools/system写到磁盘,从而产生一个linux内核软盘映像文件和包含调试信 息的tools/system文件。

    下面通过vim的命令模式,输入":72",进入Makefile文件的72行(当然你也可以直接用vim +72 Makefile)。

 72         (cd kernel/math; make)


    这一行的作用的进入kernel/math目录,并根据该目录下的makefile文件,编译其中的内容。该行在当前make版本下可以直接写为:

 72         make -C kernel/math


    我们发现,Makefile文件中有很多这样的行,目的是为了通过主目录下的Makefile文件方便地执行各个子目录下的Makefile文件,以便遍 历所有的源代码文件并编译它们。

    linux-0.11主目录下的Makefile文件就介绍到这里,更多的内容请阅读原书的第二章的相关部分。


    到这里,四个部分的内容基介绍完了,不过还有两个比较重要的部分,包括linux-0.11使用的目标文件格式和文件系统类型,得提到一下,因为它们和当 前linux版本支持的目标文件格式和文件系统类型有很大差别。linux-0.11使用的目标文件格式是a.out[3],而当前linux版本支持的 目标文件格式主要是ELF;linux-0.11使用的文件系统类型是Minix[4],而当前linux版本支持的主要文件系统格式有 ext2,ext3等(也支持Minix)。
    另外,在具体阅读代码之前,我们可能还需要对保护模式[1],内存管理[2],时钟与中断,进程调度和通信等很多方面都需要有基本地了解,建议结合原书和 网络中的资料加强这些方面的了解。

参考资料

[0] Monolithic kernel vs. Microkernel
http://www.vmars.tuwien.ac.at/courses/akti12/journal/04ss/article_04ss_Roch.pdf
[1] Protected Mode
http://www.x86.org/articles/pmbasics/tspec_a1_doc.htm
http://www.oldlinux.org/oldlinux/viewthread.php?tid=7297&extra=page%3D1
[2] Memory Management
http://mirror.lzu.edu.cn/os/oldlinux.org/Ref-docs/Linux011-Mem-YuanYi.pdf
[3] a.out
http://en.wikipedia.org/wiki/A.out
[4] MINIX file system
http://en.wikipedia.org/wiki/MINIX_file_system
[5] 跟我一起写Makefile
[6] 把vim打造成源代码阅读器
http://oss.lzu.edu.cn/blog/blog.php?/do_showone/tid_1544.html
[7] 动态符号链接的细节
http://oss.lzu.edu.cn/blog/blog.php?/do_showone/tid_1551.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《Linux 内核完全注释》阅读笔记 的相关文章

随机推荐