一、Linux的版本
1. 稳定版和开发版
Linux
内核主要分为两种版本:
- 稳定版(长期支持版):稳定版的内核具有工业级的强度,可以广泛地应用和部署。而每一代新推出的稳定版内核大部分都只是修正了一些Bug或是加入了一些新的设备驱动程序
- 开发版:开发版的内核中内核开发者不断试验新的解决方案,所以开发版内核中代码变化得都很快
这两种版本Linux
通过版本号来进行区分。
2. Linux的版本号
Linux
的版本号主要由三组数字组成xxx.yyy.zzz
:
xxx
:主版本号(Major Release
)yyy
:次版本号(Minor Release
)zzz
:修订版本号(Revision Count
)
其中:
- 次版本之间添加新的驱动、修复BUG,例如:
3.21.x
和3.22.x
- 当次版本累积到一定程度之后,就会发布新的主版本,例如:
5.x.x
和6.x.x
- 每个次版本在发布后人们在使用过程中可能会发现BUG,因此会发布该次版本的修订版本,例如
5.6.13
和5.6.14
而Linux
的稳定版和开发版,是通过次版本号来区分的:
- 偶数次版本号表示该版本内核为稳定版
- 奇数次版本号表示该版本内核为测试版
例如,Linux 3.6.34
:
主版本号
:3次版本号
:6,稳定版修订版本号
:34,表示当前版本是Linux 3.6
的第34次修订版
不过,主版本号和次版本号在一起其实就已经可以描述一个版本的内核了,因为后续的修订版本都是修复BUG,不涉及到新功能的发布。
二、Linux内核源码组织结构
Linux
内核源码庞大,2020年版本的内核源码就已经有2780万行了,这些代码分散在66492个C文件中。而Linux
代码的源文件则按照一定的组织结构存放在不同的目录下,每个目录中的源代码的功能在逻辑上都有相同的点。
除了目录以外,还有一些文件,这些文件具有特殊的作用和目的。
1. 目录
Linux
源码根目录下存放了一系列目录,每个目录中又有很多的C代码。根据这些C代码在逻辑上功能的划分,将他们放到相同的文件夹。
A. arch
文件夹
arch
目录下存放了和体系结构相关的代码,用于屏蔽不同的体系、平台之间的差异。例如:
RISC-V
架构的CPU
打开虚拟地址的地址翻译功能需要读写satp
寄存器和mstatus
寄存器x86
的CPU
打开虚拟地址的地址翻译功能需要读写cr3
寄存器和cr0
寄存器
在Linux
中打开虚拟地址翻译功能的函数是switch_mm
,而又因为不同的体系结构打开虚拟地址翻译需要进行的操作不同,所以switch_mm
函数就放在arch
目录下。
事实上,除了switch_mm
以外,还有很多类似的函数,例如中断处理函数……所以,Linux
内核中将依赖于各个体系的代码放到了arch
目录下
B. block
文件夹
block
文件夹内存放了Linux
内核的块设备驱动程序。块设备是指将信息存储在固定大小的块中,每个块都有自己的地址,而后CPU
可以通过指定地址实现在设备的任意位置读取一定长度的数据。
块设备其实指的是一类设备,具体来说是I/O
设备,即存储数据的设备,而非类似于CPU
、显卡这样的计算设备,例如:硬盘
,U盘
,SD卡
。
注意,后面会讲到Linux
内核源码根目录下有一个drivers
目录,这个目录里面放着各种设备的驱动。按理来说块设备驱动应该也是驱动的一种,但是这里为什么把block
单独拉出来,而不是放到drivers
目录下面呢?
这个其实是因为,drivers
目录下面其实放的各种设备的驱动,例如:
driver/cdrom
文件夹下存放只读光盘的驱动driver/usb
文件夹下存放USB
设备的驱动,例如U盘
而事实上只读光盘和USB
都是块设备,因此在块的操作上,例如读取块、写入块都是一样的,只不过对具体的设备,读取块的操作不同。
因此,Linux
的block
目录下是存放的是通用的块设备驱动程序,即写入块、读取块的函数,而具体各个设备读取块的函数则是在driver/设备
目录下。所以Linux
才把block
目录放到根目录下。
实施上,除了block
目录是这样,ipc
和net
目录也是一样的。只不过存放的是通用的ipc
设备驱动和通用的网络驱动。
C. certs
目录
certs
目录下存放了Linux
与认证和签名有关的代码。该目录中包含了一些预装的数字证书,这些证书可以用来验证签名的模块、内核代码和用户空间应用程序等等内容,这些证书可以用来确保这些组件是来自可信的源,从而提高系统的安全性。
之所以需要这个目录,其实是由于Linux
内核从2.2
版本之后就支持动态加载内核模块
。
内核模块
原先在编译的内核的时候,所有的驱动程序、文件系统或网络协议的代码都会编译进内核。最后就会导致内核中充满了各种设备的驱动程序,可能你的电脑的硬件系统上只有10多种设备,但是运行的内核里却有400多种设备的驱动程序。
正是因为如此,
Linux
在2.2
版本之后提出了内核模块
的概念,即将文件系统、驱动程序等等从内核的二进制程序中独立出来,在编译阶段生成单独的以.so
文件。未来需要哪个程序,那么就加载对应的.so
文件即可。例如假设我们现在是文件系统、存储相关的研究者,我们针对
ext
文件系统的缺点,进行了改进,提出了自己的文件系统Iron
文件系统。我们想要测试我们的文件系统,那么这个时候我们就可以以内核模块
的形式编写Iron
文件系统的程序,Linux
内核在运行的时候就可以加载我们编写的Iron
文件系统的内核模块
到内存
内核模块
可以在系统运行时动态加载和卸载,这意味着它们不一定是在系统启动时就被加载,而是在需要时才被加载。这种动态加载的方式为系统的灵活性和可扩展性带来了很大的好处,但也为系统带来了一些潜在的安全风险。
例如,攻击者可以在实现正常内核模块功能的代码之上,增加一些恶意的代码,例如窃取敏感信息、拒绝服务攻击、提权等。最后将其伪装成合法的内核模块,将编译后得到的.so
文件替换掉原先正常的内核模块
。最后当用户加载了这些恶意的内核模块
,这些恶意内核模块就开始破坏系统了。
正是因为如此,Linux
需要对包括内核模块
在内的多个组件进行验证,以防止开源的、经过检验的Linux
内核的源代码被插入恶意代码,亦或者加载了恶意内核模块。
D. crypto
目录
crypto
目录下存放了Linux
内核常用的压缩和加密算法。
1. 内核中的加密算法
Linux
内核在很多地方都需要使用加密算法,基本上Linux
系统中实现各种安全功能,例如:加密文件系统、网络传输加密、数字签名、安全登录等等需要保存密码的地方,都需要进行加密。
因此,Linux
内核中的crypto
目录中就包含了常用的加密算法的实现,包括:AES
、DES
、SHA1
、SHA256
等等。
此外,因为加密解密使用的非常频繁,因此Linux
内核的crypto
目录还提供了一些加速加密/解密的硬件的驱动程序,例如基于硬件的AES
加速器等,通过这些驱动来调用这些硬件,可以提高Linux
内核加密和解密的性能。
2. 内核的压缩算法
Linux
内核的源码目前已经1.1G了,最终编译得到的二进制格式的内核只会更大。而在开机前,Linux
内核是以文件形式存在在磁盘上的,想要运行还需要被加载到内存中。因此,为了减少启动时间和内存占用,Linux
内核会对自己进行进行压缩,而后在需要的时候解压对应的代码。
因此,内核中的压缩算法的主要目的是为了减小内核映像的大小。如果不进行压缩,将会导致内核映像的加载和执行时间变长,同时会占用更多的内存空间。
通过使用压缩算法,可以将内核映像的大小减小到原来的一半甚至更小,从而提高启动速度和节约内存空间。此外,一些文件系统也使用压缩算法来减小存储空间占用,提高文件系统的性能。
当然,压缩和解压都是由开销的,虽然压缩算法可以减小内核映像的大小,但同时也会增加内核启动时的解压缩的时间。因此,Linux
内核在选择具体的压缩算法时,会根据需要权衡压缩比和解压缩时间选择压缩算法,或者直接使用用户指定的压缩算法,以获得最佳的性能表现。
具体来说,crypto
目录中Linux
实现的各种压缩算法包括:LZO
、LZ4
、Zlib
、Deflate
……
E. Documentation
目录
Documentation
目录是Linux
内核的文档,主要描述了各种模块的功能、定义了一些规范
例如:
cat Documentation/riscv/boot-image-header.rst | less
F. drivers
目录
drivers
目录存放了Linux
内核的各种硬件的驱动程序。例如GPIO
设备:
ls drivers/gpio | less
不同的CPU
型号的GPIO
的设置、初始化方法均不同,所以Linux
就在drivers/gpio
目录下存放了针对不同芯片的GPIO
的代码。
G. fs
目录
fs
目录下存放了Linux
的虚拟文件系统的代码和各种类型的文件系统实现的代码。
例如Windows
中比较常见的ntfs
文件系统的实现就在fs/ntfs
目录下
ls fs/ntfs | less
H. include
目录
include
目录下存放了Linux
内核源码依赖的绝大部分头文件。各个头文件中包含了内核的各种定义和声明,为内核的构建和开发提供了必要的支持。
I. init
目录
init
目录中存放了内核初始化的代码。
内核的整个运行过程,其实能够视为两个过程:
- 按下电源键到内核启动,再到内核开始准备就绪,等待用户使用的这个过程,这一过程被称为
初始化
- 用户开始使用内核,内核正常工作直到关机,这一过程就是内核正常运行的过程
在初始化阶段,内核需要干很多的事情,例如:
- 统计可用的物理内存、打开虚拟地址翻译功能、初始化内存管理模块……
- 初始化线程管理模块、构建内核线程、创建init线程运行和用户交互的shell……
- ……
而由于内核是由多个组件组成的,所以内核初始化阶段的代码实际上就是进入内核各个组件完成各个组件的初始化。
J. ipc
目录
ipc
目录是进程间通信的实现,未来将会被编译成内核的进程间通信模块。Linux
中各种常用的进程间通信的机制,例如:
- 信号量
- 共享内存
- 匿名管道
- ……
的实现代码都是在ipc
这个目录下面的。
ls ipc
K. kernel
目录
kernel
目录是内核的核心代码,包含了:
- 进程管理
- 中断管理
- 时钟
- ……
这些是内核最核心的功能组成,因此都放在了kernel
这个目录下面。
L. lib
目录
lib
目录包含了一些通用的库函数,例如:memset
、strlen
……这些函数可以被内核的其他部分使用,从而简化内核的开发。注意,我们自己写的用户程序中#include <stdlib.h>
和#include<stdio.h>
之后也能适应memset
,但是这个memset
和内核lib
目录下的memset
是不一样的。
我们使用的memset
是C标准库
中定义的函数,而C标准库
实际上是需要操作系统的支持的,因此我们在编写内核的源代码的时候实际上是没有C标准库
给我们用的,我们得自己手动实现一些C标准库
的函数。
因此,从这个角度来理解,内核源码中的lib
目录其实就是C标准库
实现的一个子集。
此外,lib
目录中还有一些常见的数据结构和算法的实现,如链表、哈希表、红黑树、位图等。
M. mm
目录
mm
目录是Linux
内核内存管理相关的实现,包括:
- 物理内存管理
- 页面分配算法
- 缺页中断、换页算法
- ……
N. net
目录
net
目录和block
目录类似,因为有很多种网络设备,例如:无线网卡(即WiFi
)、以太网(即有线),还有4G……而这些具体的网络设备的驱动代码是放在drivers
目录下的。
不管设备有多少种,而网络协议栈其实都是一样的,例如用无线网卡和以太网收发数据包,尽管数据包收发的设备不同,但是都是遵循IPv4网络协议的。因此Linux
内核将网络协议实现的代码放在了net
目录下。
net
目录中实现的网络协议包括:
TCP
IPv6
DNS
- ……
O. samples
目录
samples
目录中存放了一些Linux
内核的示例代码和程序,这些代码和程序可以帮助新入门的内核工程师更好地了解Linux
内核的工作原理和实现细节。
samples
目录中的示例代码和程序可以帮助开发人员学习如何使用Linux
内核实现的函数,比如网络协议栈相关的函数、文件系统、驱动程序、调度器等等功能模块中相关的函数。
同时,这些示例代码和应用程序还可以作为开发人员开发自己的Linux
内核模块和应用程序的参考和范例。
最后,在samples
目录中有一些程序作为测试用例,可以用于测试Linux
内核的各种功能和接口的正确性和性能。
基本上我们在一开始学习Linux
内核开发的时候,或者Hacking Linux Kernel
的时候,为了避免直接修改其他目录下的代码来插入我们自己的代码会导致Linux
无法运行、难以调试,一般把代码放在samples
目录下去测试。等我们以后功力精进了,再去修改其他目录下的代码。
P. scripts
目录
Linux
内核源码目录中的scripts
目录包含了一些脚本工具,这些工具可以帮助内核开发者进行内核编译、调试、分析和优化等工作。
具体来说,scripts
目录中包含了以下几类工具:
- 编译工具:
scripts
目录中包含了一些编译内核的工具脚本,比如make
、gcc
、ld
等等工具的脚本。这些工具脚本可以帮助开发人员编译内核源码,生成可执行的内核镜像文件。 - 调试工具:
scripts
目录中包含了一些调试内核的脚本工具,比如gdb
、kgdb
、kdb
等工具的脚本等。这些脚本可以帮助开发人员对内核进行调试,定位和解决内核中的各种问题。 - 分析工具:
scripts
目录中包含了一些分析内核的脚本工具,比如perf
、trace-cmd
等等。这些工具可以帮助开发人员对内核进行性能分析、跟踪和统计,从而找出内核中的性能瓶颈和优化点。 - 代码检查工具:
scripts
目录中包含了一些代码检查的脚本工具,比如checkpatch.pl
、sparse
等等。这些脚本工具可以帮助开发人员检查内核源码中的代码风格、语法错误、内存泄漏等问题,提高代码质量和可维护性。
Q. security
目录
Linux
内核源码目录中的security
目录提供了Linux
内核安全机制的实现,例如:Access Contol List
(即ACL
),SELinux
……
这个目录下的代码主要实现的都是安全相关的函数,因此这个目录提供了安全相关的模块和接口,这些模块和接口可以帮助开发人员增强Linux
系统的安全性能。
具体来说,security
目录中包含了以下几类模块和接口:
- 安全模块:
security
目录中包含了一些安全模块,比如SELinux
、AppArmor
等等。这些安全模块可以帮助开发人员对系统中的各种资源进行访问控制和安全策略的管理,从而提高系统的安全性能。 - 安全接口:
security
目录中还包含了一些安全接口,比如security_inode_permission
、security_file_permission
等等。这些安全接口可以帮助开发人员对系统中的各种资源进行访问控制和权限管理,从而保护系统中的敏感数据和应用程序。 - 安全策略:
security
目录中还包含了一些安全策略,比如capability
、posix_acl
等等。这些安全策略可以帮助开发人员对系统中的各种资源进行访问控制和权限管理,从而保护系统中的敏感数据和应用程序。
R. sound
目录
sound
目录是Linux
内核源码中的包含了与声音相关的驱动程序和模块的目录。和net
、block
目录类似,具体的某种型号的声卡设备的驱动是放在drivers
目录下的,sound
目录中的代码关注的是通用的声音的播放、录制和处理的功能。
因此,sound
目录通过调用drivers
目录下的不同型号声卡的驱动程序,从而实现了让Linux
内核可以支持使用多种声卡设备播放、录制和处理声音,并为用户程序提供了一系列的接口和功能。
具体来说,sound
目录主要实现的,或者说Linux
目前处理声音的模块的架构是ALSA
,它是Advanced Linux Sound Architecture
的缩写,是Linux
系统中用于声音处理的一种高级架构。alsa
规范中定义了一系列的驱动程序和库,使得Linux
系统能够支持多种声卡设备,并提供了一系列的接口和功能,使得应用程序能够方便地进行声音的录制、播放和处理等操作。
除了alsa
之外,sound
目录中还包含了一些其他的声音驱动程序和模块,如oss
子目录,它是Open Sound System
的缩写,是一种旧的Linux
声音架构,目前已经逐渐被alsa
所取代。
此外,sound
目录中还包含了一些其他的声音驱动程序和模块,如USB声卡驱动程序、蓝牙耳机驱动程序等。这些驱动程序和模块可以支持多种声卡设备,并提供了一系列的接口和功能,使得Linux系统能够播放、录制和处理声音。
S. tools
目录
在Linux内核源码目录中,tools
目录包含了一些工具和实用程序的源代码,这些工具通常用于内核开发和调试。其中一些工具是用C语言编写的,而另一些工具则是用Python
或其他脚本语言编写的。具体来说:
- 这些工具包括了一些用于系统调试和性能分析的程序,如
perf
和ftrace
等 - 还包括了一些用于内核构建和编译的工具,如
kconfig
和kbuild
等 - 还包含了一些用于模拟和测试的程序,如
ktest
和kvm
等。
tools
目录下的工具和实用程序对于内核开发和调试非常重要,通常一名资深的Linux
内核开发人员,是需要熟悉这些工具的使用方法和实现原理。
T. usr
目录
usr
目录是用户打包盒压缩内核实现的源码。
U. virt
目录
virt
目录提供了Linux
内核对虚拟化相关支持的代码实现。
例如,virt/kvm
目录下包含了Linux
内核中的KVM
虚拟化模块的源代码,它可以让Linux内核作为一个虚拟机监控器(VMM
)来运行虚机
W. LICENSE
目录
Linux
内核源码目录中的LICENSES
目录包含了Linux
内核源码中使用的各种许可证的文本,包括GPL
、LGPL
、BSD
、MIT
等等。
这个目录的作用是为了让Linux
内核源码的开发者能够方便地查看和了解每种许可证的具体内容和限制。
这个目录是为了保证Linux
内核源码的开放性和透明度,让所有人都能够了解Linux
内核源码的使用条件和限制,从而更好地遵守这些许可证和规定。
2. 文件
除了目录以外,Linux
内核源码目录下还存放了几个文件,这些文件各有不同的用法。
A. COPYING
文件
Linux
内核源码目录中的COPYING
文件是版权声明文件,即许可证,它规定了Linux
内核源码的使用条件和限制。
Linux
内核源码采用的许可证是GPLv2
。GPLv2
开源许可声明任何人都可以自由地使用、复制、分发和修改Linux
内核源码,但是得到的成果也使用GPL
许可证开源出来。
需要注意的是,Linux
内核绝大部分的源码采用的都是GPLv2
,但是有一些例外,例如有一些系统调用,对应的授权声明在 LICENSES/exceptions/Linux-syscall-note
中
B. CREDIT
文件
Linux
内核源码目录中的CREDIT
文件记录了所有为Linux
内核做出贡献的人员名单,包括:
- 内核开发者
- 维护者
- 测试人员
- ……
CREDIT
文件的作用是为了表彰和感谢所有为Linux
内核做出贡献的人员,记录他们的贡献和成就。
同时,CREDIT
文件也是Linux
社区文化的一部分,它强调了Linux
内核开发的开放性、合作性和社区精神,让所有人都能够参与到Linux
内核的开发和维护中来。
C. Kbuild
文件
Linux
内核源码目录中的Kbuild
文件是用于构建Linux
内核的Makefile
。注意,Kbuild
文件主要定义了构建Linux
内核的Makefile
的配置。在编译内核时候运行的命令主要定义在Makefile
文件中
Kbuild
文件的作用是自动化构建Linux
内核的过程,使得开发者能够方便地编译、构建和安装Linux
内核。
同时,Kbuild
文件也提供了一些高级的编译功能,如:
- 支持模块化编译
- 交叉编译
- 并行编译
- ……
这些功能使得Linux
内核的构建更加灵活和高效。
D. MAINTAINERS
文件
Linux
内核源码目录中的MAINTAINERS
文件保存了当前所有内核的维护者名单。它记录了Linux
内核中各个子系统的维护者和贡献者信息。
MAINTAINERS
文件的作用是帮助开发者快速找到负责某个子系统的维护者或贡献者,以便于进行代码提交、修复或协作开发等工作。
MAINTAINERS
文件中列出了Linux
内核中各个子系统的维护者和贡献者的姓名、电子邮件地址、所在公司、负责的子系统等信息。开发者可以通过该文件查找到负责自己所关注的子系统的维护者或贡献者,并向其提交代码或报告问题。
除了作为开发者的参考之外,MAINTAINERS
文件还可以作为Linux
内核社区的组织和管理工具。通过该文件,Linux
内核社区可以对各个子系统的维护者和贡献者进行管理和协调,以保证Linux
内核的良好发展和稳定性。
E. Makefile
文件
Linux
内核源码目录中的Makefile
文件是用于编译内核的。它包含了在编译内核时候该执行的一系列的命令,包括:
- 用于生成内核镜像的命令
- 生成内核模块的命令
- ……
Makefile
文件会读取Kbuild
文件的中的配置信息来生成编译命令,从而编译内核源代码。
除了编译内核镜像和模块之外,Makefile
文件中还定义了其他的一些操作,例如:
- 安装内核
- 打包内核
- 清除编译生成的文件
- ……
Makefile
文件根据调用make
命令时候指定不同的目标来执行不同的操作,例如make all
用于编译内核和模块,make install
用于安装内核,make clean
用于清除编译生成的文件等等。
F. README
文件
Linux
内核源码目录中的README
文件提供了有关该特定版本的内核的一些基本信息,如:
- 内核的版本号
- 支持的硬件平台
- 安装和配置指南
- ……
此外,README
文件还可能包含其他有用的信息,如:
- 已知的问题、限制
- BUG修复
通常,README
文件提供了有关内核的基本信息,帮助用户快速入门。