超过百万次提交-Linux内核何以发展至今
2020-08-31 17:55:53 阿炯

本文转自xplanet,感谢原作者。

1991 年,21 岁的芬兰大学生 Linus Torvalds 写下第一行 Linux 内核代码时,多半没有想到它会成长为今天这样的庞然大物。当年 8 月 25 日,Torvalds 在 Minix Usenet 新闻组里发了一封帖子,称自己正在做一个自由的操作系统,“就是个兴趣爱好,不会搞得像 GNU 那么大那么专业”。

事情的发展显然远超他的预期,如今小到传感器,大到超级计算机,从智能手机、手表、汽车等日常用品到航天器等设备,Linux 内核的身影已无处不在。随着迄今为止最大版本 Linux Kernel 5.8 于 2020 年 8 月初的发布,Linux 内核已拥有超过 2 万名贡献者,历史 commit 数超过 100 万次,迎来一个新的里程碑。

恰逢 Linux 29 周年纪念日,Linux 基金会发布了一份详尽的 2020 Linux 内核报告,涵盖了自 1991 年 9 月 17 日首次发布以来,到 2020 年 8 月 2 日最新版本的 Linux 内核历史。

版本控制:从 BitKeeper 到 Git

Linux 基金会从 2008 年开始每年发布 Linux 内核报告,但此前一直难以将其发展过程完整串联起来。今年,借助 Daniel German 博士的 cregit 工具,他们成功追溯到了第一个版本,Linux 内核的发展时间线变得清晰。根据版本控制方式,可大致分为以下三个阶段:
pre-version control(前版本控制):1991 年 9 月 - 2002 年 2 月 4 日
BitKeeper:2002 年 2 月 4 日 - 2005 年 4 月 15 日
Git:2005 年 4 月 16 日至今


版本控制对社区协作能力影响重大,这在 2000 年代初是一件令人颇为苦恼的事情。使用版本控制系统,也就是 BitKeeper 之前,贡献者需要将补丁提交至邮件列表,待 Torvalds 接受后放进源码树,再发布整个树的新版本。在这种方式下,具体是谁在做贡献、贡献的数量和路径都不够透明。

2002 年 2 月 4 日,BitKeeper 的使用标志着 Linux 内核 commit 历史的开启。然而 BitKeeper 为专有软件,这一决定在社区中遭受了长期质疑。直到 2005 年,BitKeeper 拥有者 Larry McVoy 决定收回无偿使用 BitKeeper 的许可。Torvalds 本人当时又对现成的 CVS 和 Subversion 等集中式版本控制工具感到不满,因此自己动手,用十天时间写出了 Git 的第一个版本。

Git 目前已成为开发者们非常熟知且广泛使用的分布式版本控制系统。Linus Torvalds 则表现得更多的是迫于无奈,他曾声称自己“根本不想做源代码管理,觉得这是计算机世界中最无趣的事情”。自从版本控制系统由 BitKeeper 改为 Git 后,Linux 内核每年的贡献者和 commit 数量都在稳步增长。历年的内核报告数据显示,2005 年 5 月发布的 2.6.12 版本平均每小时收到 2 次 commit。15 年后,2019 年的平均数是每小时 9.4 次。而在最新的 5.8 内核中,平均每小时 commit 数达到了 10.7 次。

繁杂且自成体系的内核代码

首个内核版本 linux-0.01.tar.Z 由 88 个文件和 10,239 行代码组成,运行在 i386 这样单一的硬件架构上。到了 5.8 版本,这一数据扩张为 69,325 个文件和 28,442,673 行代码,并能够在 30 多种主要的架构上运行。

数量上的庞大仅是 Linux 内核代码的表象,它自身更是形成了一套复杂的体系,不熟悉的人往往不知从何处下手,极有可能“牵一发而动全身”。这或许也是内核维护者难寻的原因之一。

Linux 内核发布第一天起的某些代码仍在当前版本中使用,例如 Torvalds 和大学好友 Lars Wirzenius 共同编写的 vsprintf 例程,它也是为数不多存在至今的首次 commit 中的源代码。

