Linux 内核模块为什么不能在运行时处理兼容性问题?
背景
从事 Linux 内核开发的人,大概都知道从 Linux 5.7 开始,为了防止第三方模块绕过 EXPORT_SYMBOL 的限制访问内部函数, kallsyms_lookup_name() 不再被导出了。
与此同时,eBPF 也在不断发展壮大,但它目前依旧不能取代传统内核模块,例如开发硬件驱动,或是实现某些需要挂起内核操作、等待用户态程序返回判定结果的安全功能。
这让我产生了一个疑问:既然传统内核模块存在兼容性问题,为什么不能在发展 eBPF 的同时,进一步完善传统内核模块的兼容机制?
关于滥用
禁止导出 kallsyms_lookup_name(),确实提高了第三方模块访问内部符号的门槛,但对于真正需要访问这些符号的人来说,依旧可以通过其他方式定位目标。
由此产生的结果就是,滥用并未被彻底杜绝,但原本可以直接通过导出函数完成的操作,现在却需要借助更多间接手段。
例如 Linux 5.7 的这一改动就曾影响了 LTTng。
我理解内核维护者不希望第三方驱动依赖内部实现,但取消一个查询入口并不等于取消了需求。
当然,能够找到一个符号也不能等于可以随意调用它,两者并不冲突。
关于适配复杂度
eBPF 确实解决了一部分问题,但无法覆盖所有场景。
对于不在主线内核中的驱动开发者来说,即使项目完全开源,也仍然需要面对符号 CRC、结构体变化、接口删除以及不同发行版之间的差异。
虽然我们已经拥有了两套能力不同的内核扩展机制,却依旧没有为传统外部模块提供一套通用的跨版本兼容方案。
另一种思路
既然内核维护者不愿意保证所有内部符号长期稳定,那么能不能在现有的基础上只保证一套最基础的模块加载协议和一个功能更加完善的符号查询接口?
例如,开发者查询符号时,可以返回更多元数据:
- 符号地址
- 符号的类型及 ABI 版本信息
- 函数原型或类型哈希
- 符号所属模块及其生命周期信息
- 导出类型、访问限制和可用能力
至于未导出的内部符号,也可以考虑引入更细粒度的访问授权,而不是一律禁止。
其中的类型信息可以尽可能通过构建工具自动生成,而行为兼容性信息则需要明确的版本约定,以减少维护者的管理负担。
至于具体如何使用则是交由驱动开发者自行决定。如此一来,内核依旧不必为绝大部分内部函数提供长期稳定的 ABI 承诺。
成本考量
这种设计自然也有成本。
稳定的基础接口本身需要维护,符号的语义变化也不可能全部依靠一个版本号解决。尤其对于未导出的内部符号,如何划定访问权限仍然是一个问题。
但这些问题至少可以再去讨论,而不是把“不保证稳定内核 ABI”作为终点。如果某个基础接口确实需要发生不兼容的变化,在引入新版本的同时,为旧接口设定合理的支持周期即可。
Red Hat 和 Android GKI 已经在不同范围内实践了稳定内核 ABI。这也说明,在一定条件下维护稳定是可行的。
结语
如果我们可以让内核自由演进,同时让外部驱动主动识别并适配变化,何乐而不为呢?
浙公网安备 33010602011771号