Linux 内核的整体结构非常庞大,其包含的组件也非常多。我们怎样把需要的部 分都包含在内核中呢? 一种方法是把所有需要的功能都编译到 Linux 内核。
这会导致两个问题,一是生 成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编 译内核。 有没有一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需 要被使用的时候,其对应的代码可被动态地加载到内核中呢?
Linux 提供了这样的一种机制,这种机制被称为模块(Module),可以实现以上效 果。模块具有以下特点。
1-模块本身不被编译入内核映像,从而控制了内核的大小。
2-模块一旦被加载,它就和内核中的其他部分完全一样。
为了使读者对模块建立初步的感性认识,我们先来看一个最简单的内核模块 “Hello World”。
这个最简单的内核模块只包含内核模块加载函数、卸载函数和对 Dual BSD/GPL 许可权限的声明以及一些描述信息。编译它会产生 hello.ko 目标文件,通过“insmod./hello.ko”命令可以加载它,通过“rmmod hello”命令可以卸载它,加载时 输出“Hello World enter”,卸载时输出“Hello World exit”。
内核模块中用于输出的函数是内核空间的printk(),而非用户空间的print(),printk()和print()用法相似,但前者可定义输出级别。printk()可作为一种最基本的内核调试 手段。
在 Linux 系统中,使用 lsmod 命令可以获得系统中加载了的所有模块以及模块间 的依赖关系,例如:
lsmod 命令实际上读取并分析/proc/modules 文件,与上述 lsmod 命令结果对应的 /proc/modules 文件如下:
内核中已加载模块的信息也存在于/sys/module 目录下,加载 hello.ko 后,内核中 将包含/sys/module/hello 目录,该目录下又包含一个 refcnt 文件和一个 sections 目录, 在/sys/module/hello 目录下运行“tree –a”得到如下目录树:
modprobe 命令比 insmod 命令要强大,它在加载某模块时会同时加载该模块所依赖的其他模块。使用 modprobe 命令加载的模块若以“modprobe -r filename”的方式卸载将同 时卸载其依赖的模块。
使用 modinfo <模块名>命令可以获得模块的信息,包括模块的作者、模块的说明、 模块所支持的参数以及 vermagic,如下所示:
一个 Linux 内核模块主要由以下几个部分组成。
1-模块加载函数(必须)。 当通过 insmod 或 modprobe 命令加载内核模块时,模块的加载函数会自动被内核 执行,完成本模块的相关初始化工作。
2- 模块卸载函数(必须)。 当通过 rmmod 命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模 块加载函数相反的功能。
3-模块许可证声明(必须)。 模块许可证(LICENSE)声明描述内核模块的许可权限,如果不声明 LICENSE, 模块被加载时,将收到内核被污染 (kernel tainted)的警告。 在 Linux 2.6 内核中,可接受的 LICENSE 包括“GPL”、“GPL v2”、“GPL and additional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”。 大多数情况下,内核模块应遵循 GPL 兼容许可权。Linux 2.6 内核模块最常见的 是以 MODULE_LICENSE( "Dual BSD/GPL" ) 语句声明模块采用 BSD/GPL 双 LICENSE。
4- 模块参数(可选)。 模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局 变量。
5- 模块导出符号(可选)。 内核模块可以导出符号(symbol,对应于函数或变量),这样其他模块可以使用本 模块中的变量或函数。
6- 模块作者等信息声明(可选)