今年的内核报告提到,有 2,964 个能被追溯至 1991 年的 token 如今在 5.8 版本中也能找到。5.8 版本超过一半的代码写于近 7 年内,但之前所有年份都对此版本有贡献。过去的代码不断在后续的版本中留下痕迹。

持续膨胀的内核文件量和代码行数未见得完全是一件好事,为了使系统不变得臃肿,内核维护者需要做一些修枝剪叶的工作。Linux 内核中未使用的代码和文件都会被视情况删除,有一些版本还会进行大的清理,例如 2018 年的 4.17 版本,删除了 8 个架构,净减少代码大约 180,000 行

不仅仅是源码,Linux 内核维护者还会关注空白行和代码注释,以确保源码的可读性。


开发者原创证书和标签管理

2004 年对开发者原创证书(Developer Certificate of Origin,简称 DCO)的标准化是 Linux 内核史上的一个关键变化点。DCO 的引入为开发者和用户提供了法律保护,同时又不至于增加程序负担。它极大地提高了跟踪补丁进入内核的路径的能力,加上版本控制系统向 Git 的过渡,DCO 有效地减轻了开发者做贡献的开销,因此变得很受欢迎,后来也被许多其他开源项目采用。

随着 DCO 的标准化使用,现在几乎所有的 commit 都有一个 Signed-off-by 标签。通常每个 commit 平均会有两个该标签,能够反映代码在合并之前的维护者层次结构,有助于追溯补丁进入代码的路径。


Signed-off-by 之外,Linux 内核还增加了表示审查的标签(Reviewed-by 和 Acked-by)。审查对内核代码的质量来说相当重要,标签的加入令这个过程更加清晰,有越来越多的维护者选择使用标签来表示已审查。

在审查 git 仓库时,Linux 基金会发现了一些比较有趣的标签,像是 "Enithusiastically-ack'd by"、"Thanksto"、"Based-on-the-Original-screenplay-by"、"Catched-by-andrightfully-ranted-at-by" 等等。但这些标签并没有像上述两类一样被广泛采用。


行之有效的发布模式

Linux 内核的发布模式已渐趋成熟,现在基本固定为 Prepatch(或"-rc")、Mainline、Stable 和 Long Term Stable 四类版本

社区曾对发布周期进行了大量的探讨和实验,并逐渐找到了行之有效的发布模式,发布周期也几乎完全可预测——每个发布周期由时长两周的“合并窗口”开始,这时,新功能经适当的 review 后可被纳入接下来要发布的 git 仓库。一旦它被标记为 rc1,那么集成测试、调试和稳定化的周期就开始了。然后每周对 rc 候选版本进行标记,直到达到目标质量和稳定性。发布后,随着下一个合并窗口的到来,这个周期又开始循环。

内核的主线树由 Linus Torvalds 维护,这棵树引入了所有新功能。新的主线(Mainline)内核每 2 到 3 个月发布一次。但这样的发布节奏较慢,难以满足大多数用户的需求。因此,从 2005 年开始,每周发行一次稳定版(Stable)内核。

用户还想要受维护时间更长的版本,于是 2006 年发布的 2.6.16 版本成为第一个长期支持(LTS)版本内核。此后每年都有一个新的 LTS 内核,该内核将由内核社区维护至少 2 年(从 4.4 版本开始延长至 6 年)。Linux 内核官网公布了所有现存 LTS 版本的发行日期、EOL 日期及维护者(目前 6 个 LTS 版本都由 Greg Kroah-Hartman 和 Sasha Levin 这两人维护)。



贡献者:长尾的力量不可忽视

不少组织都在为 Linux 内核做贡献,贡献者排行榜前列几乎都被 Intel、Red Hat、IBM、SUSE、Google、Samsung、AMD、Oracle、华为和 ARM 这样的大企业占据。从 2007 年到 2019 年,Linux 内核共接受了来自 1,730 个组织的 780,048 次 commit。排在最前面的 20 个组织占了 68% 的 commit 量。


在过去十年中,每年有超过 400 个组织为 Linux 内核做出贡献。其中相当一部分可能只有过一次 commit。从每年的 commit 比例来看,其中 1/3 贡献来自神秘的长尾。也就是图中最上面浅灰色 “Others” 的部分。

