Chinaunix首页 | 论坛 | 博客
  • 博客访问: 545435
  • 博文数量: 101
  • 博客积分: 1889
  • 博客等级: 上尉
  • 技术积分: 906
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-14 16:22
文章分类

全部博文(101)

文章存档

2012年(11)

2011年(19)

2010年(59)

2009年(12)

我的朋友

分类: LINUX

2009-11-10 17:26:26

hello.c:

#include
#include
#include

 

#define DRIVER_AUTHOR "Foo bar"
#define DRIVER_DESC   "A sample driver"

MODULE_LICENSE ("GPL");
MODULE_AUTHOR (DRIVER_AUTHOR);
MODULE_DESCRIPTION (DRIVER_DESC);
MODULE_SUPPORTED_DEVICE ("TestDevice");

static int __init hello_2_init (void)
{
 printk (KERN_INFO "Hello world\n");
 return 0;
}

static void __exit hello_2_exit (void)
{
 printk (KERN_INFO "Goodbye world\n");
}

module_init (hello_2_init);
module_exit (hello_2_exit);

 

Makefile:

ifeq ($(KERNELRELEASE),)

#KERNELDIR ?= /usr/src/kernels/2.6.25-14.fc9-i686

#针对fedora9环境下

#如果KERNELDIR没有被定义过,那么变量KERNELDIR的值就是/usr/src/kernels/2.6.25-14.fc9-i686,如果KERNELDIR先前被定义过,那么这条语将什么也不做。

KERNELDIR ?= /lib/modules/$(shell uname -r)/build 

#/lib/modules/2.6.25-14.fc9-i686/build

下的makefileKERNELRELEASE已定义

PWD := $(shell pwd) #makefile潜规则已经定义,可以去掉

#:==,使用:=可以防止一些不可预知的情况。

如:

A = $(B)

B = $(A)

这会让make陷入无限的变量展开过程中去,当然,我们的make是有能力检测这样的定义,并会报错。还有就是如果在变量中使用函数,那么,这种方式会让我们的make运行时非常慢,更糟糕的是,他会使用得两个make的函数“wildcard”和“shell”发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。

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

#.PHONY意思表示clean是一个“伪目标”,。而在rf命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后

else
    obj-m := hello.o

#obj-m := 这个赋值语句的含义说明要使用目标文件hello.o建立一个模块,最后生成的模块的名字就是hello.ko
endif

解释如下:

KERNELRELEASE 是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容。如果make的目标是clean,直接执行clean操作,然后结束。当make的目标为modulesmodules_install时,-C $(KERNELDIR)指明跳转到内核源码目录下读取那里的MakefileM=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。

 

驱动基础
我们通过分析上面的代码来了解一个驱动程序的基本概念。

·         头文件

就像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含Kernel头文件,大多的Linux驱动程序需要包含下面三个头文件:

#include
#include
#include

·    

·    init.h 定义了驱动的初始化和退出相关的函数,

·    kernel.h 定义了经常用到的函数原型及宏定义

·    module.h 定义了内核模块相关的函数、变量及宏。

·         初始化

任何一个驱动都去需要提供一个初始化函数,当驱动加载到内核中时,这个初始化函数就会被自动执行,初始化的函数原型定义如下:

 typedef int (*initcall_t)(void);


驱动程序是通过module_init宏来声明初始化函数的:

static int __init hello_init(void)
{
    printk(KERN_ALERT "Hello World
\n");
    return 0;
}
module_init(hello_init);


__init
宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,这样当函数初始化完成后这个区域可以被清除掉以节约系统内存。Kenrel启动时看到的消息“Freeing unused kernel memory: xxxk freed”同它有关。
初始化函数是有返回值的,只有在初始化成功是才返回0,否则返回错误码(errno)。

·         卸载

如果驱动程序编译成模块(动态加载)模式,那么它需要一个清理函数。当移除一个内核模块时这个函数被调用执行清理工作。清理函数的函数原型定义为:

 typedef void (*exitcall_t)(void);


驱动程序是通过module_exit宏来声明清理函数的:

static void __exit hello_exit(void)
{
    printk(KERN_ALERT "Goodbye World
\n");
}
module_exit(hello_exit);

__init类似,如果驱动被编译进内核,则__exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作。显然,__init__exit对动态加载的模块是无效的。

·         版权信息

Linux内核是按照GPL发布的,同样Linux的驱动程序也要提供版权信息,否则当加载到内核中是系统会给出警告信息。Hello World例子中的版权信息是GPL

  MODULE_LICENSE("GPL");

阅读(2168) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~