符号命名空间

以下文档描述了如何使用符号命名空间来构建通过 EXPORT_SYMBOL() 系列宏导出的内核符号的导出表面。

1. 介绍

引入符号命名空间是为了构建内核 API 的导出表面。它允许子系统维护者将他们导出的符号划分为单独的命名空间。这对于文档目的(想想 SUBSYSTEM_DEBUG 命名空间)以及限制一组符号在内核其他部分中的可用性很有用。截至今天,使用导出到命名空间中的符号的模块,需要导入该命名空间。否则,内核将根据其配置,拒绝加载模块或警告缺少导入。

2. 如何定义符号命名空间

可以使用不同的方法将符号导出到命名空间中。所有这些方法都在改变 EXPORT_SYMBOL 及其友元被检测以创建 ksymtab 条目的方式。

2.1 使用 EXPORT_SYMBOL 宏

除了允许将内核符号导出到内核符号表的宏 EXPORT_SYMBOL() 和 EXPORT_SYMBOL_GPL() 之外,还有一些变体可用于将符号导出到特定的命名空间:EXPORT_SYMBOL_NS() 和 EXPORT_SYMBOL_NS_GPL()。它们需要一个额外的参数:命名空间。请注意,由于宏展开,该参数需要是一个预处理器符号。例如,要将符号 usb_stor_suspend 导出到命名空间 USB_STORAGE,请使用

EXPORT_SYMBOL_NS(usb_stor_suspend, "USB_STORAGE");

相应的 ksymtab 条目结构 kernel_symbol 将会相应地设置成员 namespace。未导出到命名空间的符号将引用 NULL。如果没有定义,则没有默认命名空间。modpost 和 kernel/module/main.c 分别在构建时或模块加载时使用命名空间。

2.2 使用 DEFAULT_SYMBOL_NAMESPACE 定义

为子系统的所有符号定义命名空间可能非常冗长,并且可能变得难以维护。因此,提供了一个默认定义 (DEFAULT_SYMBOL_NAMESPACE),如果设置了该定义,它将成为所有未指定命名空间的 EXPORT_SYMBOL() 和 EXPORT_SYMBOL_GPL() 宏展开的默认值。

有多种指定此定义的方法,这取决于子系统和维护者的偏好。第一个选项是在子系统的 Makefile 中定义默认命名空间。例如,要将 usb-common 中定义的所有符号导出到 USB_COMMON 命名空间,请在 drivers/usb/common/Makefile 中添加如下一行

ccflags-y += -DDEFAULT_SYMBOL_NAMESPACE='"USB_COMMON"'

这将影响所有 EXPORT_SYMBOL() 和 EXPORT_SYMBOL_GPL() 语句。在存在此定义的情况下,使用 EXPORT_SYMBOL_NS() 导出的符号仍将导出到作为命名空间参数传递的命名空间中,因为此参数优先于默认符号命名空间。

定义默认命名空间的第二个选项是在编译单元中直接作为预处理器语句。上面的示例将读取

#undef  DEFAULT_SYMBOL_NAMESPACE
#define DEFAULT_SYMBOL_NAMESPACE "USB_COMMON"

在任何 EXPORT_SYMBOL 宏使用之前,在相应的编译单元内。

3. 如何使用导出到命名空间中的符号

为了使用导出到命名空间中的符号,内核模块需要显式导入这些命名空间。否则,内核可能会拒绝加载模块。模块代码需要为它从中使用的符号的命名空间使用宏 MODULE_IMPORT_NS。例如,使用上述 usb_stor_suspend 符号的模块,需要使用如下语句导入 USB_STORAGE 命名空间

MODULE_IMPORT_NS("USB_STORAGE");

这将为每个导入的命名空间在模块中创建一个 modinfo 标签。这会产生一个副作用,即可以使用 modinfo 检查模块的导入命名空间

$ modinfo drivers/usb/storage/ums-karma.ko
[...]
import_ns:      USB_STORAGE
[...]

建议将 MODULE_IMPORT_NS() 语句添加到其他模块元数据定义(如 MODULE_AUTHOR() 或 MODULE_LICENSE())附近。有关自动创建缺失导入语句的方法,请参阅第 5 节。

4. 加载使用命名空间符号的模块

在模块加载时(例如,insmod),内核将检查模块引用的每个符号的可用性以及它可能导出到的命名空间是否已由模块导入。内核的默认行为是拒绝加载未指定足够导入的模块。将记录一个错误,并且加载将失败并显示 EINVAL。为了允许加载不满足此先决条件的模块,可以使用配置选项:设置 MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS=y 将启用加载,无论如何,但会发出警告。

5. 自动创建 MODULE_IMPORT_NS 语句

在构建时可以轻松检测到缺失的命名空间导入。事实上,如果模块使用了来自命名空间的符号而没有导入它,则 modpost 将发出警告。MODULE_IMPORT_NS() 语句通常会添加到确定的位置(与其他模块元数据一起)。为了使模块作者(和子系统维护者)的生活更轻松,提供了一个脚本和 make 目标来修复缺失的导入。可以使用以下命令修复缺失的导入

$ make nsdeps

模块作者的典型场景是

- write code that depends on a symbol from a not imported namespace
- ``make``
- notice the warning of modpost telling about a missing import
- run ``make nsdeps`` to add the import to the correct code location

对于引入命名空间的子系统维护者,步骤非常相似。同样,make nsdeps 最终会为树内模块添加缺失的命名空间导入

- move or add symbols to a namespace (e.g. with EXPORT_SYMBOL_NS())
- ``make`` (preferably with an allmodconfig to cover all in-kernel
  modules)
- notice the warning of modpost telling about a missing import
- run ``make nsdeps`` to add the import to the correct code location

您也可以为外部模块构建运行 nsdeps。一个典型的用法是

$ make -C <path_to_kernel_src> M=$PWD nsdeps