Linux 基金会指出,企业的贡献会根据业务需求和战略的不同而有所变化。前 20 名贡献者中,有些是 2007 年之后才加入,有些在此前做过很多贡献的公司,被收购后便不再继续参与。贡献者的多样性为内核发展赋予了一些弹性。

除了组织贡献者,Linux 内核社区成员也致力于增加个人贡献者的多样性,他们通常愿意花费自己的时间来指导新的开发者。Linux 基金会有一个 Kernel Mentorship(LKMP)项目,用来帮助新加入开源的开发者进行实验、学习,并为开源社区做出贡献。

内核社区的共同目标:高质量、可靠性

报告的最后,Linux 基金会指出,内核社区的重点是保持一个共同的目标,即拥有一个没有回归的高质量操作系统,愿意根据需要创建新的流程和工具,以帮助提高效率,并继续提升 Linux 内核的可靠性。

内核测试现在也引入了一些自动化测试工具:静态分析工具如 sparse(语义解析器)、smatch(源匹配器)和cocicheck(语义补丁,测试特定的 bug),由 0-day 和 Hulk Robot 这样的自动测试机器人在 Linux 内核树上运行。机器人在发现和跟踪 bug 上起到不少作用。这些测试工具能够帮助开发人员跟上上游内核的速率变化,并继续改进内核版本,提升其稳定性。

Linux 内核如今被应用于诸多领域,基金会认为,改进基础设施,进行正确的安全分析,是接下来要应对的重大挑战之一。目前 Linux 内核已拥有一个很好的基础,它应当继续引领创造最佳实践,以促进整个开源软件行业的发展。Linux 以其开源、稳定、高效的特性,占据着举足轻重的地位。无论是在服务器领域,还是在嵌入式系统中,都能看到它的身影。然而,对于许多开发者和技术爱好者来说,Linux 就像一座神秘的宝藏库,们日常使用它的各种功能,却很少深入探究其内部的奥秘。Linux 源码这本厚重的“宝典”中的那些隐藏在代码深处有着不为人知的系统奥秘,了解这个强大的操作系统究竟是如何在底层运作,创造出如此稳定且高效的运行环境的。下面从开发者的角度解析了6个内核底层原理开发技术。

一、进程管理专栏

(1)进程管理基础部分
Linux内核源码组织结构
Linux内核5个子系统关系
Linux内核源码目录结构详解
如何快速掌握阅读内核源码方法与技巧
进程原理及系统调用详解
进程/进程生命周期
task_ struct数据结构
进程优先级/系统调用
调度器及CFS调度器
实时调度类/SMP/NUMA
进程优先级与调度策略案例分析
RCU机制及内存优化屏障
内存布局和堆管理
多核调度分析
内核数据结构:链表和红黑树案例分析

(2)进程管理案例分析
编译自己Linux内核实战分析
进程管理4大常用API案例分析
系统调用API1
kthread _create_on_node案例分析
wake_up_process案例分析
系统调用API2
获取进程NICE值案例分析
设置进程NICE值案例分析
系统调用API3
complete_all案例分析
wake_up_sync_key案例分析
RCU案例实战分析
模拟cfs调度器案例分析
进程间通信案例分析
SMP调度详解

二、内存管理专栏

(1)内存管理基础部分
拟地址空间布局架构
内存管理架构分析
虚拟地址空间布局
内存映射原理机制
物理内存组织结构
统调用sys mmap/sys_ munmap
内存模块及三级结构
引导内存分配器原理
bootmem分配器原理
memblock分配器原理
伙伴分配器
伙伴分配器原理
分配页及释放页
Slab分配器详解
不连续页分配器及页表
胪空间缺顽异常
页表缓存(TLB)与巨型页
TLB表项格式及管理
ASID原理/NMID原理
处理器对巨型页的支持
标准巨型页原理及查看
巨型页池
页回收机制详解
发起页回收
回收不活动页
页交换
计算描页数
收缩活动页链表
回收slab缓存
内存反碎片技术
虚拟可移动区域技术原理
内存碎片整理算法
Linux内核内存池案例分析
内存检测与死锁检测

