英语

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.6. 提交标签的排序

为了使提交标签具有统一的视图,tip 维护者使用以下标签排序方案

  • Fixes: 12+char-SHA1 (“sub/sys: 原始主题行”)

    即使对于不需要回溯到稳定内核的更改,也应添加 Fixes 标签,即当解决最近引入的仅影响 tip 或当前主线 head 的问题时。 这些标签有助于识别原始提交,并且比在变更日志本身中突出提及引入问题的提交更有价值,因为它们可以自动提取。

    以下示例说明了差异

    Commit
    
      abcdef012345678 ("x86/xxx: Replace foo with bar")
    
    left an unused instance of variable foo around. Remove it.
    
    Signed-off-by: J.Dev <j.dev@mail>
    

    请说

    The recent replacement of foo with bar left an unused instance of
    variable foo around. Remove it.
    
    Fixes: abcdef012345678 ("x86/xxx: Replace foo with bar")
    Signed-off-by: J.Dev <j.dev@mail>
    

    后者将有关补丁的信息置于焦点位置,并使用对引入问题的提交的引用来修改它,而不是首先将焦点放在原始提交上。

  • Reported-by: Reporter <reporter@mail>

  • Closes: URL or Message-ID of the bug report this is fixing

  • Originally-by: Original author <original-author@mail>

  • Suggested-by: Suggester <suggester@mail>

  • Co-developed-by: Co-author <co-author@mail>

    Signed-off-by: Co-author <co-author@mail>

    请注意,共同作者的 Co-developed-by 和 Signed-off-by 必须成对出现。

  • Signed-off-by: Author <author@mail>

    最后一个 Co-developed-by/SOB 对之后的第一个 Signed-off-by (SOB) 是作者 SOB,即 git 标记为作者的人。

  • Signed-off-by: Patch handler <handler@mail>

    作者 SOB 之后的 SOB 来自处理和传输补丁的人,但没有参与开发。 SOB 链应反映补丁传播到我们的 **真实** 路线,第一个 SOB 条目表示单个作者的主要作者身份。 Acks 应作为 Acked-by 行给出,评审批准应作为 Reviewed-by 行给出。

    如果处理程序对补丁或变更日志进行了修改,则应在变更日志文本 **之后** 并在所有提交标签 **之上** 以以下格式提及

    ... changelog text ends.
    
    [ handler: Replaced foo by bar and updated changelog ]
    
    First-tag: .....
    

    注意两个空的新行,它们将变更日志文本和提交标签与该通知分开。

    如果补丁由处理程序发送到邮件列表,则必须在变更日志的第一行中注明作者,

    From: Author <author@mail>
    
    Changelog text starts here....
    

    以便保留作者身份。 “From:” 行之后必须跟一个空的新行。 如果缺少 “From:” 行,则该补丁将归因于发送(传输,处理)它的人。 “From:” 行在应用补丁时会自动删除,并且不会显示在最终的 git 变更日志中。 它仅影响生成的 Git 提交的作者身份信息。

  • Tested-by: Tester <tester@mail>

  • Reviewed-by: Reviewer <reviewer@mail>

  • Acked-by: Acker <acker@mail>

  • Cc: cc-ed-person <person@mail>

    如果补丁应该向后移植到 stable,请添加一个 “Cc: stable@vger.kernel.org” 标签,但在发送邮件时不要抄送 stable。

  • Link: https://link/to/information

    对于引用发布到内核邮件列表的电子邮件,请使用 lore.kernel.org 重定向器 URL

    Link: https://lore.kernel.org/email-message-id@here
    

    当引用相关的邮件列表主题,相关的补丁集或其他值得注意的讨论主题时,应使用此 URL。 将 Link: 预告片与提交消息相关联的便捷方法是使用类似 markdown 的带括号的表示法,例如

    A similar approach was attempted before as part of a different
    effort [1], but the initial implementation caused too many
    regressions [2], so it was backed out and reimplemented.
    
    Link: https://lore.kernel.org/some-msgid@here # [1]
    Link: https://bugzilla.example.org/bug/12345  # [2]
    

    您还可以使用 Link: 预告片来指示将补丁应用于 git 树时的来源。 在这种情况下,请使用专用的 patch.msgid.link 域,而不是 lore.kernel.org。 这种做法使自动工具可以识别使用哪个链接来检索原始补丁提交。 例如

    Link: https://patch.msgid.link/patch-source-message-id@here
    

请不要使用组合标签,例如 Reported-and-tested-by,因为它们只会使自动提取标签复杂化。

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.1. 注释风格

注释中的句子以大写字母开头。

单行注释

/* This is a single line comment */

多行注释

/*
 * This is a properly formatted
 * multi-line comment.
 *
 * Larger multi-line comments should be split into paragraphs.
 */

无尾部注释(见下文)

请避免使用尾部注释。 尾部注释几乎在所有上下文中都会干扰阅读流程,尤其是在代码中

if (somecondition_is_true) /* Don't put a comment here */
        dostuff(); /* Neither here */

seed = MAGIC_CONSTANT; /* Nor here */

请改用独立的注释

/* This condition is not obvious without a comment */
if (somecondition_is_true) {
        /* This really needs to be documented */
        dostuff();
}

/* This magic initialization needs a comment. Maybe not? */
seed = MAGIC_CONSTANT;

在标头中记录结构时,请使用 C++ 风格的尾部注释,以实现更紧凑的布局和更好的可读性

// eax
u32     x2apic_shift    :  5, // Number of bits to shift APIC ID right
                              // for the topology ID at the next level
                        : 27; // Reserved
// ebx
u32     num_processors  : 16, // Number of processors at current level
                        : 16; // Reserved

/* eax */
        /*
         * Number of bits to shift APIC ID right for the topology ID
         * at the next level
         */
 u32     x2apic_shift    :  5,
         /* Reserved */
                         : 27;

/* ebx */
        /* Number of processors at current level */
u32     num_processors  : 16,
        /* Reserved */
                        : 16;

评论重要的东西

在操作不明显的地方应添加注释。 记录显而易见的内容只是一个分心

/* Decrement refcount and check for zero */
if (refcount_dec_and_test(&p->refcnt)) {
        do;
        lots;
        of;
        magic;
        things;
}

相反,注释应该解释不明显的细节并记录约束

if (refcount_dec_and_test(&p->refcnt)) {
        /*
         * Really good explanation why the magic things below
         * need to be done, ordering and locking constraints,
         * etc..
         */
        do;
        lots;
        of;
        magic;
        /* Needs to be the last operation because ... */
        things;
}

函数文档注释

要记录函数及其参数,请使用 kernel-doc 格式,而不是自由形式的注释

/**
 * magic_function - Do lots of magic stuff
 * @magic:      Pointer to the magic data to operate on
 * @offset:     Offset in the data array of @magic
 *
 * Deep explanation of mysterious things done with @magic along
 * with documentation of the return values.
 *
 * Note, that the argument descriptors above are arranged
 * in a tabular fashion.
 */

这尤其适用于全局可见的函数和公共头文件中的内联函数。 对于每个需要少量解释的(静态)函数,使用 kernel-doc 格式可能有点过头了。 描述性函数名称的使用通常会取代这些少量注释。 始终应用常识。

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。 虽然机器人消息纯粹是机械的,但它也暗示着 “谢谢!已应用。”。