Linux内核源码分析 2:Linux内核版本号和源码目录结构


Linux内核源码目录结构

一、Linux的版本

1. 稳定版和开发版

Linux内核版本

Linux内核主要分为两种版本:

  • 稳定版(长期支持版):稳定版的内核具有工业级的强度,可以广泛地应用和部署。而每一代新推出的稳定版内核大部分都只是修正了一些Bug或是加入了一些新的设备驱动程序
  • 开发版:开发版的内核中内核开发者不断试验新的解决方案,所以开发版内核中代码变化得都很快

这两种版本Linux通过版本号来进行区分。

2. Linux的版本号

Linux版本号

Linux的版本号主要由三组数字组成xxx.yyy.zzz

  • xxx:主版本号(Major Release
  • yyy:次版本号(Minor Release
  • zzz:修订版本号(Revision Count

其中:

  • 次版本之间添加新的驱动、修复BUG,例如:3.21.x3.22.x
  • 当次版本累积到一定程度之后,就会发布新的主版本,例如:5.x.x6.x.x
  • 每个次版本在发布后人们在使用过程中可能会发现BUG,因此会发布该次版本的修订版本,例如5.6.135.6.14

Linux的稳定版和开发版,是通过次版本号来区分的:

  • 偶数次版本号表示该版本内核为稳定版
  • 奇数次版本号表示该版本内核为测试版

例如,Linux 3.6.34

  • 主版本号:3
  • 次版本号:6,稳定版
  • 修订版本号:34,表示当前版本是Linux 3.6的第34次修订版

不过,主版本号和次版本号在一起其实就已经可以描述一个版本的内核了,因为后续的修订版本都是修复BUG,不涉及到新功能的发布。

二、Linux内核源码组织结构

Linux内核源码庞大,2020年版本的内核源码就已经有2780万行了,这些代码分散在66492个C文件中。而Linux代码的源文件则按照一定的组织结构存放在不同的目录下,每个目录中的源代码的功能在逻辑上都有相同的点。

除了目录以外,还有一些文件,这些文件具有特殊的作用和目的。

Linux源码目录

1. 目录

Linux源码根目录下存放了一系列目录,每个目录中又有很多的C代码。根据这些C代码在逻辑上功能的划分,将他们放到相同的文件夹。

Linux内核源码目录下的目录

A. arch文件夹

arch目录下存放了和体系结构相关的代码,用于屏蔽不同的体系、平台之间的差异。例如:RISC-V架构的CPU打开虚拟地址的地址翻译功能需要读写satp寄存器,而x86CPU打开虚拟地址的地址翻译功能需要读写cr3寄存器。

arch文件夹下列出的Linux支持的所有的体系结构

Linux中打开虚拟地址翻译功能的函数是switch_mm,而又因为不同的体系结构打开虚拟地址翻译需要进行的操作不同,所以switch_mm函数就放在arch目录下。

Linux使用switch_mm打开分页机制,这一函数在RISC-V和x86下行为不同

事实上,除了switch_mm以外,还有很多类似的函数,例如中断处理函数……

B. block文件夹

block文件夹内存放了Linux内核的块设备驱动程序。块设备是指将信息存储在固定大小的块中,每个块都有自己的地址,而后CPU可以通过指定地址实现在设备的任意位置读取一定长度的数据。

块设备其实指的是一类设备,具体来说是I/O设备,即存储数据的设备,而非类似于CPU、显卡这样的计算设备,例如:硬盘U盘SD卡

注意,后面会讲到Linux内核源码根目录下有一个drivers目录,这个目录里面放着各种设备的驱动。按理来说块设备驱动应该也是驱动的一种,但是这里为什么把block单独拉出来,而不是放到drivers目录下面呢?

这个其实是因为,drivers目录下面其实放的各种设备的驱动,例如:

  • driver/cdrom文件夹下存放只读光盘的驱动
  • driver/usb文件夹下存放USB设备的驱动,例如U盘

而事实上只读光盘和USB都是块设备,因此在块的操作上,例如读取块、写入块都是一样的,只不过对具体的设备,读取块的操作不同。

因此,Linuxblock目录下是存放的是通用的块设备驱动程序,即写入块、读取块的函数,而具体各个设备读取块的函数则是在driver/设备目录下。所以Linux才把block目录放到根目录下。

实施上,除了block目录是这样,ipcnet目录也是一样的。只不过存放的是通用的ipc设备驱动和通用的网络驱动。

C. certs目录

certs目录下存放了Linux与认证和签名有关的代码。该目录中包含了一些预装的数字证书,这些证书可以用来验证签名的模块、内核代码和用户空间应用程序等等内容,这些证书可以用来确保这些组件是来自可信的源,从而提高系统的安全性。

之所以需要这个目录,其实是由于Linux内核从2.2版本之后就支持动态加载内核模块

内核模块

原先在编译的内核的时候,所有的驱动程序、文件系统或网络协议的代码都会编译进内核。最后就会导致内核中充满了各种设备的驱动程序,可能你的电脑的硬件系统上只有10多种设备,但是运行的内核里却有400多种设备的驱动程序。

正是因为如此,Linux2.2版本之后提出了内核模块的概念,即将文件系统、驱动程序等等从内核的二进制程序中独立出来,在编译阶段生成单独的以.so文件。未来需要哪个程序,那么就加载对应的.so文件即可。

例如假设我们现在是文件系统、存储相关的研究者,我们针对ext文件系统的缺点,进行了改进,提出了自己的文件系统Iron文件系统。我们想要测试我们的文件系统,那么这个时候我们就可以以内核模块的形式编写Iron文件系统的程序,Linux内核在运行的时候就可以加载我们编写的Iron文件系统的内核模块到内存

内核模块可以在系统运行时动态加载和卸载,这意味着它们不一定是在系统启动时就被加载,而是在需要时才被加载。这种动态加载的方式为系统的灵活性和可扩展性带来了很大的好处,但也为系统带来了一些潜在的安全风险。

例如,攻击者可以在实现正常内核模块功能的代码之上,增加一些恶意的代码,例如窃取敏感信息、拒绝服务攻击、提权等。最后将其伪装成合法的内核模块,将编译后得到的.so文件替换掉原先正常的内核模块。最后当用户加载了这些恶意的内核模块,这些恶意内核模块就开始破坏系统了。

正是因为如此,Linux需要对包括内核模块在内的多个组件进行验证,以防止开源的、经过检验的Linux内核的源代码被插入恶意代码,亦或者加载了恶意内核模块。

Linux内核源码根目录下的certs目录

D. crypto目录

crypto目录下存放了Linux内核常用的压缩和加密算法。

1. 内核中的加密算法

Linux内核在很多地方都需要使用加密算法,基本上Linux系统中实现各种安全功能,例如:加密文件系统、网络传输加密、数字签名、安全登录等等需要保存密码的地方,都需要进行加密。

因此,Linux内核中的crypto目录中就包含了常用的加密算法的实现,包括:AESDESSHA1SHA256等等。

此外,因为加密解密使用的非常频繁,因此Linux内核的crypto目录还提供了一些加速加密/解密的硬件的驱动程序,例如基于硬件的AES加速器等,通过这些驱动来调用这些硬件,可以提高Linux内核加密和解密的性能。

2. 内核的压缩算法

Linux内核的源码目前已经1.1G了,最终编译得到的二进制格式的内核只会更大。而在开机前,Linux内核是以文件形式存在在磁盘上的,想要运行还需要被加载到内存中。因此,为了减少启动时间和内存占用,Linux内核会对自己进行进行压缩,而后在需要的时候解压对应的代码。

因此,内核中的压缩算法的主要目的是为了减小内核映像的大小。如果不进行压缩,将会导致内核映像的加载和执行时间变长,同时会占用更多的内存空间。

通过使用压缩算法,可以将内核映像的大小减小到原来的一半甚至更小,从而提高启动速度和节约内存空间。此外,一些文件系统也使用压缩算法来减小存储空间占用,提高文件系统的性能。

当然,压缩和解压都是由开销的,虽然压缩算法可以减小内核映像的大小,但同时也会增加内核启动时的解压缩的时间。因此,Linux内核在选择具体的压缩算法时,会根据需要权衡压缩比和解压缩时间选择压缩算法,或者直接使用用户指定的压缩算法,以获得最佳的性能表现。

具体来说,crypto目录中Linux实现的各种压缩算法包括:LZOLZ4ZlibDeflate……

E. Documentation目录

Documentation目录是Linux内核的文档,主要描述了各种模块的功能、定义了一些规范

例如:

cat Documentation/riscv/boot-image-header.rst | less

Linux内核中关于RISC-V的文档

F. drivers目录

drivers目录存放了Linux内核的各种硬件的驱动程序。例如GPIO设备:

ls drivers/gpio | less

drivers目录下的GPIO设备

不同的CPU型号的GPIO的设置、初始化方法均不同,所以Linux就在drivers/gpio目录下存放了针对不同芯片的GPIO的代码。

G. fs目录

fs目录下存放了Linux的虚拟文件系统的代码和各种类型的文件系统实现的代码。

例如Windows中比较常见的ntfs文件系统的实现就在fs/ntfs目录下

ls fs/ntfs | less

fs目录下的ntfs的实现代码

H. include目录

include目录下存放了Linux内核源码依赖的绝大部分头文件。各个头文件中包含了内核的各种定义和声明,为内核的构建和开发提供了必要的支持。

I. init目录

init目录中存放了内核初始化的代码。

内核的整个运行过程,其实能够视为两个过程:

  • 按下电源键到内核启动,再到内核开始准备就绪,等待用户使用的这个过程,这一过程被称为初始化
  • 用户开始使用内核,内核正常工作直到关机,这一过程就是内核正常运行的过程

在初始化阶段,内核需要干很多的事情,例如:

  • 统计可用的物理内存、打开虚拟地址翻译功能、初始化内存管理模块……
  • 初始化线程管理模块、构建内核线程、创建init线程运行和用户交互的shell……
  • ……

而由于内核是由多个组件组成的,所以内核初始化阶段的代码实际上就是进入内核各个组件完成各个组件的初始化

J. ipc目录

ipc目录是进程间通信的实现,未来将会被编译成内核的进程间通信模块。Linux中各种常用的进程间通信的机制,例如:

  • 信号量
  • 共享内存
  • 匿名管道
  • ……

的实现代码都是在ipc这个目录下面的。

ls ipc

Linux内核源码的ipc目录

K. kernel目录

kernel目录是内核的核心代码,包含了:

  • 进程管理
  • 中断管理
  • 时钟
  • ……

这些是内核最核心的功能组成,因此都放在了kernel这个目录下面。

L. lib目录

lib目录包含了一些通用的库函数,例如:memsetstrlen……这些函数可以被内核的其他部分使用,从而简化内核的开发。注意,我们自己写的用户程序中#include <stdlib.h>#include<stdio.h>之后也能适应memset,但是这个memset和内核lib目录下的memset是不一样的。

我们使用的memsetC标准库中定义的函数,而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目录中包含了以下几类工具:

  1. 编译工具scripts目录中包含了一些编译内核的工具脚本,比如makegccld等等工具的脚本。这些工具脚本可以帮助开发人员编译内核源码,生成可执行的内核镜像文件。
  2. 调试工具scripts目录中包含了一些调试内核的脚本工具,比如gdbkgdbkdb等工具的脚本等。这些脚本可以帮助开发人员对内核进行调试,定位和解决内核中的各种问题。
  3. 分析工具scripts目录中包含了一些分析内核的脚本工具,比如perftrace-cmd等等。这些工具可以帮助开发人员对内核进行性能分析、跟踪和统计,从而找出内核中的性能瓶颈和优化点。
  4. 代码检查工具scripts目录中包含了一些代码检查的脚本工具,比如checkpatch.plsparse等等。这些脚本工具可以帮助开发人员检查内核源码中的代码风格、语法错误、内存泄漏等问题,提高代码质量和可维护性。

Q. security目录

Linux内核源码目录中的security目录提供了Linux内核安全机制的实现,例如:Access Contol List(即ACL),SELinux……

这个目录下的代码主要实现的都是安全相关的函数,因此这个目录提供了安全相关的模块和接口,这些模块和接口可以帮助开发人员增强Linux系统的安全性能。

具体来说,security目录中包含了以下几类模块和接口:

  1. 安全模块security目录中包含了一些安全模块,比如SELinuxAppArmor等等。这些安全模块可以帮助开发人员对系统中的各种资源进行访问控制和安全策略的管理,从而提高系统的安全性能。
  2. 安全接口security目录中还包含了一些安全接口,比如security_inode_permissionsecurity_file_permission等等。这些安全接口可以帮助开发人员对系统中的各种资源进行访问控制和权限管理,从而保护系统中的敏感数据和应用程序。
  3. 安全策略security目录中还包含了一些安全策略,比如capabilityposix_acl等等。这些安全策略可以帮助开发人员对系统中的各种资源进行访问控制和权限管理,从而保护系统中的敏感数据和应用程序。

R. sound目录

sound目录是Linux内核源码中的包含了与声音相关的驱动程序和模块的目录。和netblock目录类似,具体的某种型号的声卡设备的驱动是放在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或其他脚本语言编写的。具体来说:

  • 这些工具包括了一些用于系统调试和性能分析的程序,如perfftrace
  • 还包括了一些用于内核构建和编译的工具,如kconfigkbuild
  • 还包含了一些用于模拟和测试的程序,如ktestkvm等。

tools目录下的工具和实用程序对于内核开发和调试非常重要,通常一名资深的Linux内核开发人员,是需要熟悉这些工具的使用方法和实现原理。

T. usr目录

usr目录是用户打包盒压缩内核实现的源码。

U. virt目录

virt目录提供了Linux内核对虚拟化相关支持的代码实现。

例如,virt/kvm目录下包含了Linux内核中的KVM虚拟化模块的源代码,它可以让Linux内核作为一个虚拟机监控器(VMM)来运行虚机

W. LICENSE目录

Linux内核源码目录中的LICENSES目录包含了Linux内核源码中使用的各种许可证的文本,包括GPLLGPLBSDMIT等等。

这个目录的作用是为了让Linux内核源码的开发者能够方便地查看和了解每种许可证的具体内容和限制。

这个目录是为了保证Linux内核源码的开放性和透明度,让所有人都能够了解Linux内核源码的使用条件和限制,从而更好地遵守这些许可证和规定。

2. 文件

除了目录以外,Linux内核源码目录下还存放了几个文件,这些文件各有不同的用法。

Linux内核源码目录下的文件

A. COPYING文件

Linux内核源码目录中的COPYING文件是版权声明文件,即许可证,它规定了Linux内核源码的使用条件和限制。

Linux内核源码采用的许可证是GPLv2GPLv2开源许可声明任何人都可以自由地使用、复制、分发和修改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文件提供了有关内核的基本信息,帮助用户快速入门。


文章作者: Jack Wang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Jack Wang !
 上一篇
Linux内核源码分析 3:Linux线程原理以及相关系统调用 Linux内核源码分析 3:Linux线程原理以及相关系统调用
本文是Linux内核源码分析系列文章的第三篇,介绍了Linux内核中是如何实现进程和线程的
下一篇 
Linux内核源码分析 1:Linux内核体系架构和学习路线 Linux内核源码分析 1:Linux内核体系架构和学习路线
本文是Linux内核源码分析系列文章的第一篇,介绍了Linux内核的体系架构和学习路线
  目录