(2)内存管理案例分析
伙伴系统算法案例分析
设计per-cpu变量案例分析
缺页异常分析
写时复制缺页异常
文件映射缺页中断
匿名页面缺页异常
do_ page_fault函数分析
内核调优参数
/proc/sys/kernel/
/proc/sys/vm/
/proc/sy/fs/
水位调优参数min_ free_kbytes
面分配参数lowmem_reserve _ratio
内存管理meminfo&zoneinfo信息分析
分配物理页实战分析
进程虚拟区间实战分析
vmalloc案例实战分析
kmalloc案例实战分析
kzalloc&kcallolc案例实战分析
创建slab缓存案例实战分析
创建内存池案例实战分析
slab分配器案例实战分析
内存映射案例实战分析
统计进程虚拟区间页数案例分析
缓存着色
处理器缓存机制
文件页缓存
内存与Kasan_ I分析
五大常见内存访问错误
Kasan内核检测工应用

三、设备驱动专栏

(1)设备驱动基础部分
I/O体系结构
系统总线(PCI、ISA、 SCSI、 USB等)
外设交互及总线控制设备
访问设备深度详解
内核块设备详解
块设备I/O操作及源码分析
通用磁盘及磁盘分区源码分析
文件系统关联及字符设备操作
inode/字符设备及块设备操作
cdev数据结构及读写操作
资源分配及总线系统
资源分配I/O内存I/O端口
device数据结构/PCI总线/USB总线
内核插入模块及删除模块
内核模块基础知识
module_ init/module_exit
insmod/mmod/dmesg
PCI设备驱动讲解
PCI基础及拓扑关系
pci host _bridge/pci _bus/pci. dev
PC驱动讲解与实现
USB设备驱动详解
总线速度及主机控制器
传输模式与寻址方法
USB驱动总线数据结构分析
字符设备操作
主设备与次设备
开设备文件
分配与注册字符设备
文件操作实现
open/release方法
read/wite方法
lslek/poll防法
填充file operations结构体

(2)设备驱动案例分析
USB设备驱动架构分析
USB系统架构
传输模式与寻址方法
USB驱动总线数据结构分析
USB驱动分析
USB基础及层次详解
USB驱动常见数据结构
以太网驱动分析
网络设备驱动框架
sk buff/net_device/napi_struct
USB3.0设备控制器驱动分析
USB控制器分析
dwc3_ event/dwc3 _event_buffer
输入设备驱动程序
输入设备驱动程序基础及分析
核心数据结构input_dev
常用内核编程API接口
V4|2视频设备驱动框架
V4L2视频设备驱动
V4L2驱动数据结构分析
V4I2框架常用API详解
字符设备驱动项目实战分析
字符设备驱动通信案例分析
字体设备驱动数据结构设计
数据发送端设计
数据读取端设计
Makefile文件设计
make及字符设备驱动测试
删除字符设备驱动模块
杂项(MISC)驱动实例分析

四、文件系统专栏

(1)文件系统基础部分
通用文件模型
磁盘文件系统(DF)
虚拟文件系统(VF)
网络文件系统(NF)
链接
API编程接口
VFS数据结构
超级块(super. _block)
挂载描述符(mount结构体)
索|结点(inode结构体)
录项缓存(dentry结构体)
处理VFS对象及标准函数
Ext2文件系统
Ext2物理结构
Ext2数据结构分析
Ext2文件系统操作
Ext4_ 日志JBD2
Ext4文件系统特性
Ext4文件系统数据结构
Ext4_日志JBD2
proc文件系统
proc数据结构
初始化及装载proc
管理proc数据项
数据读写实现
系统控制机制
简单文件系统
顺序文件
使用libfs编写FS
调试文件系统
文件系统API : vfs fstat/fget/get. max_ files
挂载文件系统
系统调用mount处理流程
绑定挂载/挂载命名空间
挂载/注册rootfs文件系统
文件系统调用
打开/关闭文件
创建/删除文件
读/写文件实现
文件回写技术原理/接口实现
sysfs文件系统
sysfs数据结构
装载文件系统
文件和目录操作
向sysfs添加数据内容

(2)文件系统案例分析
proc文件系统案例实战分析
debugfs案例实战分析
super_block案例分析

五、网络协议栈专栏

