1、什么是内核模块?
内核模块是Linux提供的一种机制,允许在内核运行时动态加载进内核中,具有两个特点:
1)内核模块本身不编译入内核映像,有效控制缩减内核镜像大小
2)内核模块一旦被加载,他就和内核中的其他部分完全一样
2、为什么需要内核模块?
如果在内核编译时把所有的功能都编译进去,就会导致内核很大,而且要往内核中添加或删除功能时必须重新编译内核
比如在Ubuntu在通用PC平台上,预先无法知道需要什么设备,就不知道预先编译什么驱动。
3、内核模块和应用程序的区别
| 工作模式 | 工作性质 | 层次 | 权限 | 影响 | 竞态 | 运行方式 |
应用程序 | USR模式 | 策略性 | 用户层 | 低 | 局部 | 局部 | 主动 |
内核模块 | SVC模式 | 功能性 | 内核层 | 高 | 全局 | 全局 | 被挡 |
4、内核模块的基本构成
——两个函数(一般需要)
| |——模块初始化(加载)函数:当内核模块加载进内核的时候,做一些准备工作
| |——模块卸载函数:回收、清理资源
|
|——授权(许可证声明)(必须):Linux内核受GPL(General Public License)授权约束
|——模块参数(可选):模块被加载时可以被传递给它的值,本身对应模块内的全局变量
|——模块导出符号(可选)
|——模块信息说明(可选)
5、模块加载(初始化)函数
一般以 __init标识声明
函数命名规则 xxx_init xxx设备名 init功能名(初始化)
函数形式:
tatic 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、模块许可证声明
ODULE_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,表明该驱动模块支持的设备,如:
/* 对应此驱动的设备列表 */
tatic 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、传递普通变量
odule_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、传递数组参数
odule_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(导出符号表),符号可以是全局变量,也可以是函数