Linux内核模块编程
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Linux内核模块编程
主要参考书目
• 《Linux设备驱动开发 详解(第2版)》
• 宋宝华 编著
• 人民邮电出版社
主要参考书目
• 《Linux设备驱动程序 (第三版)》
• 魏永明 耿岳 等 译
• 中国电力出版社
主要参考书目
• 《精通Linux驱动程序 开发》
• Sreekrishnan 编著
• 人民邮电出版社
编译import.c时需要用到编译export.c时生成的符号文件 module.symvers
补充信息
模块声明与描述
MODULE_AUTHOR(author); ---声明模块的作者 MODULE_DESCRIPTION(description); ---声明模块的 描述 MODULE_VERSION(version_string); ---声明模块的版 本 MODULE_DEVICE_TABLE(table_info); ---声明模块的 设备表 MODULE_ALIAS(alternate_name); ---声明模块的别名
… … … … // export.c int add_integer(int a, int b) { return a+b; } int sub_integer(int a, int b) { return a-b; } EXPORT_SYMBOL(add_integer); EXPORT_SYMBOL(sub_integer); extern int add_integer(int a, int b); // import.c extern sub_integer(int a, int b); static int __init hello_init(void) { // 这是模块加载函数 printk(KERN_ALERT “%d\n“, add_integer(5, 10)); return 0; }
可装载内核模块
为了使系统功能能够更灵活的扩充,Linux支持内核 的动态扩展,即在系统运行时给内核增加新的功能 (即模块module)。 模块(module)是一段可以被动态链接的目标代码 (.ko),它可由insmod命令动态的装载并链接到正 在运行的内核。链接后,它就成了内核的一部分, 直到用rmmod命令解除链接并卸载。 Linux驱动程序就是一种特殊的内核模块。
内核模块与应用程序的不同
内核模块工作在内核空间(supervisor space),而应 用程序工作在用户空间(user space) 内核模块是一个由多个回调函数组成的“被动”代 码集合体,采用了“事件驱动模型”;而应用程序 总是从头至尾的执行单个任务。 内核模块不能调用C标准函数库(glibc库),只能调 用linux内核导出的内核函数。 内核模块在编程时必须考虑可重入性(reentrant) 内核模块可使用的栈很小(一般只有4096字节)。
Executable and Linking Format
模块卸载函数
模块卸载函数必须用宏“module_exit”指定,无返 回值。 当通过rmmod命令卸载某模块时,模块的卸载函数 会自动被内核执行,完成与模块装载函数相反的功 能。(注销一些内核数据结构,释放资源等)。
内核打印函数printk
#define __init __attribute__ ((__section__(“.init.text”))) #define __exit __attribute__ ((__section__(“.exit.text”)))
module_init背后的秘密
#define module_init(x) __initcall(x); #define __initcall(fn) device_initcall(fn) #define device_initcall(fn) __define_initcall("6", fn, 6) #define __define_initcall(level,fn,id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" level ".init"))) = fn 所以,module_init(x)最终展开为: static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" level ".init"))) = fn initcall_t是一个指向函数的指针类型 typedef int (*initcall_t)(void) 所以module_init本质上是将一个函数指针变量放在了一名为".initcall6.init" ELF节中。
printk(fmt, args …) 级别
KERN_EMERG 用于紧急消息, 常常是那些崩溃前的消息. KERN_ALERT 需要立刻动作的情形. KERN_CRIT 严重情况, 常常与严重的硬件或者软件失效有关. KERN_ERR 用来报告错误情况; 设备驱动常常使用 KERN_ERR 来报 告硬件故障. KERN_WARNING 有问题的情况的警告, 这些情况自己不会引起系统 的严重问题. KERN_NOTICE 正常情况, 但是仍然值得注意. 在这个级别一些安全相 关的情况会报告. KERN_INFO 信息型消息. 在这个级别, 很多驱动在启动时打印它们发 现的硬件的信息. KERN_DEBUG 用作调试消息.
insmod ./hello.ko
modprobe会考虑要装载的模块是否引用了一些当前内核不存 在的符号。如果有这类引用,modprobe会在当前模块路径中 搜索定义了这些符号的其他模块,并同时将这些模块也装载 到内核。(/lib/modules modules.dep /ect/modprob.conf) 使用rmmod命令卸载模块:
rmmod hello
使用lsmod命令查看内核中已加载的内核模块的信息 通过查看/proc/modules文件也可查看内核中已加载的内核模 块的信息。 通过查看/sys/module目录也可查看内核中已加载的内核模块 的信息。
内核模块参数
module_param(参数名,参数类型,参数读/写权限)
不能打印浮点数
printk消息流向
/etc/syslog.conf中可配置syslogd的分发规则,例如可以加入: kern.* /tmp/kernel_debug.txt /proc/kernel/printk文件中设置了一个优先级,高于该优先级的 消息才能显示到控制台中
模块许可证声明
模块许可证声(MODULE_LICENSE)明描述内核 模块的许可权限 如果不声明LICENSE,模块被加载时,将收到内核被 污染(kernel tainted)的警告
编译内核模块的条件
1. 已安装了GCC工具链 2. 有一份内核源码,且至少被编译过一次。 3. 内核模块程序在编译过程中要使用内核源码的头文 件(在include目录)和编译内核时生成的符号文件。
内核模块程序源码的构成
头文件 #include <linux/init.h> #include <linux/moduleห้องสมุดไป่ตู้h>
许可声明 MODULE_LICENSE("Dual BSD/GPL");
必选
必选
加载函数 static int __init hello_init(void) 卸载函数 static void __exit hello_exit(void)
内核模块的编译
可以编写一个最简单的Makefile:
obj-m := hello.o
并采用如下命令编译:
make –C /lib/modules/$(shell uname -r)/build M= $(shell pwd) modules
或采用如下较复杂的Makefile:
ifneq ($(KERNELRELEASE),) # call from kernel build system obj-m := hello.o else KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: make -C $(KERNELDIR) M=$(PWD) modules endif
内核模块的编译
如果我们想由两个源文件(比如file1.c和file2.c )构造 出一个名称为module.ko的模块, 则makefile的obj-m变 量可如下编写:
obj-m := module.o module-objs := file1.o file2.o
内核模块的加载与卸载
使用insmod命令或modprobe加载模块:
static char *whom = "world"; static int howmany = 1; module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
内核支持的模块参数类型包括: byte、short、ushort、int、uint、long、ulong、charp(字 符指针)、bool。
装载模块指定参数
可通过insmod或modprobe在装载模块时为其传入参 数: insmod hello_ext.ko howmany=5 whom="Students" modprobe也可以从它的配置文件(/etc/modprobe.conf) 读取参数的值
最简单的Kernel Module
必选 必选
可选
模块参数 module_param(num, int, S_IRUGO)
模块导出符号 EXPORT_SYMBOL(add_integer) 模块作者等信息声明 MODULE_AUTHOR(“author_name”)
可选 可选
最简单的Kernel Module
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int __init hello_init(void) { // 这是模块加载函数 printk(KERN_ALERT "Hello, world\n"); return 0; } static void __exit hello_exit(void) { // 这是模块卸载函数 printk(KERN_ALERT "Goodbye, cruel world\n"); } module_init(hello_init); module_exit(hello_exit);
模块加载函数
通过insmod或modprobe命令加载内核模块时,模块的加载函 数会自动被内核执行,完成模块的相关初始化工作,通常包 括:(1)向内核注册一些数据结构;(2)申请软硬件资源;(3) 初始化硬件 模块加载函数必须用宏“module_init”指定,它返回整型值。 若初始化成功则返回0,若失败则返回一个负值作为错误码。 “__init”和“__exit”都是宏,利用了gcc的扩展关键字,分 别要求编译器将所声明函数的目标代码放入“init.text”段和 “exit.text”段中(两个特殊的ELF段)。 static关键字为了将该函数名的可见性控制在本文件内。
主要参考书目
• 《Linux设备驱动开发 详解(第2版)》
• 宋宝华 编著
• 人民邮电出版社
主要参考书目
• 《Linux设备驱动程序 (第三版)》
• 魏永明 耿岳 等 译
• 中国电力出版社
主要参考书目
• 《精通Linux驱动程序 开发》
• Sreekrishnan 编著
• 人民邮电出版社
编译import.c时需要用到编译export.c时生成的符号文件 module.symvers
补充信息
模块声明与描述
MODULE_AUTHOR(author); ---声明模块的作者 MODULE_DESCRIPTION(description); ---声明模块的 描述 MODULE_VERSION(version_string); ---声明模块的版 本 MODULE_DEVICE_TABLE(table_info); ---声明模块的 设备表 MODULE_ALIAS(alternate_name); ---声明模块的别名
… … … … // export.c int add_integer(int a, int b) { return a+b; } int sub_integer(int a, int b) { return a-b; } EXPORT_SYMBOL(add_integer); EXPORT_SYMBOL(sub_integer); extern int add_integer(int a, int b); // import.c extern sub_integer(int a, int b); static int __init hello_init(void) { // 这是模块加载函数 printk(KERN_ALERT “%d\n“, add_integer(5, 10)); return 0; }
可装载内核模块
为了使系统功能能够更灵活的扩充,Linux支持内核 的动态扩展,即在系统运行时给内核增加新的功能 (即模块module)。 模块(module)是一段可以被动态链接的目标代码 (.ko),它可由insmod命令动态的装载并链接到正 在运行的内核。链接后,它就成了内核的一部分, 直到用rmmod命令解除链接并卸载。 Linux驱动程序就是一种特殊的内核模块。
内核模块与应用程序的不同
内核模块工作在内核空间(supervisor space),而应 用程序工作在用户空间(user space) 内核模块是一个由多个回调函数组成的“被动”代 码集合体,采用了“事件驱动模型”;而应用程序 总是从头至尾的执行单个任务。 内核模块不能调用C标准函数库(glibc库),只能调 用linux内核导出的内核函数。 内核模块在编程时必须考虑可重入性(reentrant) 内核模块可使用的栈很小(一般只有4096字节)。
Executable and Linking Format
模块卸载函数
模块卸载函数必须用宏“module_exit”指定,无返 回值。 当通过rmmod命令卸载某模块时,模块的卸载函数 会自动被内核执行,完成与模块装载函数相反的功 能。(注销一些内核数据结构,释放资源等)。
内核打印函数printk
#define __init __attribute__ ((__section__(“.init.text”))) #define __exit __attribute__ ((__section__(“.exit.text”)))
module_init背后的秘密
#define module_init(x) __initcall(x); #define __initcall(fn) device_initcall(fn) #define device_initcall(fn) __define_initcall("6", fn, 6) #define __define_initcall(level,fn,id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" level ".init"))) = fn 所以,module_init(x)最终展开为: static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" level ".init"))) = fn initcall_t是一个指向函数的指针类型 typedef int (*initcall_t)(void) 所以module_init本质上是将一个函数指针变量放在了一名为".initcall6.init" ELF节中。
printk(fmt, args …) 级别
KERN_EMERG 用于紧急消息, 常常是那些崩溃前的消息. KERN_ALERT 需要立刻动作的情形. KERN_CRIT 严重情况, 常常与严重的硬件或者软件失效有关. KERN_ERR 用来报告错误情况; 设备驱动常常使用 KERN_ERR 来报 告硬件故障. KERN_WARNING 有问题的情况的警告, 这些情况自己不会引起系统 的严重问题. KERN_NOTICE 正常情况, 但是仍然值得注意. 在这个级别一些安全相 关的情况会报告. KERN_INFO 信息型消息. 在这个级别, 很多驱动在启动时打印它们发 现的硬件的信息. KERN_DEBUG 用作调试消息.
insmod ./hello.ko
modprobe会考虑要装载的模块是否引用了一些当前内核不存 在的符号。如果有这类引用,modprobe会在当前模块路径中 搜索定义了这些符号的其他模块,并同时将这些模块也装载 到内核。(/lib/modules modules.dep /ect/modprob.conf) 使用rmmod命令卸载模块:
rmmod hello
使用lsmod命令查看内核中已加载的内核模块的信息 通过查看/proc/modules文件也可查看内核中已加载的内核模 块的信息。 通过查看/sys/module目录也可查看内核中已加载的内核模块 的信息。
内核模块参数
module_param(参数名,参数类型,参数读/写权限)
不能打印浮点数
printk消息流向
/etc/syslog.conf中可配置syslogd的分发规则,例如可以加入: kern.* /tmp/kernel_debug.txt /proc/kernel/printk文件中设置了一个优先级,高于该优先级的 消息才能显示到控制台中
模块许可证声明
模块许可证声(MODULE_LICENSE)明描述内核 模块的许可权限 如果不声明LICENSE,模块被加载时,将收到内核被 污染(kernel tainted)的警告
编译内核模块的条件
1. 已安装了GCC工具链 2. 有一份内核源码,且至少被编译过一次。 3. 内核模块程序在编译过程中要使用内核源码的头文 件(在include目录)和编译内核时生成的符号文件。
内核模块程序源码的构成
头文件 #include <linux/init.h> #include <linux/moduleห้องสมุดไป่ตู้h>
许可声明 MODULE_LICENSE("Dual BSD/GPL");
必选
必选
加载函数 static int __init hello_init(void) 卸载函数 static void __exit hello_exit(void)
内核模块的编译
可以编写一个最简单的Makefile:
obj-m := hello.o
并采用如下命令编译:
make –C /lib/modules/$(shell uname -r)/build M= $(shell pwd) modules
或采用如下较复杂的Makefile:
ifneq ($(KERNELRELEASE),) # call from kernel build system obj-m := hello.o else KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: make -C $(KERNELDIR) M=$(PWD) modules endif
内核模块的编译
如果我们想由两个源文件(比如file1.c和file2.c )构造 出一个名称为module.ko的模块, 则makefile的obj-m变 量可如下编写:
obj-m := module.o module-objs := file1.o file2.o
内核模块的加载与卸载
使用insmod命令或modprobe加载模块:
static char *whom = "world"; static int howmany = 1; module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
内核支持的模块参数类型包括: byte、short、ushort、int、uint、long、ulong、charp(字 符指针)、bool。
装载模块指定参数
可通过insmod或modprobe在装载模块时为其传入参 数: insmod hello_ext.ko howmany=5 whom="Students" modprobe也可以从它的配置文件(/etc/modprobe.conf) 读取参数的值
最简单的Kernel Module
必选 必选
可选
模块参数 module_param(num, int, S_IRUGO)
模块导出符号 EXPORT_SYMBOL(add_integer) 模块作者等信息声明 MODULE_AUTHOR(“author_name”)
可选 可选
最简单的Kernel Module
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int __init hello_init(void) { // 这是模块加载函数 printk(KERN_ALERT "Hello, world\n"); return 0; } static void __exit hello_exit(void) { // 这是模块卸载函数 printk(KERN_ALERT "Goodbye, cruel world\n"); } module_init(hello_init); module_exit(hello_exit);
模块加载函数
通过insmod或modprobe命令加载内核模块时,模块的加载函 数会自动被内核执行,完成模块的相关初始化工作,通常包 括:(1)向内核注册一些数据结构;(2)申请软硬件资源;(3) 初始化硬件 模块加载函数必须用宏“module_init”指定,它返回整型值。 若初始化成功则返回0,若失败则返回一个负值作为错误码。 “__init”和“__exit”都是宏,利用了gcc的扩展关键字,分 别要求编译器将所声明函数的目标代码放入“init.text”段和 “exit.text”段中(两个特殊的ELF段)。 static关键字为了将该函数名的可见性控制在本文件内。