第二章:模块的编译与运行-11 Preliminaries
In continuation of the previous text:第二章:模块的编译与运行-10 The Kernel Symbol Table, let's GO ahead.
Preliminaries
We are getting closer to looking at some actual module code. But first, we need to look at some other things that need to appear in your module source files. The kernel is a unique environment, and it imposes its own requirements on code that would interface with it.
我们即将开始查看实际的模块代码,但在此之前,需要先了解模块源文件中必须包含的其他内容。内核是一个特殊的运行环境,它对与之交互的代码有自己的要求。
Most kernel code ends up including a fairly large number of header files to get definitions of functions, data types, and variables. We’ll examine these files as we come to them, but there are a few that are specific to modules, and must appear in every loadable module. Thus, just about all module code has the following:
大多数内核代码都需要包含大量头文件,以获取函数、数据类型和变量的定义。我们会在后续涉及相关内容时介绍这些头文件,但有几个头文件是模块专用的,且每个可加载模块都必须包含它们。因此,几乎所有模块代码都包含以下内容:
#include <linux/module.h>
#include <linux/init.h>
module.h contains a great many definitions of symbols and functions needed by loadable modules. You need init.h to specify your initialization and cleanup functions, as we saw in the “hello world” example above, and which we revisit in the next section. Most modules also include moduleparam.h to enable the passing of parameters to the module at load time; we will get to that shortly.
module.h
包含了可加载模块所需的大量符号和函数定义。而 init.h
则用于声明模块的初始化和清理函数 —— 正如我们在前面的 “hello world” 示例中所见,下一节我们还会重新讨论这两个函数。大多数模块还会包含 moduleparam.h
,以支持在加载时向模块传递参数,这一点我们很快就会介绍。
It is not strictly necessary, but your module really should specify which license applies to its code. Doing so is just a matter of including a MODULE_LICENSE line:
虽然并非严格要求,但你的模块确实应该指定其代码所遵循的许可证。要做到这一点,只需添加一行 MODULE_LICENSE
声明即可:
MODULE_LICENSE("GPL");
The specific licenses recognized by the kernel are “GPL” (for any version of the GNU General Public License), “GPL v2” (for GPL version two only), “GPL and additional rights,” “Dual BSD/GPL,” “Dual MPL/GPL,” and “Proprietary.” Unless your module is explicitly marked as being under a free license recognized by the kernel, it is assumed to be proprietary, and the kernel is “tainted” when the module is loaded. As we mentioned in the section “License Terms” in Chapter 1, kernel developers tend to be unenthusiastic about helping users who experience problems after loading proprietary modules.
内核认可的具体许可证包括 “GPL”(适用于 GNU 通用公共许可证的任意版本)、“GPL v2”(仅适用于 GPL 版本 2)、“GPL and additional rights”(GPL 及附加权限)、“Dual BSD/GPL”(BSD 与 GPL 双重许可)、“Dual MPL/GPL”(MPL 与 GPL 双重许可)以及 “Proprietary”(专有许可)。除非你的模块明确标记为遵循内核认可的自由许可证,否则内核会默认其为专有模块,且加载该模块后会导致内核 “被污染”(tainted)。正如我们在第 1 章 “许可条款” 部分提到的,对于加载专有模块后遇到问题的用户,内核开发者通常不太愿意提供帮助。
Other descriptive definitions that can be contained within a module include MODULE_AUTHOR (stating who wrote the module), MODULE_DESCRIPTION (a human-readable statement of what the module does), MODULE_VERSION (for a code revision number; see the comments in <linux/module.h> for the conventions to use in creating version strings), MODULE_ALIAS (another name by which this module can be known), and MODULE_DEVICE_TABLE (to tell user space about which devices the module supports). We’ll discuss MODULE_ALIAS in Chapter 11 and MODULE_DEVICE_TABLE in Chapter 12.
The various MODULE_ declarations can appear anywhere within your source file outside of a function. A relatively recent convention in kernel code, however, is to put these declarations at the end of the file.
模块中还可以包含其他描述性声明,包括:
MODULE_AUTHOR
:声明模块的开发者;MODULE_DESCRIPTION
:用人类可读的文字描述模块的功能;MODULE_VERSION
:指定模块的代码修订版本(关于版本字符串的命名规范,可参考<linux/module.h>
中的注释);MODULE_ALIAS
:指定模块的别名(其他可用于引用该模块的名称);MODULE_DEVICE_TABLE
:告知用户空间该模块支持哪些设备。
我们会在第 11 章讨论 MODULE_ALIAS
,在第 12 章讨论 MODULE_DEVICE_TABLE
。上述各类 MODULE_*
声明可放在源文件中任意函数之外的位置,但内核代码中一个较新的惯例是将它们统一放在文件末尾。
补充说明:
-
核心头文件的作用
-
<linux/module.h>
:模块开发的 “基础库”,包含EXPORT_SYMBOL
、MODULE_LICENSE
等核心宏的定义,以及模块加载 / 卸载相关的函数声明,是所有模块的必包含头文件。 -
<linux/init.h>
:主要用于声明模块的初始化函数(__init
宏)和清理函数(__exit
宏),若模块未包含此文件,编译器会报错无法识别这两个宏。 -
<linux/moduleparam.h>
:提供模块参数相关的宏(如module_param
),若模块不需要加载时传参,可省略此头文件。
-
-
内核 “被污染”(tainted)的影响
-
当加载专有模块(未声明 GPL 等自由许可证)时,内核会设置
TAINT_PROPRIETARY_MODULE
标记,此时内核日志会出现 “kernel tainted” 提示。 -
被污染的内核在出现崩溃(oops)或其他故障时,生成的调试信息可能不被内核社区认可 —— 因为专有模块的代码不可见,开发者无法判断故障是否由专有模块导致,因此通常会拒绝提供技术支持。
-
-
核心头文件的作用
这些声明本质上是内核定义的宏,会展开为存储模块元数据的全局变量,这些变量会被编译到模块的 ELF 特殊段(如
.modinfo
段)中。用户可通过modinfo
命令查看这些元数据,例如modinfo hello.ko
会显示模块的许可证、作者、版本等信息,方便管理和调试。 -
声明位置的惯例意义
将
MODULE_*
声明放在文件末尾,是为了让代码结构更清晰 —— 源文件开头放头文件和函数声明,中间放函数实现,末尾放模块元数据,符合 “功能代码在前,描述信息在后” 的阅读逻辑,也便于其他开发者快速查找核心功能代码。