Linux内核模块
1、什么是内核模块?
内核模块是Linux提供的一种机制,允许在内核运行时动态加载进内核中,具有两个特点:
1)内核模块本身不编译入内核映像,有效控制缩减内核镜像大小
2)内核模块一旦被加载,他就和内核中的其他部分完全一样
2、为什么需要内核模块?
如果在内核编译时把所有的功能都编译进去,就会导致内核很大,而且要往内核中添加或删除功能时必须重新编译内核
比如在Ubuntu在通用PC平台上,预先无法知道需要什么设备,就不知道预先编译什么驱动。
3、内核模块和应用程序的区别
|
工作模式 |
工作性质 |
层次 |
权限 |
影响 |
竞态 |
运行方式 |
应用程序 |
USR模式 |
策略性 |
用户层 |
低 |
局部 |
局部 |
主动 |
内核模块 |
SVC模式 |
功能性 |
内核层 |
高 |
全局 |
全局 |
被挡 |
4、内核模块的基本构成
|——两个函数(一般需要) | |——模块初始化(加载)函数:当内核模块加载进内核的时候,做一些准备工作 | |——模块卸载函数:回收、清理资源 | |——授权(许可证声明)(必须):Linux内核受GPL(General Public License)授权约束 |——模块参数(可选):模块被加载时可以被传递给它的值,本身对应模块内的全局变量 |——模块导出符号(可选) |——模块信息说明(可选)
5、模块加载(初始化)函数
一般以 __init标识声明
函数命名规则 xxx_init xxx设备名 init功能名(初始化)
函数形式:
static ini __init xxx_init(void) { /* 初始化代码 * 返回值: 成功:0 失败:负数,绝对值是错误码 * 应用层得到的返回值是-1,错误码保存到errno(每个进程有一个); 标准化errno.h已经明确定义linux/errno.h */ }
注册方式: module_init(x); x为模块初始化函数的首地址
6、模块卸载函数
一般以 __exit标识声明
函数命名规则 xxx_exit xxx设备名 exit功能名(卸载)
static ini __exit xxx_exit(void) { /* 释放代码 */ }
注册方式: module_exit(x); x为模块卸载函数的首地址
7、模块许可证声明
MODULE_LICENSE(_license) //_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 MIT/GPL" [GNU Public License v2 or MIT license choice] //"Dual MPL/GPL" [GNU Public License v2 or Mozilla license choice]
8、模块声明与描述
在Linux内核模块中,我们可以用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分别来声明模块的作者、描述、版本、设备表和别名,例如:
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
对于USB、PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLE,表明该驱动模块支持的设备,如:
/* 对应此驱动的设备列表 */
static struct usb_device_id skel_table [ ] = { { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, { } /* 表结束 */ } }; MODULE_DEVICE_TABLE (usb, skel_table);
9、模块参数:在加载模块时,可以给模块传参
头文件 linux/moduleparam.h
A、传递普通变量
module_param(name, type, perm); 声明内核模块参数 /* name - 接收参数的变量名 type - 变量类型 Standard types are: byte, short, ushort, int, uint, long, ulong charp: a character pointer bool: a bool, values 0/1, y/n, Y/N. invbool: the above, only sense-reversed (N = true) perm - 权限 头文件 linux/stat.h #define S_IRWXUGO (S_IRWXU|S_IRWXG|S_IRWXO) #define S_IALLUGO (S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO) #define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH) #define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH) #define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH) */
范例:
int i = 0; module_param(i, int, 0644);
运行:
# insmod xxx.ko i=10
B、传递数组参数
module_param_array(name, type, nump, perm) /* 声明内核模块数组参数 name - 数组名 type - 数组成员类型 nump – 一个指向保存数组长度的整型变量的指针 perm - 权限 */
范例:
int arr[] = {1,2,3,4,5,6}; int len=0; module_param(arr, int, &len, 0644);
运行:
# insmod xxx.ko arr=1,2,3,4,5
C、传递字符串参数
module_param_string(name, string, len, perm) /* 声明内核模块字符串参数 name - 字符串缓存的外部名(传入变量名) string - 字符串缓存的内部名 nump - 数组的数量 perm - 权限 */
范例:
char insidestr[] = "hello world"; module_param(extstr, insidestr, szieof(insidestr), 0644);
运行:
# insmod xxx.ko extstr="hello"
10、编译内核模块
如果一个内核模块要加载到某个内核中运行,则这个模块必须使用编译该内核镜像的源码进行编译,否则运行时会出错
A、头文件(语法问题)
B、编译结果(最主要影响)
- 编译时符号表(只在编译时使用)
- 运行时内核符号表
- # cat /proc/kallsyms 运行时内核符号表
C、编译系统
示例Makefile:
# 内核模块的Makefile(模块源码在内核源码外,且内核先编译) # 1、找内核的Makefile # 2、内核的Makefile找内核模块的Makeifle 内核模块的Makeifle定义要编译对象 ifneq ($(KERNELRELEASE),) #要编译对象表示把demo.c编译成demo.ko obj-m = demo.o else #内核源码目录 KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif clean: rm -rf .tmp_versions Module.symvers modules.order .tmp_versions .*.cmd *.o *.ko *.mod.c
KERNELRELEASE 是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容。
当make的目标为all时,-C $(KERNELDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。
当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。
每个内核的名字都包含了它的版本号,这也是 uname -r 命令显示的值。
11、操作内核模块
A、加载模块
- # insmode 模块文件名
- # modprobe 模块名 # depmod
- # modinfo 可以产看模块信息
B、查看模块
# lsmod
模块名 模块大小 使用计数 使用者(by没有内容的是用户模块,有没用的是内核模块)
Module size Used by
demo 2333 0 (Used是当前有多少在用,为0时才允许被卸载)
mptscsih 39530 1 mptspi
C、卸载模块
# rmmod 模块名(Used为0时才允许被卸载)
D、查看内核输出信息
# dmesg | tail –n 10 /* 查看内后最后十行信息 */
F、导出符号表
#define EXPORT_SYMBOL(导出符号表),符号可以是全局变量,也可以是函数