type
date
slug
category
icon
password
实验代码讲解
代码框架
Linux 内核模块的代码框架通常由模块加载、模块卸载函数、模块许可证证明,如上三者为必须,还由模块参数、模块导出符号和模块的其他相关信息组成。
上面hello module程序通过
module_init
宏定义在内核注册模块加载函数,module_exit
宏定义在内核注册内核卸载函数,MODULE_LICENSE 声明模块遵循的许可证。还通过<linux/module.h>提供的其他宏定义添加模块其他相关信息(作者、描述和别名)。头文件
编写内核所需的头文件处于Linux源码中include的文件夹。
- #include <linux/module.h>: 包含了内核加载module_init()/卸载module_exit()函数和内核模块信息相关函数的声明。
- #include <linux/init.h>: 包含一些内核模块相关节区的宏定义
- #include <linux/kernel.h>: 包含内核提供的各种函数,如printk(内核模块不能依赖与C库函数,因此不能用 printf 函数,需要使用单独的打印输出函数printk。)
模块加载/卸载函数
module_init
返回值:
- 0: 表示模块初始化成功,并会在/sys/module下新建一个以模块名为名的目录,如下图中的红框处
- 非0: 表示模块初始化失败
- __init 用于修饰原函数,__initdata 用于修饰变量,带有__init的修饰符,表示将该函数放到可执行文件的__init节区中,该节区的内容只能用于模块的初始化阶段, 初始化阶段执行完毕之后,这部分的内容就会被释放掉。
- 宏定义
module_init
用于通知内核初始化模块的时候, 要使用哪个函数进行初始化。它会将函数地址加入到相应的节区section中, 这样的话,开机的时候就可以自动加载模块了。
module_exit
与函数func_init区别在于,该函数的返回值是void类型,且修饰符也不一样, 这里使用的使用__exit,表示将该函数放在可执行文件的__exit节区, 当执行完模块卸载阶段之后,就会自动释放该区域的空间。
类比于模块加载函数,__exit用于修饰函数,__exitdata用于修饰变量。 宏定义module_exit用于告诉内核,当卸载模块时,需要调用哪个函数。
printk
- printf:glibc实现的打印函数,工作于用户空间
- printk:内核模块无法使用glibc库函数,内核自身实现的一个类printf函数,但是需要指定打印等级。
- #define KERN_EMERG “<0>” 通常是系统崩溃前的信息
- #define KERN_ALERT “<1>” 需要立即处理的消息
- #define KERN_CRIT “<2>” 严重情况
- #define KERN_ERR “<3>” 错误情况
- #define KERN_WARNING “<4>” 有问题的情况
- #define KERN_NOTICE “<5>” 注意信息
- #define KERN_INFO “<6>” 普通消息
- #define KERN_DEBUG “<7>” 调试信息
查看当前系统printk打印等级:
cat /proc/sys/kernel/printk
, 从左到右依次对应当前控制台日志级别、默认消息日志级别、 最小的控制台级别、默认控制台日志级别。
打印内核所有打印信息:dmesg,注意内核log缓冲区大小有限制,缓冲区数据可能被覆盖掉。
许可证
内核模块许可证有 “GPL”,“GPL v2”,“GPL and additional rights”,“Dual SD/GPL”,“Dual MPL/GPL”,“Proprietary”
相关信息声明
函数 | 说明 |
MODULE_AUTHOR() | 描述模块的作者信息 |
MODULE_DESCRIPTION() | 对模块的简单介绍 |
MODULE_ALIAS() | 给模块设置一个别名 |
模块加载卸载时机
模块加载
- 开机时自动加载:
- 某些模块可能配置为在系统启动时自动加载。这通常在系统的启动脚本或配置文件中指定。
- 例如,Linux 中可以通过
/etc/modules
文件或其他启动配置来实现。
- 手动加载:
- 系统管理员或用户可以在需要时手动加载模块,通常使用命令行工具(例如 Linux 中的
modprobe
或insmod
)。
- 设备驱动请求:
- 当一个设备被连接到系统时,系统可能会自动加载相应的驱动模块。
模块卸载
- 手动卸载:
- 模块可以通过命令行工具手动卸载(例如 Linux 中的
rmmod
或modprobe -r
)。
- 依赖关系:
- 如果一个模块没有被其他模块或进程依赖,则可以被卸载。
- 系统关闭:
- 在系统关闭时,某些模块可能会被自动卸载。
实验准备
获取内核模块源码,将配套代码 linux_driver/module/hellomodule 解压到内核代码同级目录。
- 第1行:该Makefile定义了变量KERNEL_DIR,来保存内核源码的目录。
- 第3-5行: 指定了工具链并导出环境变量
- 第6行:变量obj-m保存着需要编译成模块的目标文件名。
- 第8行:’$(MAKE)modules’实际上是执行Linux顶层Makefile的伪目标modules。通过选项’-C’,可以让make工具跳转到源码目录下读取顶层Makefile。’M=$(CURDIR)’表明返回到当前目录,读取并执行当前目录的Makefile,开始编译内核模块。CURDIR是make的内嵌变量,自动设置为当前目录。
上机演示
- Author:felixfixit
- URL:http://www.felixmicrospace.top/article/linux_drv_firstmodule
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!