目录
7.3 插入和删除模块
7.3.1 模块的表示
7.3.2 依赖关系和引用
7.3.3 模块的二进制结构
7.3.4 插入模块
7.3.5 移除模块
本专栏文章将有70篇左右,欢迎+关注,查看后续文章。
7.3 插入和删除模块
两个系统调用:
init_module:插入模块。
delete_module:删除模块。
request_module 函数:非系统调用
作用:
1. 内核中加载一个模块。
2. 实现热插拔。
7.3.1 模块的表示
每个模块都有一个 struct module 实例:
struct module {
enum module_state state; //模块的装载状态
struct list_head list; //连接内核所有模块
struct kernel_symbol *syms; //该模块导出的符号及其内存地址。
unsigned int num_syms;
struct module_ref ref;
//该模块的引用计数。
module_put 时减1。
try_module_get 加1。
unsigned int init_size, core_size;
// 模块中__init 类型的符号大小,其余符号大小。
unsigned int init_text_size, core_text_size;
void (*init)(void); // 模块初始化时调用。
void (*exit)(void);
}
struct kernel_symbol {
unsigned long value; // 内存地址。
const char *name; // 符号。
}
# cat /proc/kallsyms:
查看导出的所有符号。
当模块污染内核,原因:
许可证问题。
7.3.2 依赖关系和引用
为管理模块间的依赖关系,引入如下结构体。
struct module_use {
struct list_head source_list; // 依赖本模块的模块。
struct list_head target_list; // 本模块所依赖的模块。
struct module *source, *target;
};
如果模块 A 依赖了模块 B 中函数,则调用:
int add_module_usage(struct module *a, struct module *b)
{
struct module_use *use;
use = kmalloc(sizeof(*use), GFP_ATOMIC);
use->source = a;
use->target = b;
list_add(&use->source_list, &b->source_list);
list_add(&use->target_list, &a->target_list);
}
already_uses:判断模块A是否使用了模块B。
int already_uses(struct module *a, struct module *b)
{
struct module_use *use;
list_for_each_entry(use, &b->source_list, source_list)
{
if (use->source == a) {
return 1;
}
}
}
7.3.3 模块的二进制结构
一个模块文件的格式是ELF,其特有的段有:
1. __ksymtab段:保存内核符号表。
2. __kcrctab段:保存导出函数的校验和。
3. __param段:保存模块参数。
4. __ex_table段:保存新增内核异常表。
5. .modinfo段:保存依赖模块名称、author、描述、许可证、参数列表等。
(modinfo命令:读取该段)
6. .init.data,.exit.data段:
保存__init和__exit类型的数据。
7. .init.text,.exit_text段:
保存__init和__exit类型的函数。
1. 初始化和清理函数
module_init(nat_init_module);
module_exit(nat_cleanup_module);
即在.init.data,.init.text,.exit.data段中定义模块的init,exit函数。
2. 导出符号
两个宏:
EXPORT_SYMBOL:导出一般符号。
EXPORT_SYMBOL_GPL:导出GPL兼容代码的符号。
EXPORT_SYMBOL
原理:将相应符号放置在模块二进制文件的一个段中。
举例:将变量 a 导出到文件的 .my_test.data 段中。
static int a __attribute__((section(".my_test.data")));
EXPORT_SYMBOL_PREFIX
作用:给导出符号分配一个前缀。
使用场景:某些特定体系架构需要。
3. 模块的信息
MODULE_INFO(tag, info):
作用:生成一个模块的相关信息。
原理:使用__attribute__((section(".modinfo"))),将模块的信息保存在 .modinfo 段中。
#define MODULE_LICENSE(_license) MODULE_INFO(license, _license)
#define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias)
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
宏VERMAGIC_STRING:
定义了安装模块时,需要检查的信息,若和内核不一致,则不可安装模块。
1. SMP配置。
2. 内核抢占配置
3. 模块版本。
4. 特定于体系架构的常数。
#define VERMAGIC_STRING \
MODULE_VERMAGIC_SMP \
MODULE_VERMAGIC_PREEMPT \
MODULE_VERMAGIC_MODVERSIONS \
MODULE_ARCH_VERMAGIC \
7.3.4 插入模块
对应系统调用:
init_module
load_module 内容:
1. 使用copy_from_user,将模块加载到内核空间。
2. 重写函数和变量的地址。
3. 在内存中组织数据。
使用 layout_sections,判断模块的段加载到内存哪个位置。
模块的段分为两类:
core 和 init。
4. 分配内存,传输模块到内存。
5. 检查模块许可证。
6. 通过reslove_symbol,解决引用重定位。
7.3.5 移除模块
对应系统调用:
delete_module