学习目标:
- 理解内核是怎么加载和卸载驱动
- 理解相关的头文件
- 理解内核打印函数printk以及日志等级
- 理解__init和__exit修饰的作用
- makefile相关语法解析
一、代码
hellomodule.c
#include "linux/init.h"
#include "linux/module.h"
#include "linux/kernel.h"
/*
入口函数
*/
static int __init hellomodule_init(void)
{
printk(KERN_EMERG "[ KERN_EMERG ] Hello Module Init\n");
printk( "[ default ] Hello Module Init\n");
return 0;
}
/*
出口函数
*/
static void __exit hellomodule_exit(void)
{
printk(KERN_DEBUG "[ KERN_DEBUG ] Hello Module exit\n");
printk("[ default ] Hello Module Exit\n");
}
module_init(hellomodule_init); // 注册入口函数
module_exit(hellomodule_exit); // 注册出口函数
MODULE_AUTHOR("LingTu <luo_lin_lai_sheng@163.com>"); // 设置编写驱动的作者信息
MODULE_DESCRIPTION("LingTu hello module test code"); // 设置驱动的描述信息
MODULE_LICENSE("GPL"); // 设置驱动遵循的开源协议
Makefile
KERNEL_DIR=../../ebf_linux_kernel/build_image/build
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m := hellomodule.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
.PHONE:clean copy
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
copy:
sudo cp *.ko /home/embedfire/workdir
二、相关知识点
1. 内核模块头文件
- #include <linux/module.h>:包含内核模块信息声明的相关函数:MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_ALIAS
- #include <linux/init.h>:包含了 module_init()和 module_exit()函数的声明
- #include <linux/kernel.h>:包含内核提供的各种函数,如printk
2. __init和__exit修饰的作用
想要理解这两个宏的作用,那么我们应该找到这两个宏的定义处,在include\linux下的init.h中定义的。
从该定义中可以猜测出,使用__init和__exit是用于规定代码存放的段
3. 内核模块打印函数
- 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
显示:7 7 1 7
分别代表以下含义:
- 当前控制台日志级别: 也就是目前控制台能输出的信息的等级
- 默认消息日志级别 : 也就是设置当使用printk未加修饰符的打印信息级别
- 最小的控制台级别: 控制台最小的级别
- 默认控制台日志级别:默认时控制台的日志级别
一般我们常设置当前控制台日志级别和 默认消息日志级别,当当前控制台日志级别级别<默认消息日志级别时,默认消息日志才能显示,数字越小,级别越高!
比如当7 7 1 7时,由于默认消息日志级别(7)不大于当前控制台日志级别(7),所以不打印默认日志信息。
设置打印等级:echo "7 6 1 7" > /proc/sys/kernel/printk
设置为上述后就可以打印了。
打印内核所有打印信息:dmesg
- 内核log缓冲区大小有限制,缓冲区数据可能被冲掉
在代码中可以人为的指定打印等级,比如下面所示的KERN_EMERG
printk(KERN_EMERG "[ KERN_EMERG ] Hello Module Init\n");
可以在printk后面加上修饰符KERN_EMERG、 KERN_ALERT等等,用于设置当前的日志等级,这些宏是在include\linux下的kern_levels.h中定义的,如下图所示:
4. Makefile分析
-
KERNEL_DIR:指向linux内核具体路径
-
export:导出变量给子Makefile使用
-
obj-m := <模块名>.o:定义要生成的模块
-
$(MAKE):Makefile的默认变量,值为make
-
选项”-C”:让make工具跳转到linux内核目录下读取顶层Makefile
-
M=:表示内核模块源码目录
-
$(CURDIR):Makefile默认变量,值为当前目录所在路径
-
make modules:执行Linux顶层Makefile的伪目标,它实现内核模块的源码读取并编译为.ko文件
三、实验分析
1. 编译内核模块
make
2. 把生成的内核模块拷贝到nfs共享目录
make copy
3. 开发板加载内核模块
insmod hellomodule.ko
由于我们设置的默认消息日志为6,而当前控制台日志等级为7所以,default的日志消息打印了,同时KERN_EMERG日志等级为0,所以这个消息日志也打印了。
从日志信息中可以看出,运行insmod命令时运行了入口函数
4. 查看是否有相关驱动
lsmod
5. 卸载驱动
lsmod
由于我们当前控制台日志等级为7,而KERN_DEBUG日志等级为7,所以在hellomodule_exit函数中的
printk(KERN_DEBUG "[ KERN_DEBUG ] Hello Module exit\n");
这句打印信息没有打印,从日志信息中可以看出,运行rmmod命令时运行了出口函数