(1)网络协议栈基础部分
套接字及分层模型
套接字通信基础
网络分层模型
接字缓冲区及net _device
sk buff数据结构分析
net _device数据结构分析
从套接字缓冲区获取TCP首部
内核邻接子系统
邻接子系统基础
ARP协议(IPv4)
内核Netlink套接字案例分析
数据结构设计与API系统调用
内核模块与用户应用程序设计
网络接口及IP地址实现
网络层分析
接收分组原理分析
分组转发原理与分析
发送分组原理与分析
TCP/UDP协议栈分析
套接字分析
发送与接收UDP数据包分析
发送与接收TCP数据包分析
传输层分析
流控制传输协议(SCTP)
数据报拥塞控制协议(DCCP)
ICMP协议分析
发送/接收ICMPv4消息
发送/接收ICMPv6消息
NIC数据包接收与发送分析
NIC原理机制
NC数据包接收与发送流程分析
IPsec(互联网安全协议)
IPsec基础知识部分
XFRM框架/策略/状态
传输模式/隧道模式/IPsec高可靠性
Netfilter框架分析
Netfilter架构分析
Netfilter挂接点
Netfilter连接跟踪
epoll分析
poll系统调用
epoll数据结构
epoll内核实现
IPv4路由选择子系统
FIB表
ICMPv4重定向消息
高级路由选择
组播路由选择
策略路由选择
IPv6协议分析
IPv6协议基础架构
接收IPv6数据包
发送IPv6数据包
InfiniBand架构
InfiniBand组件与编址
InfiniBand功能与数据包
无线子系统分析
802.11 MAC帧结构分析
扫描/身份验证/关联
高郚吐量(802.11n)
网状网络(802.11s)

(2)网络协议栈案例分析
epoll原理及系统调用案例分析
NIC网-卡驱动案例实战分析
NIC网-卡驱动的recv与sk _buff
NIC网卡open与stop的实现
NIC编译与用户态协议栈
RDMA栈架构
Netfilter内核防火墙报文处理
Iptables基础/表和链及过滤规则
Netilter5种挂接点详解
netfilter注册和注销钩子函数

六、中断管理与基础部分

(1)中断管理部分
ARM64异常处理技术
异常级别与分类
异常向量表与异常处理
中断及软中断
tasklet等待队列工作队列
中断控制器及域
中断处理流程
禁止_开启中断
处理器间中断
软中断分析
审计规则及数据结构
中断管理API案例分析
tasklet_init/tasklet_schedule
tasklet_hi_ schedule/tasklet_ kill
tasklet_disable_ nosync/tasklet _disable
setup_irq/request _threaded. irq/irqp_set _chip_data
中断管理之保存中断上下文
内核调试方法printk案例分析
gdb调试内核和模块案例分析
perf性能分析工具案例分析
perf原理机制与安装配置
perf采集数据命令29种工具应用
perf采集数据至火焰图分析
开源社区
如何参与开源社区
如何提交Linux内核补丁

(2)内核基础
Linux操作系统进程
计算机基础技术
进程原理
Linux特性与内核版本
进程特征与调度算法
死锁产生必要条件
进程状态及转换
进程调度策略与调度依据
存储器结构与分区存储管理
主存储器结构及技术指标
分区存储管理技术
物理内存与虚拟内存
存储管理
段式存储管理
页式存储管理
文件管理基础架构
文件基础知识
Linux文件系统
VFS(虚拟文件系统)
x86处理器架构
CPU(中央处理器)和内存
x86平台
64位通用寄存器结构.
汇编基础与寻址方式
汇编基础技术
常用寻址方式
立即寻址
直接寻址
间接寻址
址寻址
相对寻址
基址寻址
KVM架构基础
CPU虚拟化技术
I/O虚拟化技术
内存虚拟化技术
网络虚拟化技术
内核定时器
斥锁分析
自旋锁机制
原子变量案例实战分析
信号量案例实战分析
自旋锁项目实战分析
顺序锁案例实战分析
互斥锁项目实战分析

内核难点与学习方法
1.知识点多,关系错综复杂。
2.每一个知识点的难度都很难。
3.代码量很大,内核有几百万行。
4.操作系统相关的知识。
5.程序结构上的很多规范要求。