1、Linux提供的模块机制使得不需要对整个内核进行重新编译的情况下,内核动态载入和移除模块。模块一旦链接到内核,它的作用和静态链接的内核目标代码完全等价。为了让内核模块能够访问所有内核资源,内核必须维护符号表,并在装入和卸载模块时修改符号表。
2、编写一个最简单的内核模块
//hello.c
#include <linux/module.h>
#include <linux/kernel.h>
int init_hello(void)
{
printk("***************Start***************\n");
printk("Start of hello world module!\n");
return 0;
}
void exit_hello(void)
{
printk("***************End***************\n");
printk(" End of hello world module!\n");
}
//该宏会在模块的目标代码中增加一个特殊的段,用于说明初始化函数所在的位置。
module_init(init_hello_module);
module_exit(exit_hello_module);
//Makefile
//定义变量
obj-m := helloworld.o
CC = gcc
//得到主机内核源码目录
KERNELDIR ?= /usr/src/linux-headers-$(shell uname -r)
PWD := $(shell pwd)
all: modules
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
//-C表示进入$(KERNELDIR)目录执行Makefile
//M是内核根目录下Makefile使用的变量,指定编译模块所在目录
clean:
rm -rf *.o *~ core .depend *.symvers .*.cmd *.ko *.mod.c .tmp_versions $(TARGET)
$make 或make all编译生成hello.ko
//加载模块
$sudo insmod hello.ko
//查看
$lsmod | grep hello
//卸载
$remod hello.ko
3、模块只能使用内核空间定义的那些资源受限的函数。内核符号表是一个用来存放所有模块可以访问的那些符号以及相应地址的特殊的表。可以通过/proc/kallsyms查看。在模块编程中,可以利用符号名称从这个文件中检索出该符号在内存的地址,然后直接对该地址内存访问而获得内核数据。
insmod完成的下工作
- 从命令行读入要载入的模块名
- 确定模块代码所在文件的位置(用户空间)
- 计算存放模块代码、模块名和module对象所需要的内存大小
- 执行系统调用create_module(),向它传递新模块的名称和大小
- 反复调用query_module()系统调用来获得所有已连接的模块名,和内核符号表及所有已经链接到内核的模块的符号表
- 根据符号表和 create_module()返回的地址重新定位该模块文件中所包含的文件对象代码。(某些函数指针)
- 在用户空间分配一个内存区,把module对象、模块名以及为正在运行的内核所重定位的模块代码拷贝到这个内存。其中module对象中的init域指向这个模块的init_module函数重新分配到的地址;cleanup域指向cleanup_module()函数所重新分配的地址。
- 调用sys_init_module(),向它传递上面所创建的用户态内存区的地址
- 释放用户态内存,结束整个过程
模块实用程序modutils
modutils-x.y.z.tar.gz源码安装后会在/sbin/目录下有insmod、rmmod、ksyms、lsmod、modprobe等实用程序
ismod 使用时会自动运行我们在init_module()函数中定义的函数
rmmod 使用时会自动运行我们在cleanup_module()函数中定义的函数
lsmod 查看当前系统正在使用的模块信息,等价于cat /proc/modules
modprobe 自动根据模块之间的依赖关系插入模块程序
kmod 是运行在内核空间的进程,可以在内核空间直接运行modprobe简化整个流程(/kernel/kmod.c)
内核模块和系统交互的主要方式为:/proc文件系统和设备驱动程序。文件系统是操作系统用于明确磁盘或分区上的文件的方法和数据结构,即在磁盘上组织文件的方法。
模块所使用的变量或函数的三个来源:
- 本身在模块中定义的
- 别的模块通过导出操作
- 内核提供的
一般模块中定义的所有全局变量和函数都会被导出到内核符号表。也可以用宏EXPORT_SYSBOL选定要导出的变量或函数。
EXPORT_SYSBOL(myval);
4、数据结构
每个模块信息被描述成这样一个moudule对象,所有对象由链表串在一起,module下面还申请了一部分空间给module使用。
struct module
{
unsigned long size_of_struct; /* 模块大小sizeof(module) */
struct module *next;
const char *name;
unsigned long size;//module大小+下面申请使用的空间大小
union
{
atomic_t usecount;//模块引用计数器
long pad;
} uc; /* Needs to keep its size - so says rth */
unsigned long flags; /* 模块当前状态AUTOCLEAN et al */
unsigned nsyms;//模块定义的符号个数
unsigned ndeps;//引用的模块的链表中节点个数
struct module_symbol *syms;//指向符号表
struct module_ref *deps;//该模块引用的模块的链表
struct module_ref *refs;//指向引用该模块的链表
int (*init)(void);
void (*cleanup)(void);
//中断向量表的入口和结束位置
const struct exception_table_entry *ex_table_start;
const struct exception_table_entry *ex_table_end;
#ifdef __alpha__
unsigned long gp;
#endif
/* 维持一些和模块相关的数据,如配置信息,下次加载时不需要重新设置 */
const struct module_persist *persist_start;
const struct module_persist *persist_end;
int (*can_unload)(void);//用户自定义的模块卸载函数的指针
int runsize; /* In modutils, not currently used */
const char *kallsyms_start; /* All symbols for kernel debugging */
const char *kallsyms_end;
/* 所有和体系结构相关数据所占内存的其实和结束地址*/
const char *archdata_start;
const char *archdata_end;
const char *kernel_data; /* Reserved for kernel internal use */
};
//每个符号地址及其对应名称
struct module_symbol
{
unsigned long value;
const char *name;
};
//把相互依赖的各模块串接在一起
struct module_ref
{
struct module *dep; /* "parent" pointer */
struct module *ref; /* "child" pointer */
struct module_ref *next_ref;
};
//系统调用sys_get_kernel_syms函数用到的结构,
//用于将内核符号拷贝到用户空间的kernel_sym{}结构中,
//从而可以在用户空间存放内核的模块信息,这里直接把名称存在数组中
struct kernel_sym
{
unsigned long value;
char name[60]; /* should have been 64-sizeof(long); oh well */
};
5、启动时内核模块初始化
/*
*内核启动时调用,导出内核符号对module{}结构赋值,针对所有已存在模块
*/
void __init init_modules(void)
{
kernel_module.nsyms = __stop___ksymtab - __start___ksymtab;
arch_init_modules(&kernel_module);
}
6、创建一个模块
创建一个名称为name大小为size的模块节点,放在内核的模块链表中。返回创建的模块地址。
asmlinkage unsigned long
sys_create_module(const char *name_user, size_t size)
{
char *name;
long namelen, error;
struct module *mod;
unsigned long flags;
if (!capable(CAP_SYS_MODULE))
return -EPERM;
lock_kernel();
//为名字在内核空间申请内存,并拷贝名字,下面申请该模块的module{}
if ((namelen = get_mod_name(name_user, &name)) < 0) {
error = namelen;
goto err0;
}
if (size < sizeof(struct module)+namelen) {
error = -EINVAL;
goto err1;
}
if (find_module(name) != NULL) {
error = -EEXIST;
goto err1;
}
//module_map类似于malloc,申请内核空间存放模块数据
if ((mod = (struct module *)module_map(size)) == NULL) {
error = -ENOMEM;
goto err1;
}
//初始化module{}成员
memset(mod, 0, sizeof(*mod));
mod->size_of_struct = sizeof(*mod);
mod->name = (char *)(mod + 1);
mod->size = size;
memcpy((char*)(mod+1), name, namelen+1);
put_mod_name(name);
//把模块插入原来链表的头部
spin_lock_irqsave(&modlist_lock, flags);
mod->next = module_list;
module_list = mod; /* link it in */
spin_unlock_irqrestore(&modlist_lock, flags);
error = (long) mod;
goto err0;
err1:
put_mod_name(name);
err0:
unlock_kernel();
return error;
}
7、初始化一个模块
- 根据传入的模块名在内核空间的module_list查找该模块
- 把模块内容从用户空间拷贝到内核空间里的模块
- 修改模块引用的链表
- 对模块的module{}结构的一些成员进行赋值
sys_init_module(const char *name_user, struct module *mod_user)
{
......
if (!capable(CAP_SYS_MODULE))//权限检查
lock_kernel();
//得到内核中存放name的地址
if ((namelen = get_mod_name(name_user, &name)) < 0)
//根据名字在module_list中查找这个模块是否存在
if ((mod = find_module(name)) == NULL)
//判断模块大小是否符合条件,允许一定的溢出
if ((error = get_user(mod_user_size, &mod_user->size_of_struct)) != 0)
goto err1;
if (mod_user_size < (unsigned long)&((struct module *)0L)->persist_start
|| mod_user_size > sizeof(struct module) + 16*sizeof(void*)) {
printk(KERN_ERR "init_module: Invalid module header size.\n"
KERN_ERR "A new version of the modutils is likely "
"needed.\n");
error = -EINVAL;
goto err1;
//申请空间保存module名称(此时名称并没和module在内存中连在一起)
mod_tmp = *mod;
name_tmp = kmalloc(strlen(mod->name) + 1, GFP_KERNEL); /* Where's kstrdup()? */
strcpy(name_tmp, mod->name);
//把用户空间module拷贝到内核空间的module
error = copy_from_user(mod, mod_user, mod_user_size);
.....
判断各成员是否符合要求
.....
//把用户空间的mod_user中剩余部分(module下面的附属数据部分)拷贝到内核空间的mod
if (copy_from_user((char *)mod+mod_user_size,
(char *)mod_user+mod_user_size,
mod->size-mod_user_size)) {
error = -EFAULT;
goto err3;
}
1. 遍历mod模块的deps链表,检查在mod的deps链表中的每一个需要引用到的模块都应该已经在系统链表中建立。如果一个模块同时引用多个其他模块,那么这些引用的关系module_ref{}结构在内存中是顺序排列的。
2. 遍历mod的deps成员链表,修改他们的ref指针指向该mod
3. 设置状态标识flags,表示初始化已经结束,进入运行状态
}
8、删除一个模块
- 检查是否允许用户删除该模块
- 调用find_module()在module_list链表里查找相应的模块对象
- 检查refs域是否为空,通过flags判断该对象是否可以删除,可以则用free_module来删除
//删除名字为name_user的模块,若name_user为NULL则删除当前内核中所有未使用的模块
sys_delete_module(const char *name_user);
9、获取内核符号表的内容
//参数为NULL,返回当前系统中内核符号的个数,
//否则将插入到内核的模块的数据填充到用户空间的table结构
//模块名本身也算一个符号
asmlinkage long sys_get_kernel_syms(struct kernel_sym *table);