• 沒有找到結果。

第 5 章 ARM LINUX 驱动程序开发入门

5.2 最简单的内核模块举例

Linux 设备驱动模块属于内核的一部分,这在前面已经介绍,Linux 内核的一个模块可 以以两种方式被编译和加载,方法一是直接配置编译进 Linux 内核,随同 Linux 启动时加载;

方法二是编译成一个可加载和卸载的模块,使用 insmod 加载(modprobe 和 insmod 命令类 似,但依赖于相关的配置文件)模块,而 rmmod 用来卸载模块。通过方法二可以控制内核 的大小,而模块一旦被插入内核,它就和内核其他部分一样被使用,此外方法二是动态的加 载和卸载模块而不需要重新编译整个内核,所以方便调试。

5.2.1 编写 Hello World 模块

下面以一个最简单的“Hello World”模块程序为例介绍 Linux 内核模块程序的框架结构,

在准备运行该模块程序之前,建议你已经安装了 2.6 的内核,并且最好安装的内核版本与你 所使用的内核版本是一致的。

1 #include <linux/init.h>

2 #include <linux/module.h>

3 #include <linux/moduleparam.h>

4

5 MODULE_LICENSE("Dual BSD/GPL"); 含的头文件是定义__init、__exit、module_init 和 module_exit 必须的宏。第 2 行包含的头文 件定义所有模块相关的宏,如 MODULE_LICENSE。第 3 行包含的头文件是用来定义模块 参数所必须的。现在来具体分析一下这段程序,该内核模块程序定义了两个函数,当该模块 被装载进内核时调用 hello_init 函数,当该模块被卸载时调用 hello_exit 函数。其中 module_init 和 module_exit 为内核特殊的宏,用来定义模块被装载和卸载时依次分别调用的函数。其中 第 5 行用 MODULE_LICENSE 宏来声明该模块的许可协议,该模块声明为 BSD(Berkly Software Distribution)和 GPL(General Public License)双重许可协议。内核函数 printk 被定 义在 Linux 内核中,它行为简单类似于标准 C 库函数 printf。也许读者会问为什么不用 printf 函数呢?细心的读者应该记得在第二章我们讲述交叉编译工具链的时候就说过,编译 Linux 内核不需要标准 C 库和其他函数库的支持,所以这里就不能使用 printf 库函数,然而内核需 要自己的打印函数即 printk,它通过自身内核运行而不需要 C 库的帮助。内核模块之所以能 够调用 printk 函数,是因为它是在 insmod 装载之后,此时内核模块可以与内核公共函数和 变量进行链接,从而可以使用 printk 函数。其中 KERN_ALERT 宏是标记 printk 打印出的字 符串优先等级,通常有 8 种消息等级,定义在<include/linux/kernel.h>文件中,具体定义如下:

#define KERN_EMERG "<0>" /* system is unusable */

#define KERN_ALERT "<1>" /* action must be taken immediately */

#define KERN_CRIT "<2>" /* critical conditions */

#define KERN_ERR "<3>" /* error conditions */

#define KERN_WARNING "<4>" /* warning conditions */

#define KERN_NOTICE "<5>" /* normal but significant condition */

#define KERN_INFO "<6>" /* informational */

#define KERN_DEBUG "<7>" /* debug-level messages */

这些宏的具体说明如下:

ü KERN_EMERG

用于紧急事件发生时的消息说明,一般用在系统崩溃之前的说明消息 ü KERN_ALERT

用于需要立即执行的情况 ü KERN_CRIT

用在临界状态下的情况,如硬件或软件的故障时 ü KERN_ERR

用于报告一个错误条件,设备驱动经常使用它来报告硬件错误 ü KERN_WARNING

用于有问题情况下的警告,通常不会警告一个严重的系统问题 ü KERN_NOTICE

用于正常的通知,许多安全相关的条件由这个级别的宏报告 ü KERN_INFO

用于打印一些相关信息消息,许多驱动程序用这个级别宏打印一些硬件的启动时信息 ü KERN_DEBUG

用于调试信息。

上面的程序中还应用到一个重要的概念,即模块参数。在实际设备驱动模块开发中,经 常需要传递给硬件设备一些不同的指令来控制不同的功能,其中模块参数就是用来传递给硬 件设备指令的。该程序中利用参数 who 来指定一个对象,用 times 来指定打印“Hello,who!”

信息的次数。其中 module_param 宏是 Linux 2.6 内核中新增的,该宏被定义在<include/linux/

moduleparam.h>文件中,具体定义如下:

#define module_param(name, type, perm) \ module_param_named(name, name, type, perm)

其中,参数 name 为要传递的参数变量,参数 type 为变量的数据类型,参数 perm 为访 问参数的权限。

5.2.2 编写 Hello World 模块的 Makefile

接下来编写一个 Makefile 文件来编译“Hello World”模块程序,此处 Makefile 的内容 编写如下:

EXEC = hello OBJS = hello.o SRC = hello.c

INCLUDE = /usr/src/linux-2.6.10/include CC = arm-linux-gcc

LD = arm-linux-ld

MODCFLAGS = -O2 -Wall -D__KERNEL__ -DMODULE -I$(INCLUDE) -march=armv4t -c -o

LDFLAGS = -r

all: $(EXEC)

$(EXEC): $(OBJS)

$(LD) $(LDFLAGS) -o $@ $(OBJS)

%.o:%.c

$(CC) $(MODCFLAGS) -mapcs -c $< -o $@

clean:

-rm -f $(EXEC) *.o *~ core

在上面的 Makefile 文件中,INCLUDE 来指定要编译的程序需要的头文件路径,这个路 径要根据你的内核所放的位置来定。其中 CC 和 LD 定义使用的编译器和连接器,由于我们 希望在 S3C2410 开发板上运行,所以需要使用交叉编译器来执行。MODCFLAGS 变量来指 定编译该模块时所用的编译选项,LDFLAGS 变量定义了连接选项。

5.2.3 加载和卸载 Hello World 模块

接下来在存放 Makefile 和“Hello World”源程序的目录下执行 make 命令,经过编译会 在该目录下生成 hello 和 hello.o 的基于 ARM 体系的可执行文件。然后将这些可执行性文件 下载到开发板上执行以下命令:

# insmod hello who=“world” times=5

执行这条命令的作用是,装载该模块到内核,并且传递了两个参数,who 参数传递为 world,times 参数传递为 5。执行这条命令将在控制台显示 5 次“Hello world!”。

接下来执行卸载模块操作,通过执行以下命令来卸载刚才加载的模块,具体操作如下:

# rmmod hello Goodbye, world!

通过“Hello World”一个完整模块程序的学习,读者应该会觉得 Linux 内核模块开发并 不是一件难事。的确,Linux 内核开发并不像许多人想象的那样深奥,其实一个庞大的体系 结构往往都是由简单的模块组合而成,我相信读者只要下定决心去学,一定能够成为 Linux 开发高手。下面将介绍 Linux 驱动程序开发一些要点。