4. tip tree 手册¶
4.1. 什么是 tip tree?¶
tip tree 是几个子系统和开发领域的集合。 tip tree 既是直接开发树,也是几个子维护者树的聚合树。 tip tree gitweb URL 是: https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git
tip tree 包含以下子系统
x86 架构
x86 架构的开发在 tip tree 中进行,除了 x86 KVM 和 XEN 特定的部分,它们在相应的子系统中维护,并直接从那里路由到主线。 在 x86 特定的 KVM 和 XEN 补丁上抄送给 x86 维护者仍然是一个好的做法。
除了整体 x86 维护者之外,一些 x86 子系统还有自己的维护者。 请在涉及 arch/x86 中文件的补丁上抄送给整体 x86 维护者,即使它们没有在 MAINTAINER 文件中被调用。
请注意,
x86@kernel.org
不是邮件列表。 它只是一个邮件别名,用于将邮件分发到 x86 顶级维护者团队。 请始终抄送 Linux 内核邮件列表 (LKML)linux-kernel@vger.kernel.org
,否则您的邮件最终只会出现在维护者的私人收件箱中。调度器
调度器开发在 -tip tree 中进行,位于 sched/core 分支中 - 偶尔也会有用于正在进行中的补丁集的子主题树。
锁定和原子操作
锁定开发(包括原子操作和其他与锁定相关的同步原语)在 -tip tree 中进行,位于 locking/core 分支中 - 偶尔也会有用于正在进行中的补丁集的子主题树。
通用中断子系统和中断芯片驱动程序:
中断核心开发发生在 irq/core 分支中
中断芯片驱动程序开发也发生在 irq/core 分支中,但补丁通常在单独的维护者树中应用,然后聚合到 irq/core 中
时间,定时器,时间管理,NOHZ 和相关芯片驱动程序:
时间管理,clocksource 核心,NTP 和 alarmtimer 开发发生在 timers/core 分支中,但补丁通常在单独的维护者树中应用,然后聚合到 timers/core 中
clocksource/event 驱动程序开发发生在 timers/core 分支中,但补丁主要在单独的维护者树中应用,然后聚合到 timers/core 中
性能计数器核心,架构支持和工具:
perf 核心和架构支持开发发生在 perf/core 分支中
perf 工具开发发生在 perf tools 维护者树中,并聚合到 tip tree 中。
CPU 热插拔核心
RAS 核心
主要是在 tip ras/core 分支中收集 x86 特定的 RAS 补丁。
EFI 核心
EFI 开发在 efi git 树中进行。 收集的补丁聚合在 tip efi/core 分支中。
RCU
RCU 开发发生在 linux-rcu 树中。 结果更改聚合到 tip core/rcu 分支中。
各种核心代码组件:
debugobjects
objtool
随机片段
4.2. 补丁提交注意事项¶
4.2.1. 选择 tree/branch¶
通常,针对 tip tree master 分支的 head 进行开发是可以的,但对于单独维护,拥有自己的 git 树并且仅聚合到 tip tree 中的子系统,应该针对相关的子系统树或分支进行开发。
针对主线的错误修复应该始终适用于主线内核树。 与已经在 tip tree 中排队的更改的潜在冲突由维护者处理。
4.2.2. 补丁主题¶
tip tree 补丁主题前缀的首选格式是 “subsys/component:”,例如 “x86/apic:”,“x86/mm/fault:”,“sched/fair:”,“genirq/core:”。 请不要使用文件名或完整文件路径作为前缀。 在大多数情况下,“git log path/to/file” 应该给你一个合理的提示。
主题行中简明的补丁描述应以大写字母开头,并应以祈使语气书写。
4.2.3. 变更日志¶
关于 提交补丁指南 中变更日志的一般规则适用。
tip tree 维护者重视遵循这些规则,特别是以祈使语气编写变更日志,而不是模仿代码或其执行的要求。 这不仅仅是维护者的突发奇想。 用抽象词语编写的变更日志比用小说形式编写的变更日志更精确,也更不容易混淆。
将变更日志分成几个段落而不是将所有内容都集中到一个段落中也很有用。 一个好的结构是在单独的段落中解释上下文,问题和解决方案,并按此顺序排列。
示例说明
示例 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 内核,它们就会重新打开。
大型系列应在合并窗口打开 **至少** 一周前以可合并状态提交。 例外情况是错误修复和 *有时* 用于新硬件的小型独立驱动程序或硬件启用方面的最小侵入性补丁。
在合并窗口期间,维护者而是专注于关注上游更改,修复合并窗口 fallout,收集错误修复,并让自己喘口气。 请尊重这一点。
所谓的 _紧急_ 分支将在每个版本的稳定阶段合并到主线中。
4.2.10. Git¶
tip 维护者接受来自维护者的 git pull 请求,这些维护者提供子系统更改以在 tip tree 中进行聚合。
通常不接受针对新补丁提交的 pull 请求,并且不能代替向邮件列表正确提交补丁。 主要原因是审查工作流程是基于电子邮件的。
如果您提交了一个更大的补丁系列,那么在私有存储库中提供一个 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 位混淆。
在 “unsigned long” 会被使用的情况下,对于 32 位内核来说,如果代码变得模棱两可,也建议使用 u64。 虽然在这种情况下也可以使用 “unsigned long long”,但 u64 更短,并且清楚地表明无论目标 CPU 如何,操作都需要 64 位宽。
请使用 “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. 命名空间¶
函数/变量命名空间提高了可读性,并允许轻松进行 grepping。 这些命名空间是全局可见的函数和变量名称(包括内联)的字符串前缀。 这些前缀应结合子系统和组件名称,例如 “x86_comp_”,“sched_”,“irq_” 和 “mutex_”。
这也包括立即放入全局可见的驱动程序模板中的静态文件范围函数 - 对于这些符号来说,携带一个好的前缀也很有用,以提高回溯的可读性。
对于本地静态函数和变量,可以省略命名空间前缀。 真正本地的函数,仅由其他本地函数调用,可以具有更短的描述性名称 - 我们的主要关注点是 greppability 和回溯可读性。
请注意,“xxx_vendor_” 和 “vendor_xxx_` 前缀对于特定于供应商的文件中的静态函数没有帮助。 毕竟,代码已经是特定于供应商的代码这一点已经很清楚了。 此外,供应商名称仅应用于真正特定于供应商的功能。
与往常一样,应用常识,并力求一致性和可读性。
4.4. 提交通知¶
tip tree 由一个机器人监视是否有新的提交。 该机器人为每个新提交向专用的邮件列表 (linux-tip-commits@vger.kernel.org
) 发送电子邮件,并抄送所有在提交标签之一中提到的人。 它使用标签列表末尾的 Link 标签中的电子邮件消息 ID 来设置 In-Reply-To 电子邮件标头,以便该消息与补丁提交电子邮件正确地进行线程处理。
tip 维护者和子维护者尝试在合并补丁时回复提交者,但他们有时会忘记或不适合当时的 workflow。 虽然机器人消息纯粹是机械的,但它也暗示着 “谢谢!已应用。”。
4.3.1. 注释风格¶
注释中的句子以大写字母开头。
单行注释
多行注释
无尾部注释(见下文)
评论重要的东西
函数文档注释