关注了就能看到更多这么棒的文章哦~
Exported-symbol changes in 5.13
By Jonathan Corbet
May 17, 2021
DeepL assisted translation
https://lwn.net/Articles/856312/
多年来,内核社区对于是否将 kernel 内部内核符号(kernel symbol)导出供 loadable module 使用,一直存在着很多争议。导出(export)符号表,一般来说就会将具体实现细节暴露给了外部代码,也就是可能会被按照意料之外的一些方式来使用(或滥用),并且未来要想再对这些函数进行修改的话就会面临极大的困难。目前并没有任何机构来监管 symbol export,也没有什么官方流程来对这些 export 进行批准。每次都是有人发现了他们希望看到的改动时,才会有相关讨论出现。但是,我们可以很容易地对比出新的 kernel 版本比起之前来说有哪些 export symbol 发生了变化,这样一来我们就可以了解到底层究竟发生了哪些变化。
内核有数千个的函数以及数据结构,其中大多数都是某些源文件自己的私有内容,其他的则是整个内核都可以使用的。但 loadable module 的地位很特殊,它们只能访问那些用 EXPORT_SYMBOL()(或它的其他变体) 明确 export 出来的符号。内核 Image 代码可以使用的符号中,有很多都是 loadable module 无法使用的。这样做的目的是为了使 module 的接口数量尽量少一些,方便管理。
不过,这个目标目前看来并未实现。5.12 版的 kernel 向 module 总共 export 了 31695 个符号,这并不是一个数量很少的接口列表。这个数字在 5.13-rc1 中增长到了 31822 个。这意味着增加了 127 个符号,但实际情况比这更复杂,在这段时间里有 244 个之前 export 的符号被删除,而新增了 371 个。细节可以在这个页面(https://lwn.net/Articles/856313/)上看到。
有些改动其实不算是新增或删除,实际上是改了个名字。例如,pmbus_do_probe()在 5.13 中就不再按照这个名字 export 了,它现在(按照编者随便定义的标记来说的话)是 PMBUS::pmbus_do_probe()。换句话说,这个符号已经从全局命名空间(global namespace)移到了子系统自己特定的 namespace。2018 年开始支持对 kernel symbol export 时使用 namespace,但目前看来进展不是很快。5.13 内核增加了一个新的 namespace(PMBUS),这个子系统内的 export symbol 都正在迁移过来。现在内核中有 18 个关于 symbol 的 namespace:
CRYPTO_INTERNAL, FIRMWARE_LOADER_PRIVATE, LTC2497, MCB, NVME_TARGET_PASSTHRU, PMBUS, SND_INTEL_SOUNDWIRE_ACPI, SND_SOC_SOF_INTEL_HDA_COMMON, SND_SOC_SOF_MERRIFIELD, SND_SOC_SOF_HDA_AUDIO_CODEC, SND_SOC_SOF_HDA_AUDIO_CODEC_I915, SND_SOC_SOF_INTEL_HIFI_EP_IPC, SND_SOC_SOF_INTEL_HIFI_EP_IPC, SND_SOC_SOF_ACPI_DEV, SND_SOC_SOF_PCI_DEV, SND_SOC_SOF_XTENSA, SOUNDWIRE_INTEL_INIT, and TEST_FIRMWARE.
显然,目前位置 sound 子系统是最热心的 symbol namespace 用户。
export 出来的 symbol 中,许多其他改动都是来自于内核内部的代码重构。在 bit-finding library 中再进行一些优化,导致 find_first_bit() 这类函数都变成了头文件中的 inline 函数,这些函数不再需要 export。但它们底层调用的 find_first_bit() 之类的函数,现在就需要 export 了。vmem_map 这个通用符号是专门针对 ia64 架构的,既然 ia64 放弃了对 VMEMMAP memory model 的支持,那么这个 symbol 也就被移除了。wimax 相关的 symbol 已经跟着那些不再流行的 WiMAX 驱动程序一起消失了。像 rt_mutex_destroy() 这样的函数被删除了则是因为它们没有什么地方会用到。
新增的许多 symbol 都是跟新功能相对应的。例如,alloc_pages() 就是跟着 batch page allocation 这个功能一起加进来的。还有一些 symbol 就不那么清晰易懂了,比如说,dotdot_name 是什么意思?相关的 commit 中指出这是一个 "会有用的常量:用于表示'…'的 struct qstr",很多人估计会觉得这个说法很难理解。它实际上是为文件系统代码提供了一个快捷方式,使其能够引用名为 "… " 的目录,不用再将其封装在 "quick string" structure(用于在虚拟文件系统层中传递字符串)之中了。有些文件系统在 5.13 中开始使用它。
一般来说,如果 mainline kernel 中没有地方会用到某个 kernel symbol 的话,它就不应该被 export。这条规则通常是被遵守的,但也有一些例外。举个例子,zynqmp_pm_pinctrl_get_function()在 5.13-rc1 中被 export 出来,但 kernel 里面并没有地方在用。其他许多也被 export 出来的 zynqmp_ 开头的符号(都与 Xilinx Zynq SoC 有关)也没有被广泛使用,看起来都应该隐藏在它们自己的 namespace 中。另一个导出但未被使用的符号是 __cfi_slowpath_diag(),这是 Clang control-flow-integrity 实现代码的一部分,也在本次合并窗口之内被合并了。导出这个符号的原因尚不完全清楚。__cpu_dying_mask()也是在 5.13 中引入并 export 的,内核中也没有地方使用到。还有许多其他类似情况。"先导出,以备不时之需" 似乎是内核开发者的一个常见习惯。
在 5.13 内核中,增加了 11 个 devm_ 开头的 export 符号,还有两个内部使用的__devm_开头的符号。并非所有这些都已经被用起来了,但它们确实是典型的、人们希望 export 出去的符号。这些函数是用来做 "managed device",也就是使编写设备驱动程序更容易,并在 device 关闭时方便释放所分配的资源,从而更加安全。现在已经有超过 300 个这样的函数被 export 到 module 之中,而且这个列表看起来还会继续增加。
direct rendering manager(DRM)图形子系统这次增加了 17 个 drm_ 开头的 export symbol。DRM 显然是内核中最复杂的驱动 API 之一,在 5.13 中导出的符号不少于 850 个。人们开始理解为什么这个子系统的开发者会把文档放在第一优先级。如果没有文档,这些 API 就很难使用了。当然,这也是因为 graphics processor 都是很复杂的设备,所以 API 会很多。
既然 kernel 提供了接近 32000 个 export symbol,还号称是一个 "尽量缩小规模" 的 module 接口,可以说明内核整体来说也是一个非常复杂的环境。这种复杂性反映在很多地方,比如它提供给用户空间的接口越来越多,提供给 loadable module 接口越来越多。多年来,这些接口的规模已经增加了许多,并且往往没有经过深入的 review。好消息是,export symbol 还是算作是 kernel 的内部接口,也就是随时可以更改。因此,也许将来有一天这个 list 会变小也说不定,但 5.13 周期里是不会发生这个情况了。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~