#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello, Tekkaman Ninja !/n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, Tekkaman Ninja !/n Love Linux !Love ARM ! Love KeKe !/n");
}
module_init(hello_init);
module_exit(hello_exit);
我将其复制到我的工作目录,并编写了一个简单的Makefile文档:
obj-m := hello.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
.PHONY: modules modules_install clean
说实话,以上是我参考了《Linux设备驱动程式(第3版)》的Makefile源码修改得来的。我对Makefile不是很了解,是该好好学习学习了!
然后就是make modules 、 make modules_install 。
[root@Tekkaman-Ninja Helloworld]# make modules
make -C /home/tekkaman/working/SBC2440/linux-2.6.22.2 M=/home/tekkaman/working/Linuxdriver/Helloworld modules
make[1]: Entering directory `/home/tekkaman/working/SBC2440/linux-2.6.22.2'
CC [M] /home/tekkaman/working/Linuxdriver/Helloworld/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/tekkaman/working/Linuxdriver/Helloworld/hello.mod.o
LD [M] /home/tekkaman/working/Linuxdriver/Helloworld/hello.ko
make[1]: Leaving directory `/home/tekkaman/working/SBC2440/linux-2.6.22.2'
[root@Tekkaman-Ninja Helloworld]# make modules_install
cp hello.ko /home/tekkaman/working/rootfs/lib/modules
[root@Tekkaman-Ninja Helloworld]#
在我的研发板上的操作:
[Tekkaman2440@SBC2440V4]#cd /lib/modules/
[Tekkaman2440@SBC2440V4]#ls
cs89x0.ko hello.ko p80211.ko prism2_usb.ko
[Tekkaman2440@SBC2440V4]#insmod hello.ko
Hello, Tekkaman Ninja !
[Tekkaman2440@SBC2440V4]#lsmod
Module Size Used by Not tainted
hello 1376 0
[Tekkaman2440@SBC2440V4]#rmmod hello
Goodbye, Tekkaman Ninja !
Love Linux !Love ARM ! Love KeKe !
[Tekkaman2440@SBC2440V4]#lsmod
Module Size Used by Not tainted
[Tekkaman2440@SBC2440V4]#
学习心得:
(1)驱动模块运行在内核空间,运行时不能依赖于任何函数库和模块连接,所以在写驱动时所调用的函数只能是作为内核一部分的函数。
(2)驱动模块和应用程式的一个重要不同是:应用程式退出时可不管资源释放或其他的清除工作,但模块的退出函数必须仔细撤销初始化函数所作的一切,否则,在系统重新引导之前某些东西就会残留在系统中。
(3)处理器的多种工作模式(级别)其实就是为了操作系统的用户空间和内核空间设计的。在Unix类的操作系统中只用到了两个级别:最高和最低级别。
(4)要十分注意驱动程式的并发处理。
(5)内核API中具备双下划线(_ _)的函数,通常是接口的底层组件,应慎用。
(6)内核代码不能实现浮点书运算。
(7)Makefile文档分析:
obj-m := hello.o 代表了我们要构造的模块名为hell.ko,make 会在该目录下自动找到hell.c文档进行编译。假如 hello.o是由其他的源文档生成(比如file1.c和file2.c)的,则在下面加上(注意红色字体的对应关系):
hello-objs := file1.o file2.o ......
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
其中 -C $(KERNELDIR) 指定了内核源代码的位置,其中保存有内核的顶层makefile文档。
M=$(PWD) 指定了模块源代码的位置
modules目标指向obj-m变量中设定的模块。
(8)insmod使用公共内核符号表来解析模块中未定义的符号。公共内核符号表中包含了任何的全局内核项(即函数和变量的地址),这是实现模块化驱动程式所必须的。
(9)Linux使用模块层叠技术,我们能够将模块划分为多个层,通过简化每个层可缩短研发周期。假如一个模块需要向其他模块到处符号,则使用下面的宏:
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
符号必须在模块文档的全局变量部分导出,因为这两个宏将被扩展为一个特别变量的声明,而该变量必须是全局的。
(10)任何模块代码中都包含一下两个头文档:
#include linux/init.h>
#include linux/module.h>
(11)任何模块代码都应该指定所使用的许可证:
MODULE_LICENSE("Dual BSD/GPL");
此外更有可选的其他描述性定义:
MODULE_AUTHOR("");
MODULE_DESCRIPTION("");
MODULE_VERSION("");
MODULE_ALIAS("");
MODULE_DEVICE_TABLE("");
上述MODULE_声明习惯上放在文档最后。
(12)初始化和关闭
初始化的实际定义通常如下:
static int _ _init initialization_function(void)
{
/*初始化代码*/
}
module_init(initialization_function)
清除函数的实际定义通常如下:
static int _ _exit cleanup_function(void)
{
/*清除代码*/
}
module_exit(cleanup_function)
(13) Linux内核模块的初始化出错处理一般使用“goto”语句。通常情况下很少使用“goto”,但在出错处理是(可能是唯一的情况),他却很有用。在大二学习C语言时,老师就建议不要使用“goto”,并说很少会用到。在这里也是我碰到的第一个建议使用“goto”的地方。“在追求效率的代码中使用goto语句仍是最好的错误恢复机制。”--《Linux设备驱动程式(第3版)》以下是初始化出错处理的推荐代码示例:
struct something *item1;
struct somethingelse *item2;
int stuff_ok;
void my_cleanup(void)
{
if (item1)
release_thing(item1);
if (item2)
release_thing2(item2);
if (stuff_ok)
unregister_stuff();
return;
}
int __init my_init(void)
{
int err = -ENOMEM;
item1 = allocate_thing(arguments);
item2 = allocate_thing2(arguments2);
if (!item2 || !item2)
goto fail;
err = register_stuff(item1, item2);
if (!err)
stuff_ok = 1;
else
goto fail;
return 0; /* success */
fail:
my_cleanup( );
return err;
}
(14)模块参数:内核允许对驱动程式指定参数,而这些参数可在装载驱动程式模块时改变。
以下是我的实验程式:
#include linux/init.h>
#include linux/module.h>
#include linux/moduleparam.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *whom = "Tekkaman Ninja";
static int howmany = 1;
static int TNparam[] = {1,2,3,4};
static int TNparam_nr = 4;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
module_param_array(TNparam , int , &TNparam_nr , S_IRUGO);
static int hello_init(void)
{
int i;
for (i = 0; i howmany; i++)
printk(KERN_ALERT "(%d) Hello, %s !/n", i, whom);
for (i = 0; i ; i++)
printk(KERN_ALERT "TNparam[%d] : %d /n", i, TNparam);
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, Tekkaman Ninja !/n Love Linux !Love ARM ! Love KeKe !/n");
}
module_init(hello_init);
module_exit(hello_exit);
实验结果是 :
[Tekkaman2440@SBC2440V4]#cd /lib/modules/
[Tekkaman2440@SBC2440V4]#ls
cs89x0.ko hello.ko prism2_usb.ko
hello-param.ko p80211.ko
[Tekkaman2440@SBC2440V4]#insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1
(0) Hello, KeKe !
(1) Hello, KeKe !
TNparam[0] : 4
TNparam[1] : 3
TNparam[2] : 2
TNparam[3] : 1
TNparam[4] : 1836543848
TNparam[5] : 7958113
TNparam[6] : 1836017783
TNparam[7] : 0
[Tekkaman2440@SBC2440V4]#insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1,5,6,7,8
TNparam: can only take 4 arguments
hello_param: `4' invalid for parameter `TNparam'
insmod: cannot insert 'hello-param.ko': Invalid parameters (-1): Invalid argument
[Tekkaman2440@SBC2440V4]#
我这个实验除了对参数的改变进行实验外,我的一个重要的目的是测试“ module_param_array(TNparam , int , &TNparam_nr , S_IRUGO);”中&TNparam_nr对输入参数数目的限制作用。经过我的实验,表明&TNparam_nr并没有对输入参数的数目起到限制作用。真正起到限制作用的是“static int TNparam[] = {1,2,3,4};”本身定义的大小,我将程式进行修改:
static int TNparam[] = {1,2,3,4};
改为 static int TNparam[] = {1,2,3,4,5,6,7,8};
其他都不变。
编译后再进行实验,其结果是:
[Tekkaman2440@SBC2440V4]#insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1,5,6,7,8
(0) Hello, KeKe !
(1) Hello, KeKe !
TNparam[0] : 4
TNparam[1] : 3
TNparam[2] : 2
TNparam[3] : 1
TNparam[4] : 5
TNparam[5] : 6
TNparam[6] : 7
TNparam[7] : 8
[Tekkaman2440@SBC2440V4]#
(15)“#include linux/sched.h>” 最重要的头文档之一。包含驱动程式使用的大部分内核API的定义,包括睡眠函数连同各种变量声明。
(16)“#include linux/version.h>” 包含所构造内核版本信息的头文档。
在学习过程中找到了几篇很好的参考文档:
(1)第一章 模块(Modules) URL:http://greenlinux.blogcn.com/diary,103232026.shtml
(2)《从 2.4 到 2.6:Linux 内核可装载模块机制的改变对设备驱动的影响》
URL:http://www.ibm.com/developerworks/cn/linux/l-module26/
(3)《Linux2.6内核驱动移植参考》
URL:http://blog.chinaunix.net/u1/40912/showart_377391.html
以上就是我对《Linux设备驱动程式(第3版)》的《第二章 构造和运行模块》 的学习总结。
Makefile解析:
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /home/linux/linux-2.6.22.6
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY:
modules modules_install clean else obj-m := myhello.o
endif
其中有一些是我们常见或是见过的,第一个 ifeq ($(KERNELRELEASE),)目前,并无 用处,它的由来是指在 Linux 源码根目录下的 Makefile 编译内核时,KERNELRELEASE 宏会被定义,那么如果是从源码根目录开始的 make 则会将 myhello.o 模块编译进内核。
这个 KERNELDIR ?= /home/linux/linux-2.6.22.6, 这句是对 KERNELDIR 进行赋值, 变量是后面我们用到的指代内核源码目录用的。
PWD := $(shell pwd),这句是对 PWD 变量进行赋值,作用是将$(shell pwd)的返回 结果既求得当前目录的路径赋值给 PWD,这个变量我们在后面指代我们要编译的驱动程序 所在的位置。
modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 这句是 Makefile 的规则:这里的$(MAKE)就相当于 make,-C 选项的作用是指将当前 工作目录转移到你所指定的位置。“M=”选项的作用是,当用户需要以某个内核为基础编译 一个外部模块的话,需要在 make modules 命令中加入“M=dir”,程序会自动到你所指定 的 dir 目录中查找模块源码,将其编译,生成 KO 文件。
modules_install: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install 这个命令是模块的安装,在 Makefile 中搜索“lib\/modules”可以看到下面的语句,通 过阅读你不难找到这个“MODLIB”的用处,它是用来指定安装路径的,而变量 “INSTALL_MOD_PATH”往往为空。
MODLIB = $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE) Export MODLIB
.PHONY: modules modules_install clean 这句话是的作用是保证 modules,modules_install,clean 这三个命令能正常完 成。.PHONY 这是一个特殊目标名称,还有其它的,如: .SUFFIXES,.DEFAULT,.PRECIOUS,.INTERMEDIATE,.SECONDARY,.SEC , , , , , ONDEXPANSION,.DELETE_ON_ERROR,.IGNORE .LOW_RESOLUTION_TIME .SI , , LENT .EXPORT_ALL_VARIABLES .NOTPARALLEL 它们的具体用法可以参考 GNU 手册中的 Special Built-in Target Names 章节。 .PHONY 目标的具体意思是如果在 Makefile 的工作目录中有名如: modules,modules_install,clean 等文件时命令会出错。它是防止这出错的方式。