英语

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

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

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

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

    以下示例说明了差异

    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@mail>

  • Closes: URL 此修复的错误报告的 Message-ID

  • Originally-by: 原始作者 <original-author@mail>

  • Suggested-by: 建议者 <suggester@mail>

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

    Signed-off-by: 合作作者 <co-author@mail>

    请注意,合作开发和合作作者的 Signed-off-by 必须成对出现。

  • Signed-off-by: 作者 <author@mail>

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

  • Signed-off-by: 补丁处理者 <handler@mail>

    作者 SOB 之后的 SOB 来自处理和传输补丁的人员,但未参与开发。SOB 链应反映补丁传递给我们的实际路线,第一个 SOB 条目表示单个作者的主要作者身份。Ack 应以 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>

    如果补丁需要向后移植到稳定版本,请添加 ‘Cc: [email protected]’ 标签,但在发送邮件时不要抄送给 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 内核,它们就会重新打开。

大型系列应在合并窗口打开前 至少 一周以可合并状态提交。对于错误修复以及 有时 对于新硬件的小型独立驱动程序或用于硬件启用的小型侵入性补丁,可以例外。

在合并窗口期间,维护人员将专注于跟踪上游更改、修复合并窗口的后果、收集错误修复并让自己喘口气。请尊重这一点。

所谓的 _紧急_ 分支将在每个版本的稳定阶段合并到主线中。

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.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 位混淆。

在对于 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 维护者和子维护者在合并补丁时会尝试回复提交者,但他们有时会忘记或不适合当时的工作流程。虽然机器人消息纯粹是机械性的,但也暗示了“谢谢!已应用。”。