使用 Bazel、Kleaf 和 DDK 构建外部 Android 内核模块——您准备好了吗?
发表于 2024-03-14 10:11:54

您是否已经开始将 Android 内核构建流程迁移到 Google Bazel 构建系统和 Kleaf 框架?从 Android 14 开始,强烈建议开发者使用 Bazel Kleaf 构建 Android 内核及其工件,因此您应该将此迁移工作列为待办事项。

在高通创新中心 (QUIC),我们已经完成迁移,将所有内核构建流程迁移到 KleafKleaf 很重要的一部分是用于构建外部模块的开源驱动程序开发工具包(DDK)。本篇文章将介绍我们过去是如何构建这些模块的,以及现在如何使用 DDK 进行构建。我将解释一些优缺点,希望您顺利完成迁移。

(这是我在 Linux Plumbers 大会上的演讲“驱动程序开发工具包(DDK 和供应商工作流”的摘要。详见文件底部。)

DDK 之前,我们如何构建外部模块

内核

首先,我们有一个内核团队,管理内部kernel_platform 树,包括通用构建定义和 API 合规性等内容。build.sh 构建内核和树内(即在下游 msm-kernel 树中)供应商模块:

技术包

对于显示、音频、WLAN和蓝牙等特定技术领域,我们有技术团队或“技术包”。每个技术团队都拥有各自的 Git 存储库和在供应商树中的位置。有些技术团队在构建中包含内核模块,因此,我们为其中大约 50 个执行树外构建。

技术包的源代码位于内核平台树之外。除了技术包存储库中的模块源代码外,他们还有一个 Kbuild 文件和一个 Makefile

该存储库中还有 Android.mk 文件。它包含针对共享生成文件 Build_external_kernelmodule.mk include 语句,每个技术包都使用该语句。这就是我们为顶层 Android 开源项目 AOSP)构建定义目标的方式。该共享 makefile 再调用 build_module.sh,启动经典的 make -C M=$PWD 外部模块构建。

我们注意到,此构建需要大量的自定义行为,因此十分脆弱。通常出现构建问题时,我们只需清理工作区即可解决问题。这表明构建是不正确的,以至于经常不能正确重新构建。

我们现在如何使用 DDK 构建外部模块

Bazel Google 推出的不受特定编程语言限制的构建系统,在不牺牲速度的情况下确保构建的正确性。Google 在内部使用Bazel完成很多工作,因此,顺理成章地将其扩展到Android

内核

自从我们迁移到 Bazel DDK 以来,用于构建内核模块和供应商树模块的树发生了变化:

不再使用 build.sh。我们转而使用 Kleaf Bazel 构建定义 BUILD.bazel),并使用 Kleaf 定义一个 kernel_build() 目标。我们运行 bazel build //msm-kernel:${target}_${variant},而不是 build.sh。所有这些均在存储库的 Bazel 文件中定义。

技术包

不再使用 Kbuild Makefile,技术包现在有一个 BUILD.bazel 文件。他们还有一个 ddk_module() 定义,可以从 Kleaf 加载ddk_module规则,我们可以定义输入、输出、依赖项和要构建的内核。

在将顶层 AOSP build 转换成使用 Bazel 之前,我们继续使用相同的 Android.mk 并调用 Build_external_kernelmodule.mk。但是,当我们进入到构建脚本环节时,我们不再使用原始的 make 命令,而是运行 bazel build //vendor/qcom/opensource/techpack:my_module,构建 DDK 目标。

另外需要了解的是,Kleaf 假定所有源代码都在内核平台树下,但实际并非如此。因此,我们插入了 vendor -> ../vendor 符号链接,欺骗 Kleaf 认为模块源代码位于kernel_platform

DDK 的优点和缺点

我们发现,切换到 DDK 利大于弊。

优点:内核头文件的可见性有限

这是我们内核团队认为的最大优势。

构建 DDK 模块时,Kleaf 会将所有公共内核头文件和源代码复制到沙盒中。如果模块试图使用私有内核头文件,Kleaf 将生成如下错误:

在此示例中,mm/slab.h 是私有头文件,因此 Kleaf 没有将其复制到沙盒中。因而在构建时找不到这个文件。

有了这个功能,我们发现了有些模块中有好几处不经意地使用了私有头文件。此后,我们做了改变。

优点:上游支持和协作

