4. tip 树手册¶
4.1. 什么是 tip 树?¶
tip 树是几个子系统和开发领域的集合。tip 树既是一个直接的开发树,也是几个子维护者树的聚合树。tip 树 gitweb URL 是:https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git
tip 树包含以下子系统
x86 架构
x86 架构的开发在 tip 树中进行,除了 x86 KVM 和 XEN 特定的部分,这些部分在相应的子系统中维护,并直接从那里路由到主线。在 x86 特定的 KVM 和 XEN 补丁上抄送 x86 维护者仍然是一个好习惯。
一些 x86 子系统除了整体 x86 维护者之外还有自己的维护者。请在触及 arch/x86 中文件的补丁上抄送整体 x86 维护者,即使 MAINTAINER 文件中没有明确指出。
请注意,
[email protected]
不是邮件列表。它只是一个邮件别名,将邮件分发给 x86 顶级维护者团队。请始终抄送 Linux 内核邮件列表 (LKML)[email protected]
,否则您的邮件最终只会出现在维护者的私人收件箱中。调度器
调度器的开发在 -tip 树中进行,在 sched/core 分支中进行 - 偶尔会有一些用于进行中补丁集的主题子树。
锁定和原子操作
锁定开发(包括原子操作和其他与锁定相关的同步原语)在 -tip 树中进行,在 locking/core 分支中进行 - 偶尔会有一些用于进行中补丁集的主题子树。
通用中断子系统和中断芯片驱动程序:
中断核心开发在 irq/core 分支中进行
中断芯片驱动程序的开发也在 irq/core 分支中进行,但补丁通常在单独的维护者树中应用,然后聚合到 irq/core 中
时间、计时器、时间保持、NOHZ 和相关的芯片驱动程序:
时间保持、时钟源核心、NTP 和 alarmtimer 开发在 timers/core 分支中进行,但补丁通常在单独的维护者树中应用,然后聚合到 timers/core 中
时钟源/事件驱动程序开发在 timers/core 分支中进行,但补丁大多在单独的维护者树中应用,然后聚合到 timers/core 中
性能计数器核心、架构支持和工具:
perf 核心和架构支持开发在 perf/core 分支中进行
perf 工具开发在 perf 工具维护者树中进行,并聚合到 tip 树中。
CPU 热插拔核心
RAS 核心
大多数 x86 特定的 RAS 补丁都收集在 tip ras/core 分支中。
EFI 核心
EFI 开发在 efi git 树中进行。收集的补丁聚合到 tip efi/core 分支中。
RCU
RCU 开发在 linux-rcu 树中进行。结果更改聚合到 tip core/rcu 分支中。
各种核心代码组件:
debugobjects
objtool
随机的零星代码
4.2. 补丁提交说明¶
4.2.1. 选择树/分支¶
一般来说,针对 tip 树 master 分支的头部进行开发是可以的,但对于单独维护、有自己的 git 树且仅聚合到 tip 树中的子系统,开发应针对相关的子系统树或分支进行。
针对主线的错误修复应始终适用于主线内核树。针对 tip 树中已排队更改的潜在冲突由维护者处理。
4.2.2. 补丁主题¶
tip 树首选的补丁主题前缀格式是“subsys/component:”,例如“x86/apic:”、“x86/mm/fault:”、“sched/fair:”、“genirq/core:”。请不要使用文件名或完整的文件路径作为前缀。“git log path/to/file”在大多数情况下应该会给您一个合理的提示。
主题行中简明的补丁描述应以大写字母开头,并应使用祈使语气编写。
4.2.3. 变更日志¶
提交补丁指南中关于变更日志的一般规则适用。
tip 树维护者重视遵守这些规则,特别是以祈使语气编写变更日志,而不是冒充代码或代码的执行。这不仅仅是维护者的突发奇想。以抽象词语编写的变更日志比以小说形式编写的变更日志更精确,并且往往不易混淆。
将变更日志分成几个段落,而不是将所有内容都堆到一个段落中也很有用。一个好的结构是以单独的段落并按此顺序解释上下文、问题和解决方案。
插图示例
示例 1
x86/intel_rdt/mbm: Fix MBM overflow handler during hot cpu When a CPU is dying, we cancel the worker and schedule a new worker on a different CPU on the same domain. But if the timer is already about to expire (say 0.99s) then we essentially double the interval. We modify the hot cpu handling to cancel the delayed work on the dying cpu and run the worker immediately on a different cpu in same domain. We do not flush the worker because the MBM overflow worker reschedules the worker on same CPU and scans the domain->cpu_mask to get the domain pointer.改进版本
x86/intel_rdt/mbm: Fix MBM overflow handler during CPU hotplug When a CPU is dying, the overflow worker is canceled and rescheduled on a different CPU in the same domain. But if the timer is already about to expire this essentially doubles the interval which might result in a non detected overflow. Cancel the overflow worker and reschedule it immediately on a different CPU in the same domain. The work could be flushed as well, but that would reschedule it on the same CPU.示例 2
time: POSIX CPU timers: Ensure that variable is initialized If cpu_timer_sample_group returns -EINVAL, it will not have written into *sample. Checking for cpu_timer_sample_group's return value precludes the potential use of an uninitialized value of now in the following block. Given an invalid clock_idx, the previous code could otherwise overwrite *oldval in an undefined manner. This is now prevented. We also exploit short-circuiting of && to sample the timer only if the result will actually be used to update *oldval.改进版本
posix-cpu-timers: Make set_process_cpu_timer() more robust Because the return value of cpu_timer_sample_group() is not checked, compilers and static checkers can legitimately warn about a potential use of the uninitialized variable 'now'. This is not a runtime issue as all call sites hand in valid clock ids. Also cpu_timer_sample_group() is invoked unconditionally even when the result is not used because *oldval is NULL. Make the invocation conditional and check the return value.示例 3
The entity can also be used for other purposes. Let's rename it to be more generic.改进版本
The entity can also be used for other purposes. Rename it to be more generic.
对于复杂场景,特别是竞争条件和内存排序问题,使用表格来描述场景非常有用,该表格显示并行性和事件的时间顺序。这是一个例子
CPU0 CPU1
free_irq(X) interrupt X
spin_lock(desc->lock)
wake irq thread()
spin_unlock(desc->lock)
spin_lock(desc->lock)
remove action()
shutdown_irq()
release_resources() thread_handler()
spin_unlock(desc->lock) access released resources.
^^^^^^^^^^^^^^^^^^^^^^^^^
synchronize_irq()
Lockdep 提供类似的有用输出,以描述可能的死锁场景
CPU0 CPU1
rtmutex_lock(&rcu->rt_mutex)
spin_lock(&rcu->rt_mutex.wait_lock)
local_irq_disable()
spin_lock(&timer->it_lock)
spin_lock(&rcu->mutex.wait_lock)
--> Interrupt
spin_lock(&timer->it_lock)
4.2.4. 变更日志中的函数引用¶
当在变更日志(正文或主题行)中提及函数时,请使用“function_name()”格式。省略函数名称后面的括号可能会产生歧义
Subject: subsys/component: Make reservation_count static
reservation_count is only used in reservation_stats. Make it static.
带括号的变体更精确
Subject: subsys/component: Make reservation_count() static
reservation_count() is only called from reservation_stats(). Make it
static.
4.2.5. 变更日志中的回溯¶
请参阅 提交消息中的回溯。
4.2.7. 文档链接¶
在变更日志中提供文档链接对于以后的调试和分析有很大帮助。不幸的是,URL 经常会很快失效,因为公司会频繁地重组他们的网站。 “非易失性”的例外包括 Intel SDM 和 AMD APM。
因此,对于“易失性”文档,请在内核 bugzilla https://bugzilla.kernel.org 中创建一个条目,并将这些文档的副本附加到 bugzilla 条目。最后,在变更日志中提供 bugzilla 条目的 URL。
4.2.8. 补丁重发或提醒¶
请参阅 不要气馁 - 或者不耐烦。
4.2.9. 合并窗口¶
请不要期望 tip 维护人员在合并窗口期间或附近审查或合并补丁。在此期间,树会关闭,仅允许紧急修复。一旦合并窗口关闭并发布了新的 -rc1 内核,它们就会重新打开。
大型系列应在合并窗口打开前 至少 一周以可合并状态提交。对于错误修复以及 有时 对于新硬件的小型独立驱动程序或用于硬件启用的小型侵入性补丁,可以例外。
在合并窗口期间,维护人员将专注于跟踪上游更改、修复合并窗口的后果、收集错误修复并让自己喘口气。请尊重这一点。
所谓的 _紧急_ 分支将在每个版本的稳定阶段合并到主线中。
4.2.10. Git¶
tip 维护人员接受来自维护人员的 git 拉取请求,这些维护人员提供子系统更改以在 tip 树中聚合。
通常不接受针对新补丁提交的拉取请求,并且不能取代向邮件列表提交补丁的正确方法。主要原因是审查工作流程基于电子邮件。
如果您提交更大的补丁系列,则在私有存储库中提供一个 git 分支会很有帮助,这允许感兴趣的人轻松地拉取该系列进行测试。提供此分支的常用方法是在补丁系列的封面信中提供 git URL。
4.2.11. 测试¶
代码在提交给 tip 维护人员之前应该经过测试。除了小的更改之外,任何其他更改都应使用全面的(和重量级的)内核调试选项启用进行构建、启动和测试。
这些调试选项可以在 kernel/configs/x86_debug.config 中找到,并且可以通过运行以下命令将其添加到现有的内核配置中
make x86_debug.config
其中一些选项是 x86 特定的,在其他架构上进行测试时可以省略。
4.3. 编码风格说明¶
4.3.2. 记录锁定要求¶
记录锁定要求是一件好事,但注释不一定是最佳选择。而不是写
/* Caller must hold foo->lock */ void func(struct foo *foo) { ... }请使用
void func(struct foo *foo) { lockdep_assert_held(&foo->lock); ... }在 PROVE_LOCKING 内核中,如果调用者没有持有锁,则 lockdep_assert_held() 会发出警告。注释无法做到这一点。
4.3.3. 括号规则¶
只有当 ‘if’、‘for’、‘while’ 等语句之后真正只有一行时,才可以省略括号
if (foo)
do_something();
即使 C 不需要括号,以下也不被视为单行语句
for (i = 0; i < end; i++)
if (foo[i])
do_something(foo[i]);
在外循环周围添加括号可以增强阅读流程
for (i = 0; i < end; i++) {
if (foo[i])
do_something(foo[i]);
}
4.3.4. 变量声明¶
函数开头变量声明的首选顺序是倒二叉树顺序
struct long_struct_name *descriptive_name;
unsigned long foo, bar;
unsigned int tmp;
int ret;
上面的解析速度比相反的顺序更快
int ret;
unsigned int tmp;
unsigned long foo, bar;
struct long_struct_name *descriptive_name;
甚至比随机排序更快
unsigned long foo, bar;
int ret;
struct long_struct_name *descriptive_name;
unsigned int tmp;
此外,请尝试将相同类型的变量聚合到一行中。没有必要浪费屏幕空间
unsigned long a;
unsigned long b;
unsigned long c;
unsigned long d;
这样做就足够了
unsigned long a, b, c, d;
还请避免在变量声明中引入换行符
struct long_struct_name *descriptive_name = container_of(bar,
struct long_struct_name,
member);
struct foobar foo;
最好将初始化移到声明后的单独行
struct long_struct_name *descriptive_name;
struct foobar foo;
descriptive_name = container_of(bar, struct long_struct_name, member);
4.3.5. 变量类型¶
请对用于描述硬件或用作访问硬件的函数的参数的变量使用适当的 u8、u16、u32、u64 类型。这些类型清楚地定义了位宽,并避免了截断、扩展和 32/64 位混淆。
在对于 32 位内核,当使用 ‘unsigned long’ 时会变得模棱两可的代码中,也建议使用 u64。虽然在这种情况下也可以使用 ‘unsigned long long’,但 u64 更短,并且还清楚地表明该操作必须是 64 位宽,而与目标 CPU 无关。
请使用 ‘unsigned int’ 而不是 ‘unsigned’。
4.3.6. 常量¶
请不要在代码或初始化器中使用字面(十六进制)十进制数字。要么使用具有描述性名称的正确定义,要么考虑使用枚举。
4.3.7. 结构体声明和初始化器¶
结构体声明应以表格形式对齐结构体成员名称
struct bar_order {
unsigned int guest_id;
int ordered_item;
struct menu *menu;
};
请避免在声明中记录结构体成员,因为这通常会导致格式奇怪的注释,并且结构体成员会被混淆
struct bar_order {
unsigned int guest_id; /* Unique guest id */
int ordered_item;
/* Pointer to a menu instance which contains all the drinks */
struct menu *menu;
};
相反,请考虑在结构体声明之前的注释中使用 kernel-doc 格式,这更容易阅读,并且还具有将信息包含在内核文档中的额外优势,例如,如下所示
/**
* struct bar_order - Description of a bar order
* @guest_id: Unique guest id
* @ordered_item: The item number from the menu
* @menu: Pointer to the menu from which the item
* was ordered
*
* Supplementary information for using the struct.
*
* Note, that the struct member descriptors above are arranged
* in a tabular fashion.
*/
struct bar_order {
unsigned int guest_id;
int ordered_item;
struct menu *menu;
};
静态结构体初始化器必须使用 C99 初始化器,并且也应以表格形式对齐
static struct foo statfoo = {
.a = 0,
.plain_integer = CONSTANT_DEFINE_OR_ENUM,
.bar = &statbar,
};
请注意,虽然 C99 语法允许省略最后一个逗号,但我们建议在最后一行使用逗号,因为它使重新排序和添加新行更容易,并且也使得未来此类补丁更容易阅读。
4.3.8. 换行符¶
将行长度限制为 80 个字符会使深度缩进的代码难以阅读。考虑将代码分解为辅助函数,以避免过多的换行。
80 个字符的规则不是严格的规则,因此在换行时请运用常识。特别是格式字符串永远不应被打断。
在拆分函数声明或函数调用时,请将第二行中的第一个参数与第一行中的第一个参数对齐
static int long_function_name(struct foobar *barfoo, unsigned int id,
unsigned int offset)
{
if (!id) {
ret = longer_function_name(barfoo, DEFAULT_BARFOO_ID,
offset);
...
4.3.9. 命名空间¶
函数/变量命名空间提高了可读性,并易于使用 grep 搜索。这些命名空间是全局可见的函数和变量名称的字符串前缀,包括内联函数。这些前缀应组合子系统和组件名称,例如“x86_comp_”、“sched_”、“irq_”和“mutex_”。
这也包括立即放入全局可见的驱动程序模板中的静态文件作用域函数 - 这些符号携带一个好的前缀也很有用,以便于回溯的可读性。
局部静态函数和变量可以省略命名空间前缀。真正由其他局部函数调用的局部函数可以使用较短的描述性名称 - 我们主要关注的是可搜索性和回溯的可读性。
请注意,“xxx_vendor_”和“vendor_xxx_”前缀对于供应商特定文件中的静态函数没有帮助。毕竟,已经很清楚该代码是供应商特定的。此外,供应商名称只应用于真正的供应商特定功能。
与往常一样,请运用常识,力求一致性和可读性。
4.4. 提交通知¶
一个机器人会监控 tip 树中的新提交。该机器人会向一个专门的邮件列表([email protected]
)发送每项新提交的电子邮件,并抄送在提交标签中提及的所有人员。它使用标签列表末尾 Link 标签中的电子邮件消息 ID 来设置 In-Reply-To 电子邮件标头,以便该消息可以正确地与补丁提交电子邮件一起进行线程处理。
tip 维护者和子维护者在合并补丁时会尝试回复提交者,但他们有时会忘记或不适合当时的工作流程。虽然机器人消息纯粹是机械性的,但也暗示了“谢谢!已应用。”。
4.3.1. 注释风格¶
注释中的句子以大写字母开头。
单行注释
多行注释
无尾部注释(见下文)
注释重要的内容
函数文档注释