Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4241772
  • 博文数量: 176
  • 博客积分: 10059
  • 博客等级: 上将
  • 技术积分: 4681
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-24 12:27
文章分类

全部博文(176)

文章存档

2012年(1)

2011年(4)

2010年(14)

2009年(71)

2008年(103)

分类: LINUX

2008-07-08 20:47:21

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


//hello.c

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
    printk(KERN_ALERT "Hello world, zieckey!\n");
    return 0;
}

static void hello_exit(void)
{
    printk(KERN_ALERT "Goodbye, zieckey!\n");
}

module_init(hello_init);
module_exit(hello_exit);


为该驱动模块编写一个makefile文件:

# Makefile
ifneq ($(KERNELRELEASE),)
  obj-m := -DEXPORT_SYMTAB
  obj-m := hello.o
else

# your kernel dir

KERNELDIR ?= /usr/src/kernels/2.6.21-1.3194.fc7-i686
PWD := $(shell pwd)
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
    rm *.o *.mod.c *.symvers *.ko
endif


注意: Makefile文件的文件名一定要是这个“Makefile”,不能是小写的“makefile“,不知道什么原因。

编译:

[root@localhost helloworld]# make
make -C /usr/src/kernels/2.6.21-1.3194.fc7-i686 M=/home/work/device_dev/helloworld modules
make[1]: Entering directory `/usr/src/kernels/2.6.21-1.3194.fc7-i686'
  Building modules, stage 2.
  MODPOST 1 modules
make[1]: Leaving directory `/usr/src/kernels/2.6.21-1.3194.fc7-i686'

You have new mail in /var/spool/mail/root
[root@localhost helloworld]# ls
hello.c hello.ko hello.mod.c hello.mod.o hello.o Makefile Module.symvers
[root@localhost helloworld]#


加载该驱动:

[root@localhost helloworld]# insmod ./hello.ko
Hello world, zieckey


打印一行信息,该字符串由程序中的 hello_init 函数实现的。

查看驱动:

[root@localhost helloworld]# lsmod
Module Size Used by
hello 5504 0
zaurus 9409 0
cdc_ether 10049 1 zaurus

....


卸载该驱动:

[root@localhost helloworld]# rmmod hello
Goodbye,


打印一行信息,该字符串由程序中的 hello_exit 函数实现的。

如果在 XWindow 下面,会看不到内核输出,可以切换到文本界面下查看。

以下是网上资料:
http://blog.csdn.net/xdxiaodao/archive/2008/05/22/2470963.aspx

学习心得:
(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
#include

(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;
}


(15)“#include ”  最重要的头文件之一。包含驱动程序使用的大部分内核API的定义,包括睡眠函数以及各种变量声明。

(16)“#include ” 包含所构造内核版本信息的头文件。

在学习过程中找到了几篇很好的参考文档:
(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
阅读(2937) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~