在我们的迁移过程中,Google 始终高度配合。他们的工程师协助我们在我们的环境中使用 Kleaf,并将我们的技术包所需的功能和修复程序集成到 DDK 中。任何开发者都可以向 AOSP 提交更改,如果该特性有价值,Google 可能会与您合作将其合并。

优点:更少的样板代码

某些情况下,我们可以在构建中删除大约一半的代码,主要是因为我们可以使用 Starlark 宏进行矢量化和循环。DDK 能够更简洁地降低构建定义逻辑的复杂性。

例如,DDK 会自动生成 Kbuild Makefile。尽管根据现有驱动构建版本移植行为可能比较棘手,但我们发现,对于新驱动程序而言好处多多。

优点:依赖关系图绘制

利用 bazel 查询,您可以查询有向无环图 DAG)。您可以询问有关构建系统的问题,例如“哪些模块依赖于此模块?”“此模块使用哪些头文件?”、“此模块依赖于哪些内核模块?”在尝试迁移大型环境时,这些问题非常有用。

优点:构建性能分析

您可以向 Bazel build 命令传递一个额外标志,生成包含性能数据的输出文件。将其加载到 Chrome 中,以获得类似于以下的性能分析图:

然后进行分析,检查是否有任何特定的瓶颈、不必要的依赖关系和并行执行的可能性。

优点:更方便分离相互依赖和 API

在我们以前使用的构建系统中,技术包通常依赖于各自的符号或头文件。没有其他更简洁的方法。持续传递 Makefile includes 和连接 Module.symvers 是一件很繁琐的任务。

DDK 简化了这些工作。如果您希望某个模块向其他模块提供输入,可以列出公共头文件以及构建目录中向其他模块公开的头文件。另外,您也可以利用本地 Bazel 可见性,定义允许哪些模块依赖指定模块。

如果希望让模块 foo 依赖于模块 bar”,只需将 deps = ":bar" 添加到模块 foo 的定义中。运行 build 命令时,它将从依赖模块中复制公共头文件。还将处理所有 Module.symvers 符号解析。

缺点:新的构建系统

说实话,学习新的构建系统并非易事。很少有内核开发人员熟悉 Bazel StarlarkBazel 使用和类似 Python 的语言),所以,对我们来说是一个沉重的负担。

此外,Bazel 分为不同的构建阶段,可能会使人困惑。您可能会在构建一个模块时,接收到有关构建中其他完全不相关部分的错误信息。

Bazel 会有一些限制,例如禁止使用环境变量污染构建。而我们的内核开发人员已经有多年使用环境变量的习惯,所以,如果不能再使用的话,可能会感到阻力和挫败感。请注意,这些限制是有充分理由的,但也确实影响了一些现有的工作流程。必须将其看作是确保构建严谨性的成本。

缺点:调试

正如上文所言,限制内核头文件可见性是一大优势。但是,沙盒可能会使调试变得更加复杂。默认情况下,构建完成后,Bazel 会清理沙盒,清除调试构建问题时可能依赖的任何标记。如果希望保留,必须传递一个特殊的标志。

此外,调试消息中的沙盒路径较长,不易处理。往往会使输出造成不必要地拥挤、混乱。

下一步

不管怎样,我们和您一样,都是 Android 开发者,和您一样忙碌。我们已经从 Make 切换到 Bazel DDK,您也可以。总的来说,我们很高兴完成了迁移工作。

当然,Bazel 有一定的学习门槛。在开始进行像我们这样大规模的迁移之前,有必要先了解架构,在这方面,Half-Day Bazel Bootcamp是一个很好的资源。这个视频分成两个部分,对我们极有帮助,强烈推荐观看。

有关更多信息和代码,请参阅我在 Linux Plumbers Conference 上的演讲:驱动程序开发工具包(DDK)和供应商工作流”,其中包含我的幻灯片和视频的链接。

CSDN官方微信
扫描二维码,向CSDN吐槽
微信号:CSDNnews
微博关注
【免责声明:CSDN本栏目发布信息,目的在于传播更多信息,丰富网络文化,稿件仅代表作者个人观点,与CSDN无关。其原创性以及文中陈述文字和文字内容未经本网证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本网不做任何保证或者承诺,请读者仅作参考,并请自行核实相关内容。您若对该稿件有任何怀疑或质疑,请立即与CSDN联系,我们将迅速给您回应并做处理。】