Chinaunix首页 | 论坛 | 博客
  • 博客访问: 186519
  • 博文数量: 87
  • 博客积分: 2975
  • 博客等级: 少校
  • 技术积分: 926
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-24 17:14
文章分类

全部博文(87)

文章存档

2010年(87)

我的朋友

分类: LINUX

2010-07-24 21:22:33

一个学习Linux设备驱动程序都会碰到的第一个例程:

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
    printk(KERN_ALERT "Hello, apple!\n");
    return 0;
}
static void hello_exit(void)
{
    printk(KERN_ALERT "Goodbye, apple!\n Love Linux !Love ARM ! Love my wife !
\n"
);
}
module_init(hello_init);
module_exit(hello_exit);


编写了一个简单的Makefile文件:

ifneq ($(KERNELRELEASE),)

obj-m := hello.o

else
KDIR := /home/study/Kernel/2.6.29

CROSS_COMPILE = /usr/local/arm/4.3.2/bin/arm-linux-

all:
        $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
        rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c.tmp_versions

.PHONY:modules clean

endif          

注意:在内核目录中,我使用我系统的内核,编译就成功,但是使用我自己解压的,编译就提示错误说找不到目录,后来经过我一位同学的提醒,应该把我目录的内核编译后,才可以!!!
然后就是make。就会在开发板的lib目录下生成需要加载的模块。

[root@localhost hello]# make
make -C /usr/src/kernels/2.6.18-53.el5-i686 M=/home/study/driver/L2/hello modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
  CC [M]  /home/study/driver/L2/hello/hello.o
  Building modules, stage 2.
  MODPOST
  CC      /home/study/driver/L2/hello/hello.mod.o
  LD [M]  /home/study/driver/L2/hello/hello.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'


在我的开发板上的操作:

[root@FriendlyARM 2.6.29.4-FriendlyARM]# ls                                             
hello.ko                                                                                
[root@FriendlyARM 2.6.29.4-FriendlyARM]# insmod hello.ko                                
Hello,aplle!                                                                            
[root@FriendlyARM 2.6.29.4-FriendlyARM]# lsmod                                          
hello 1108 0 - Live 0xbf000000                                                          
[root@FriendlyARM 2.6.29.4-FriendlyARM]# rmmod hello                                    
Goodbye,apple!                                                                          
 Love Linux!Love ARM!Love my wife                                                           
[root@FriendlyARM 2.6.29.4-FriendlyARM]# 

注意:友善之臂的板子必须把驱动模块放到/lib/modules/2.6.29.4-FriendlyARM目录下才可以移除内核所以我新建了2.6.29.4-FriendlyARM目录,并把内核模块放到该目录下。

学习心得:
(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 ......
 
KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义, 所以make将读取执行else之后的内容。如果make的目标是clean,直接执行clean操作,然后结束。当make的目标为all时,-C $(KERNELDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。mymodule-objs := file1.o file2.o表示mymoudule.o 由file1.o与file2.o 连接生成。obj-m := mymodule.o表示编译连接后将生成mymodule.o模块。
(8)insmod使用公共内核符号表来解析模块中未定义的符号。公共内核符号表中包含了所有的全局内核项(即函数和变量的地址),这是实现模块化驱动程序所必须的。modprobe和insmod类似,但是它会考虑要装载的模块是否引用了当前内核不存在的符号,如果引用,modprobe会在当前模块搜索路径中查找定义了的这些符号的其它模块,如果找到,就装载。
(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语句仍是最好的错误恢复机制。以下是初始化出错处理的推荐代码示例:

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
#include
#include

MODULE_LICENSE("Dual BSD/GPL");

static char *whom = "apple";
static int howmany = 1;
static int param[] = {1,2,3,4};
static int param_nr = 4;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
module_param_array(param, int, ¶m_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 < 8; i++)
      printk(KERN_ALERT "param[%d]: %d \n",i, param[i]);
   return 0;
}

static void hello_exit(void)
{
    printk(KERN_ALERT "Goodbye,apple ! \n Love Linux !Love ARM !Love my wife !\n");
}

module_init(hello_init);
module_exit(hello_exit);


实验结果是 :

[root@FriendlyARM 2.6.29.4-FriendlyARM]# insmod hello.ko howmany=2 whom="my wife        
"                                                                                       
(0) Hello, my wife  !                                                                   
(1) Hello, my wife  !                                                                   
param[0]: 1                                                                             
param[1]: 2                                                                             
param[2]: 3                                                                             
param[3]: 4                                                                             
param[4]: 4                                                                             
param[5]: 1                                                                             
param[6]: -1069724280                                                                   
param[7]: -1069724280                                                                   
[root@FriendlyARM 2.6.29.4-FriendlyARM]# rmmod hello
Goodbye,apple !                                                                         
 Love Linux !Love ARM !Love my wife !                                                   
[root@FriendlyARM 2.6.29.4-FriendlyARM]#


我这个实验除了对参数的改变进行实验外,我的一个重要的目的是测试“ module_param_array(param , int , ¶m_nr , S_IRUGO);”中¶m_nr对输入参数数目的限制作用。经过我的实验,表明¶m_nr并没有对输入参数的数目起到限制作用。真正起到限制作用的是static int param[] = {1,2,3,4};本身定义的大小,我将程序进行修改:
static int param[] = {1,2,3,4}; 
改为
static int param[] = {1,2,3,4,5,6,7,8};
其他都不变。

编译后再进行实验,其结果是:

[root@FriendlyARM 2.6.29.4-FriendlyARM]# insmod hello.ko howmany=2 whom="my wife        
"                                                                                       
(0) Hello, my wife  !                                                                   
(1) Hello, my wife  !                                                                   
param[0]: 1                                                                             
param[1]: 2                                                                             
param[2]: 3                                                                             
param[3]: 4                                                                             
param[4]: 5                                                                             
param[5]: 6                                                                             
param[6]: 7                                                                             
param[7]: 8  

[root@FriendlyARM 2.6.29.4-FriendlyARM]# rmmod hello                                    
Goodbye,apple !                                                                         
 Love Linux !Love ARM !Love my wife !   


(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
阅读(936) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~