您的位置:首页 > 运维架构 > Linux

【笔记】Linux驱动学习第一章

2015-06-25 14:51 621 查看
作者: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 v2GPL第二版
GPL and additional rigntsGPL及附加权利
Dual BSD/GPLBSD/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_EMERG0紧急事件消息,系统崩溃之前提示,表示系统不可用
KERN_ALERT1报告消息,表示必须立即采取措施
KERN_CRIT2临界条件,通常涉及严重的硬件或软件操作失败
KERN_ERR3错误条件,驱动程序常用KERN_ERR来报告硬件错误
KERN_WARNING4警告条件,对可能出现问题的情况进行警告
KERN_NOTICE5正常但又重要的条件。常用于与安全相关的消息
KERN_INFO6提示信息,如驱动程序启动时,打印硬件信息
KERN_DEBUG7调试级别的消息
第二个函数:

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


来列出当前加载运行的模块。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息