模块是如何加载到内核的


要支持模块的动态加载,卸载, 在编译内核时要注意: “Lodable Module Support" 中的相应选项要选上. 比如要支持加载模块, 应该选上Enable loadable module support; 要支持卸载模块, 要选上Module unloading ; 要支持强制卸载模块, 要选上Forced module unloading!

1, When the kernel needs a feature that is not resident in the kernel, the kernel module daemon kmod (In earlier versions of linux, this was known as kerneld) execs modprobe to load the module in. modprobe is passed a string in one of two forms:
  • A module name like softdog or ppp.

  • A more generic identifier like char-major-10-30

传递给mdoprobe的参数为模块名时, 不需要加.ko的扩展名. 若传递给modprobe的是通用标志符, 那么modprobe通过查看etc/modprobe.conf知道通用标志符对应的模块名.

modprobe.conf文件是特定于发行版的, 比如我的Ubuntu中, modprobe查看/etc/modprobe.d/aliases来将通用标志符转化为模块名.

2, 注意模块也存在依赖性问题: 比如你要加载msdos.ko, 需要先加载fat.ko. modprobe查看/lib/module/version/modules.dep得知模块的依赖关系. (version = uname -r). moules.dep由$ depmod -a 命令创建.

依赖其他模块的模块称为: "stacking modules"


3, 知道了依赖关系之后, mprobe先加载prerequisites模块, 再加载模块自身. 实际上, modprobe是通过调用insmod来加载这些模块的!

有两种加载模块的方法, 以刚才的msdos.ko为例子:
$ insmod /lib/modules/version/kernel/fs/fat/fat.ko
$ insmod /lib/modules/version/kernel/fs/msdos/msdos.ko
$ modprobe msdos

modprobe和insmod的区别:
1, modprobe知道内核模块默认的存在目录(/lib/modules/version/), 而insmod不知道.
2, 调用modprobe时, 只需给出模块名(不代,ko扩展), 而insmod需要给出完整路径和模块名.
3, modprobe自动解决依赖性. 而insmod需要指定加载内核的先后顺序.
4, 由于modprobe假设要加载的模块在默认目录, 那么若要加载在默认目录之外的模块, 就要调用insmod了.

发行版将modprobe, insmod, depmod打包到一起, 称之为Linux内核模块管理工具, 针对2.4或以前的内核, 该工具名为modutils, 2.6的为module-init-tools.

通过$ lsmod 可以看到加载到内核中的模块信息 也可以查看/proc/modules文件的内容. 实际上,lsmod读命令就是通过查看/proc/modules的内容来显示模块信息的.

卸载模块

使用 $ sudo rmmod mod_name 可以卸载模块. 但内核有时候认为卸载该模块是不安全的, 此时可以使用 $ sudo rmmod -f mod_name来强制卸载模块.

这里都是介绍的加载,卸载模块的命令, 至于模块加载,卸载的原理. 参考" 模块运行环境"

准备工作


模块可以加载到当前运行的内核中, 也可以加载到另一个未运行的内核. 这里暂时只考虑将模块加载到启动的内核中.

学习模块编程, 先要重新编译内核, 为什么要编译内核的? 原因有二:

1, 我们使用的Linux发行版中的内核针对kernel.org的官方内核添加了许多补丁, 提供的内核头文件并不完整, 内核API也可能被修改了, 要学习模块编程, 最好使用官方内核编译.

2, 发行版的内核中, 一般默认的CONFIG_MODVERSIONS被设置为y.  这样你在加载模块时会由于版本问题失败, 所以应该不设置CONFIG_MODVERSIONS.

参考内核模块编程之_初窥门径


模块程序组成

模块程序设计有点类似于应用程序设计: 起码模块程序中有entry point和exit point. 并且模块程序代码位于独立的文件中.

下面看看模块程序组成.

模块程序中至少要有两个函数: 一个初始化函数, 它在模块被加载到内核中的时候被调用, 一个退出函数(clean_up), 它在模块被卸载的时候被调用.

有两种方法定义上述的两个函数, 推荐用后面的定义方法!

int init_module(void)
{
 ...
}

void cleanup_module(void)
{
 ...
}

在2.3.13版之后的内核, 可以用下面的方法来定义它们:

static int hello_start(void)
{
 ...
}

static void hello_end(void)
{
 ...
}

module_init(hello_start);
module_exit(hello_end);

1, 初始化函数和退出函数的定义.
初始化函数和退出函数应该是这样的形式:

static int funname_init(void);     /* 模块被加载时被调用 */
static void funname_exit(void);  /* 模块被卸载时被调用 */

由于不向外输出这些函数, 一般给它们加上static限定符号.

2, module_init和module_exit宏
funname_init()通过module_init()被注册为模块的entry point. 同样地, funname_exit()通过module_exit()宏被注册为模块的exit point. 注意: 如果文件被编译到静态内核映像中, 退出函数不会被执行. 这些宏扩展必须位于相应函数定义之后!

3, 版权信息
从2.4版的内核起, 可以使用一些宏来声明模块的版权信息:

MODULE_LICENSE()宏:
括号中的内容可以是下面的几种形式:
"GPL"	                    [GNU Public License v2 or later]
"GPL v2" [GNU Public License v2]
"GPL and additional rights" [GNU Public License v2 rights and more]
"Dual BSD/GPL" [GNU Public License v2 or BSD license choice]
"Dual MPL/GPL" [GNU Public License v2 or Mozilla license choice]
"Proprietary" [Non free products]
如果是MODULE_LICENSE("Proprietary"), 那么你所编写的模块不是免费的, 内核社区将其视为"污染"了内核, 不会理会相关的bug report, 而且, 不遵循GPL的模块无法调用只针对GPL的符号(参考"输出符号"的内容).

如果声明双重版权, 那么在Linux上, 它与声明为GPL的效果是一样的, 即Linux只在意GPL版权.

 另外, MODULE_DESCRIPTION() 描述模块的功能; MODULE_AUTHOR()描述模块的作者;  and MODULE_SUPPORTED_DEVICE() 声明模块所支持设备的类型.

在内核源码树的/include/linux/module.h中定义版权宏. 这些宏一般位于文件结尾.

4, 包含头文件.
在模块程序开头需要包含头文件:
#include <linux/module.h>	/* 所有的模块文件都要包含 */
#include <linux/kernel.h>	/* 若使用了优先级标志,需包含 */
#include <linux/init.h> /* 若使用了宏, 需包含 */