【笔记】Linux驱动学习第一章
作者:Exculivor
日期:2015年6月25日
-
- 本次学习内容
- 简单模块Hello的编写
- 代码分析
- 简单Makefile的编写以及分析
- 模块的加载和卸载
本次学习内容:
- 简单模块——Hello的编写
- 代码分析
- 简单Makefile的编写以及分析
- 模块的加载和卸载
简单模块——Hello的编写
按照惯例,我们仍然通过helloworld这个简单的小程序来进入驱动编写的世界。
首先来看代码:
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT "hello module init\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "hello module exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
代码分析
代码中包含的两个头文件:
#include <linux/init.h>
包含了模块初始化所需要的函数,
#include <linux/module.h>
则包含了动态的将模块加载到内核中去的一些函数。
接下来的的宏定义内容:
MODULE_LICENSE("Dual BSD/GPL");
则负责告诉内核,本模块采用的许可证是自由许可证;
“如果没有这样的许可证,内核在装载该模块时会产生抱怨。”
《linux设备驱动程序》中如是说,但是并没有详细阐明原因以及结果。我们将之注释掉后运行测试的结果为:
[13979.742518] hello: module license ‘unspecified’ taints kernel.
[13979.742572] Disabling lock debugging due to kernel taint
[13979.750285] hello module init
Linux遵循GPL协议,通常排斥非GPL软件。强行加入没有GPL许可证的模块直接导致内核判断自己被污染!
Linux内核识别的许可证有:
| 协议 | 简介 |
|---|---|
| GPL | 任意版本的GNU通用公共许可证 |
| GPL v2 | GPL第二版 |
| GPL and additional rignts | GPL及附加权利 |
| Dual BSD/GPL | BSD/GPL双许可证 |
| Proprietary | 专有 |
我们可根据相关协议的具体内容判断自己应该使用哪个协议,并包含进去。
接下来就是重点了。
这个模块的两个函数:
static int hello_init(void)
{
printk(KERN_ALERT "hello module init\n");
return 0;
}
的作用为打印一条语句:
hello module init
至于使用的打印函数则要注意并不是我们常用的 printf而是printk。
这两个函数的功能类似,区别是printf的运行需要C库,而printk是内和自己的打印输出函数,并不依赖C库。
当模块加载到内核之中后,就可以访问内核的公用符号(包括函数和变量)。
后面的KERN_ALERT定义了这条消息的优先级。
根据不同的使用环境还有不同的的优先级:
| 字符串 | 优先级 | 简介 |
|---|---|---|
| KERN_EMERG | 0 | 紧急事件消息,系统崩溃之前提示,表示系统不可用 |
| KERN_ALERT | 1 | 报告消息,表示必须立即采取措施 |
| KERN_CRIT | 2 | 临界条件,通常涉及严重的硬件或软件操作失败 |
| KERN_ERR | 3 | 错误条件,驱动程序常用KERN_ERR来报告硬件错误 |
| KERN_WARNING | 4 | 警告条件,对可能出现问题的情况进行警告 |
| KERN_NOTICE | 5 | 正常但又重要的条件。常用于与安全相关的消息 |
| KERN_INFO | 6 | 提示信息,如驱动程序启动时,打印硬件信息 |
| KERN_DEBUG | 7 | 调试级别的消息 |
第二个函数:
static void hello_exit(void)
{
printk(KERN_ALERT "hello module exit\n");
}
作用为输出另一条语句:
hello module exit
函数有了,按照我们的预想,加载模块的时候输出** hello module init** 卸载模块的时候输出** hello module exit** 。
但是写到这里还没完,内核并不知道什么时候该用哪个函数。所以我们需要为内核标记一下。于是我们调用两个函数:module_init和module_exit来为内核指路。
加载模块时,内核会调用模块内传递给module_init的参数对应的函数,这里即hello_init函数。同样,退出时会调用传递给module_exit的参数对应的函数,这里是hello_exit。
简单Makefile的编写以及分析
接下来我们需要编写Makefile来编译代码
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.ko *.mod.c *.order *.symvers
.PHONY: modules clean
else
obj-m := hello.o
endif
注意格式,Makefile对格式要求严格。
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
-C指定内核源代码目录,此处保存有顶层的Makefile,这个顶层Makefile即Linux内核编译系统的入口。
M=指定在构造modules目标之前返回到当前目录,即把要生成的modules目标放在当前目录下。
在此的构造过程中,该Makefile将被读取两次。
当从命令行执行make命令时,该Makefile将会被调用,此时是第一次读取该Makefile,变量KERNELRELEASE没有设置,ifeq条件不满足,所以会运行默认分支,定义KERNELDIR和PWD:
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
并且执行默认编译选项modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
从而第二次运行make命令,此次由于前面定义了KERNELDIR故
ifeq ($(KERNELRELEASE),)
不成立,执行else分支:
obj-m := hello.o
即通过hello.o文件来生成一个目标模块,将目标模块名为hello.ko。
如果我们需要从多个文件生成目标模块,则可以使用:
obj-m := module.o
module-objs := file1.o file2.o
之后可以通过shell命令make来生成hello.ko和通过shell命令make clean来清除构造生成的文件。
模块的加载和卸载
模块的加载使用shell命令:
insmod
模块的卸载使用shell命令:
rmmod
另外还可以通过shell命令:
lsmod
来列出当前加载运行的模块。
