《Linux内核完全注释》学习笔记:2.7 Linux内核源代码的目录结构
由于Linux内核是一种单内核模式的系统,因此内核中所有程序几乎都有紧密的联系,它们之间的调用关系非常密切。所以在阅读一个源代码文件时往往需要参阅其他相关的文件。因此有必要在开始阅读内核源代码之前,先熟悉一下源代码文件的目录结构。
这里我们首先列出 Linux 内核完整的源代码目录,包括其中的子目录。然后逐一介绍各个目录中所含程序的主要功能,使得整个内核源代码的安排形式能在我们的头脑中建立起一个大概的框架,有利于下一章开始的源代码阅读工作。当我们使用 tar 命令将 linux-0.11.tar.gz 解开时,内核源代码文件被放到了 linux 目录中。其中的目录结构如图 2-15 所示。
图2-15 Linux内核源代码目录结构
该内核版本的源代码目录中含有 14 个子目录,总共包括 102 个代码文件。下面逐个对这些子目录中的内容进行描述。
linux/ 目录是源代码的主目录,在该主目录中除了包括所有的 14 个子目录以外,还含有唯一的一个 Makefile 文件。该文件是编译辅助工具软件 make 的参数配置文件。make 工具软件的主要用途是通过识别哪些文件已被修改过,从而自动地决定在一个含有多个源程序文件的程序系统中哪些文件需要被重新编译。因此,make 工具软件是程序项目的管理软件。
linux/ 目录下的这个 Makefile 文件还嵌套地调用了所有子目录中包含的 Makefile 文件,这样,当 linux/ 目录(包括子目录)下的任何文件被修改过时,make 都会对其进行重新编译。因此为了编译整个内核所有的源代码文件,只要在 linux 目录下运行一次make 命令即可。
1. 引导启动程序目录 boot
boot 目录中含有3个汇编语言文件,是内核源代码文件中最先被编译的程序。这3个程序完成的主要功能是当计算机加电时引导内核启动,将内核代码加载到内存中,并做一些进入32位保护运行方式前的系统初始化工作。
其中 bootsect.s 和 setup.s 程序需要使用 as86 软件来编译,使用的是 as86 的汇编语言格式(与微软的类似),而 head.s 需要用GNU as来编译,使用的是AT&T格式的汇编语言。
这两种汇编语言在下一章的代码注释里以及代码列表后面的说明中会有简单的介绍。
- bootsect.s 程序是磁盘引导块程序,编译后会驻留在磁盘的第一个扇区中(引导扇区,0磁道(柱面),0磁头,第1个扇区)。在PC加电ROM BIOS自检后,将被BIOS加载到内存0x7C00处执行。
- setup.s 程序主要用于读取机器的硬件配置参数,并把内核模块 system 移动到适当的内存位置处。
- head.s 程序会被编译链接在 system 模块的最前部分,主要进行硬件设备的探测设置和内存管理页面的初始设置工作。
2. 文件系统目录fs
fs 目录是文件系统实现程序的目录,共包含 17 个 C 语言程序。这些程序之间的主要引用关系见图2-16。图中每个方框代表一个文件,从上到下基本按引用关系放置。其中各文件名均略去了后缀.c,虚线框中的程序文件不属于文件系统,带箭头的线条表示引用关系,粗线条表示有相互引用关系。
图2-16 fs 目录中各程序中函数之间的引用关系
由图可看出,这些程序可以分成四个部分:
- 高速缓冲区管理
- 底层文件操作
- 文件数据访问
- 文件高层函数
在对本目录中文件进行注释时,我们也将分成这四个部分来描述。
对于文件系统,我们可以将它看成是内存高速缓冲区的扩展部分。所有对文件系统中数据的访问,都需要首先读取到高速缓冲区中。本目录中的程序主要用来管理高速缓冲区中缓冲块的使用分配和块设备上的文件系统。管理高速缓冲区的程序是 buffer.c,而其他程序则主要用于文件系统管理。
- 在 file_table.c 文件中,目前仅定义了一个文件句柄(描述符)结构数组。
- ioctl.c 文件将引用 kernel/chr_drv/tty.c 中的函数,实现字符设备的 I/O 控制功能。
- exec.c 程序主要包含一个执行程序函数
do_execve()
,它是所有exec()
函数簇中的主要函数。 - fcntl.c 程序用于实现文件 I/O 控制的系统调用函数。
- read_write.c 程序用于实现文件读/写和定位三个系统调用函数。
- stat.c 程序中实现了两个获取文件状态的系统调用函数。
- open.c 程序主要包含实现修改文件属性和创建与关闭文件的系统调用函数。
- char_dev.c 主要包含字符设备读写函数
rw_char()
。 - pipe.c 程序中包含管道读写函数和创建管道的系统调用。
- file_dev.c 程序中包含基于i节点和描述符结构的文件读写函数。
- namei.c 程序主要包括文件系统中目录名和文件名的操作函数和系统调用函数。
- block_dev.c 程序包含块数据读和写函数。
- inode.c 程序中包含针对文件系统 i 节点操作的函数。
- truncate.c 程序用于在删除文件时释放文件所占用的设备数据空间。
- bitmap.c 程序用于处理文件系统中 i 节点和逻辑数据块的位图。
- super.c 程序中包含对文件系统超级块的处理函数。
- buffer.c 程序主要用于对内存高速缓冲区进行处理。
虚框中的 ll_rw_block
是块设备的底层读函数,它并不在fs目录中,而是 kernel/blk_drv/ll_rw_block.c 中的块设备读写驱动函数。放在这里只是让我们清楚地看到,文件系统对于块设备中数据的读写,都需要通过高速缓冲区与块设备的驱动程序 ll_rw_block()
来进行,文件系统程序集本身并不直接与块设备的驱动程序打交道。
在对程序注释过程中,我们将另外给出这些文件中各个主要函数之间的调用层次关系。
3. 头文件主目录 include
头文件目录中总共有 32 个 .h 头文件。其中 include/ 主目录下有13个头文件,其他头文件则存放在 asm(4个)、linux(10个)和 sys(5个)三个子目录中。
- include/ 主目录下的头文件主要是供内核和用户程序使用;
- asm/ 子目录主要用于存放与计算机硬件体系结构密切相关的头文件;
- linux/ 子目录用于存放 Linux 内核专用的头文件;
- sys/ 子目录用于存放一些与文件状态、进程、系统数据类型等有关的头文件。
4. 内核初始化程序目录 init
该目录中仅包含一个文件 main.c,用于执行内核所有的初始化工作,然后移到用户模式创建新进程,并在控制台设备上运行 shell 程序。
- 程序首先根据机器内存的容量对缓冲区内存容量进行分配,
- 如果还设置了虚拟盘,则在缓冲区内存后面也为它留下空间。
- 之后就进行所有硬件的初始化工作,包括人工创建第一个任务(task 0),并设置中断允许标志。
- 在执行从内核态移到用户态之后,系统第一次调用创建进程函数
fork()
,创建出一个用于运行init()
的进程,在该子进程中,系统将进行控制台环境设置,并且再生成一个子进程用来运行 shell 程序。
顺序按描述实际上是可以再细分的。
5. 内核程序主目录 kernel
linux/kernel 目录中共包含12个代码文件和一个 Makefile 文件,另外还有3个子目录。由于这些文件中代码之间调用关系复杂,因此这里就不详细列出各文件之间的引用关系,但仍然可以进行大概分类,如图2-17所示。
图2-17 各文件的调用层次关系
- asm.s 程序用于处理系统硬件异常所引起的中断
- 对各硬件异常的实际处理程序则是在 traps.c 文件中,在各个中断处理过程中,将分别调用 traps.c 中相应的C语言处理函数。
- exit.c 程序主要包括用于处理进程终止的系统调用。包含:
- 进程释放、
- 会话(进程组)终止
- 程序退出处理函数
- 杀死进程
- 终止进程
- 挂起进程等系统调用函数。
- fork.c 程序给出了进程创建系统调用 sys_fork() 相关的两个C语言函数。
- mktime.c 程序包含一个内核使用的时间函数 mktime(),用于计算从1970年1月1日0时起到开机当日的时间,作为开机时间(秒)。仅在 init/main.c 中被调用一次。
- panic.c 程序包含一个显示内核出错信息并停机的函数 panic()。
- printk.c 包含一个内核专用信息显示函数 printk()。
- vsprintf.c 实现了现已归入标准库中的字符串格式化函数。
- sched.c 程序中包括有关调度的基本函数(sleep_on、wakeup、schedule 等)以及一些简单的系统调用函数。另外还有几个与定时相关的软盘操作函数。
- signal.c 程序中包括了有关信号处理的 4 个系统调用以及一个在对应的中断处理程序中处理信号的函数 do_signal()。
- sys.c 程序包括很多系统调用函数,其中有些还没有实现。
- system_call.s 程序实现了系统调用(int 0x80)的接口处理过程,实际的处理过程则包含在各系统调用相应的C语言处理函数中,这些处理函数分布在整个Linux内核代码中。
5.1 块设备驱动程序子目录 kernel/blk_drv
通常情况下,用户是通过文件系统来访问设备的,因此设备驱动程序为文件系统实现了调用接口。
在使用块设备时,由于其数据吞吐量大,为了能够高效地使用块设备上的数据,在用户进程与块设备之间使用了高速缓冲机制。在访问块设备上的数据时,系统首先以数据块的形式把块设备上的数据读入到高速缓冲区中,然后再提供给用户。blk_drv 子目录共包含4个 C 文件和1个头文件。头文件 blk.h 由于是块设备程序专用的,所以与C文件放在一起。这几个文件之间的大致关系见图2-18。
图2-18 blk_drv子目录中文件的层次关系
- blk.h 中定义了3个C程序中共用的块设备结构和数据块请求结构。
- hd.c 程序主要实现对硬盘数据块进行读/写的底层驱动函数
do_hd_request()
等; - floppy.c 程序主要实现了对软盘数据块的读/写驱动函数。
- ll_rw_blk.c程序实现了底层块设备数据读/写函数
ll_rw_block()
,内核中所有其他程序都是通过该函数对块设备进行数据读写操作。你将看到该函数在许多访问块设备数据的地方被调用,尤其是在高速缓冲区处理文件fs/buffer.c 中。
5.2 字符设备驱动程序子目录 kernel/chr_drv
字符设备程序子目录共含有4个C语言程序和2个汇编程序文件。这些文件实现了对串行端口rs-232、串行终端、键盘和控制台终端设备的驱动。图2-19显示了这些文件之间的大致调用层次关系。
图2-19 字符设备程序之间的关系示意图
- tty_io.c 程序中包含 tty 字符设备读函数
tty_read()
和写函数tty_write()
,为文件系统提供了上层访问接口。另外还包括在串行中断处理过程中调用的C函数do_tty_interrupt()
,该函数将会在中断类型为读字符的处理中被调用。 - console.c 文件主要包含控制台初始化程序和控制台写函数
con_write()
,用于被tty设备调用,还包含对显示器和键盘中断的初始化设置函数con_init()
。 - rs_io.s 汇编程序用于实现两个串行接口的中断处理程序。该中断处理程序会根据从中断标识寄存器(端口0x3fa或0x2fa)中取得的4种中断类型分别进行处理,并在处理中断类型为读字符的代码中调用
do_tty_interrupt()
。 - serial.c 用于对异步串行通信芯片UART进行初始化操作,并设置两个通信端口的中断向量。另外还包括tty用于向串口输出的
rs_write()
函数。 - keyboard.s 程序主要实现了键盘中断处理过程
keyboard_interrupt
。 - tty_ioctl.c 程序实现了tty的I/O控制接口函数
tty_ioctl()
以及对termio[s]终端I/O结构的读写函数,并会在实现系统调用sys_ioctl()
的 fs/ioctl.c 程序中被调用。
5.3 协处理器仿真和操作程序子目录 kernel/math
该子目录中目前仅有一个C程序 math_emulate.c。其中的 math_emulate()
函数是中断int7的中断处理程序调用的C函数。当机器中没有数学协处理器,而CPU执行了协处理器的指令时,就会引发该中断。因此,使用该中断就可以用软件来仿真协处理器的功能。本书所讨论的内核版本还没有包含有关协处理器的仿真代码。本程序只是打印一条出错信息,并向用户程序发送一个协处理器错误信号 SIGFP E
。
6. 内核库函数目录 lib
内核库函数用于为初始化程序 init/main.c 执行在用户态的进程提供调用支持。它与普通静态函数库的实现方法完全一样。在该目录中共有12个C语言文件,除了一个由 Tytso 编制的 malloc.c 程序较长以外,其他的程序都很短,有的只有一两行代码。这些文件主要包括:
- 退出函数
_exit()
- 关闭文件函数
close()
- 复制文件描述符函数
dup()
- 文件打开函数
open()
- 写文件函数
write()
- 执行程序函数
execve()
- 内存分配函数
malloc()
- 等待子进程状态函数
wait()
- 创建会话系统调用
setsid()
- 以及在 include/string.h 中实现的所有字符串操作函数。
7. 内存管理程序目录 mm
该目录包括2个代码文件。主要用于管理程序对主内存区的使用,实现了进程逻辑地址到线性地址以及线性地址到主内存区中物理内存地址的映射,通过内存的分页管理机制,在进程的虚拟内存页与主内存区的物理内存页之间建立了对应关系。
- page.s文件包括内存页面异常中断(int 14)处理程序,主要用于处理程序由于缺页而引起的页异常中断和访问非法地址而引起的页保护。
- memory.c程序包括对内存进行初始化的函数
mem_init()
,由 page.s 的内存处理中断过程调用的do_no_page()
和do_wp_page()
函数。在创建新进程而执行复制进程操作时,使用该文件中的内存处理函数来分配管理内存空间。
8. 编译内核工具程序目录tools
该目录下的 build.c 程序用于将 linux 各个目录中被分别编译生成的目标代码链接合并成一个可运行的内核映像文件 image。其具体的功能可参见第3章内容。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~