提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
开发环境:ubuntu18.04 硬件开发板:imx6ull 内核:Linux-4.9.88
一、内核模块概念
概念性知识不做科普(复制、粘贴),大家可以参照野火的教程可以理解一些概念,对代码为什么这样写有帮助,知其所以然! 需要了解微内核和宏内核的区分? 为什么需要引入内核模块? 编写Linux设备驱动和内核模块有什么关系?
二、内核模块的文件格式
.ko 文件在数据组织形式上是ELF(Excutable And Linking Format) 格式,是一种普通的可重定位目标文件。这类文件包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库也可以归为这一类。
三、内核模块的过程分析
通俗的理解:编译出来的.ko文件是经过内核处理过的代码,所以在我们执行加载的过程中内核会有办法去利用.ko文件。
内核相关的文件一般都在kernel目录下
D:\imx6ull\imx-linux4.9.88\kernel\module.c
2. 内核模块加载过程insmod
我们在终端输入insmod的时候,通过文件系统将.ko 模块读到用户空间的一块内存中,然后执行系统调用sys_init_module() 解析模块,,将内核在vmalloc 区分配与ko 文件大小相同的内存来暂存.ko 文件,暂存好之后解析ko 文件。
SYSCALL_DEFINE3(init_module, void __user *, umod,
unsigned long, len, const char __user *, uargs)
{
int err;
struct load_info info = { };
err = may_init_module();
pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",umod, len, uargs);
err = copy_module_from_user(umod, len, &info);
return load_module(&info, uargs, 0);
}
对.ko文件的解析是一个复杂的工作,load_module 包括内核模块的重定位和加载等许多操作,.ko文件中的各个section 的init 段和core 段分配到最终的运行地址,然后开始执行初始化,.ko加载完成后init段会被释放,只保留core段来运行。
2. 内核模块卸载过程rmmod
在终端输入rmod指令,最终在系统内核中需要调用sys_delete_module进行实现。
SYSCALL_DEFINE2(delete_module, const char __user *, name_user,unsigned int, flags)
{
struct module *mod;
char name[MODULE_NAME_LEN];
int ret, forced = 0;
if (strncpy_from_user(name, name_user, MODULE_NAME_LEN-1) < 0)
return -EFAULT;
name[MODULE_NAME_LEN-1] = '\0';
if (mutex_lock_interruptible(&module_mutex) != 0)
return -EINTR;
mod = find_module(name);
if (!list_empty(&mod->source_list))
if (mod->state != MODULE_STATE_LIVE)
if (mod->init && !mod->exit)
ret = try_stop_module(mod, flags, &forced);
mutex_unlock(&module_mutex);
if (mod->exit != NULL)
mod->exit();
blocking_notifier_call_chain(&module_notify_list,MODULE_STATE_GOING, mod);
klp_module_going(mod);
ftrace_release_mod(mod);
async_synchronize_full();
strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module));
free_module(mod);
return 0;
out:
mutex_unlock(&module_mutex);
return ret;
}
3. 内核符号的导出 EXPORT_SYMBOL
符号指的就是内核模块中使用EXPORT_SYMBOL 声明的函数和变量 当模块被装进内核,所有导出的符号都会记录在公共的内核符号表中
EXPORT_SYMBOL(name)
EXPORT_SYMBOL_GPL(name)
四、内核模块的实验
1.头文件的引用
#include <linux/module.h>:包含了内核加载module_init()/卸载module_exit() 函数和内核模块信息相关函数的声明
#include <linux/init.h>:包含一些内核模块相关节区的宏定义(前面可知一般都要用到)
#include <linux/kernel.h>:包含内核提供的各种函数,如printk
注意: 内核模块运行的过程中,他不能依赖于C 库函数,因此用不了printf 函数,需要使用单独的打印输出函数printk。
2.模块加载函数
当通过insmod 或modprobe 命令加载内核模块时,模块的加载函数module_init就会自动被内核执行,完成本模块相关的初始化工作。
static int __init demo_module_init(void)
{
printk(KERN_INFO "demo module init !!!");
return 0;
}
module_init(demo_module_init);
3.模块卸载函数
当执行rmmod 命令卸载模块时,模块卸载函数module_exit就会自动被内核执行,完成相关清理工作。
static void __exit demo_module_exit(void)
{
printk(KERN_INFO "demo module exit !!!");
}
module_exit(demo_module_exit);
注意:为什么需要加__init ? 在头文件/linux/init.h中有对应的宏定义:
D:\imx6ull\imx-linux4.9.88\include\linux\init.h
__init修饰函数 ,__initdata修饰变量
带init表示将可执行文件放到init区(变量需要加__initdata),该区只能用于模块的初始化阶段,初始化完毕之后就会被释放。
4.附加信息
MODULE_LICENSE() | GPL许可证(必须有) |
---|
MODULE_AUTHOR() | 作者 | MODULE_DESCRIPTION() | 模块描述 | MODULE_ALIAS() | 别名 |
3.Makefile文件
KERNEL_DIR := /home/book/100ask_imx6ull-sdk/Linux-4.9.88
arch = arm
CROSS_COMPILE = arm-linux-gnueabihf-
obj-m += demo_module.o
all:
make -C $(KERNEL_DIR) M=$(CURDIR) modules
clean:
make -C $(KERNEL_DIR) M=$(CURDIR) clean
rm -rf modules.order
-C 表示改变目录,转到内核的目录的Makefile去执行,M=(CURDIR)表示又返回当前的目录来执行当前的Makefile和内核一起编译。
五、验证
这里把常用到的命令列举一下
insmod <xxx.ko> 安装
rmmod <xxx.ko> 卸载
lsmod 列举
cat /proc/modules lsmod读取的也是/proc/modules
modprobe <xxx.ko> 检查依赖并安装
modprobe -r <xxx.ko> 检查依赖并卸载
modinfo <xxx> 模块信息
补充内容
1.vmalloc 与malloc ?
2.strncpy_from_user 用于短的字符串拷贝
3. 内核信息打印
cat /proc/sys/kernel/printk
dmesg
|