分类: LINUX
2009-04-06 15:24:57
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/
#针对fedora9环境下
#如果KERNELDIR没有被定义过,那么变量KERNELDIR的值就是/usr/src/kernels/
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
#/lib/modules/
下的makefile中KERNELRELEASE已定义
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的目标为modules或modules_install时,-C $(KERNELDIR)指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。
驱动基础
我们通过分析上面的代码来了解一个驱动程序的基本概念。
· 头文件
就像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含Kernel头文件,大多的Linux驱动程序需要包含下面三个头文件:
#include |
·
· init.h 定义了驱动的初始化和退出相关的函数,
· kernel.h 定义了经常用到的函数原型及宏定义
· module.h 定义了内核模块相关的函数、变量及宏。
· 初始化
任何一个驱动都去需要提供一个初始化函数,当驱动加载到内核中时,这个初始化函数就会被自动执行,初始化的函数原型定义如下:
typedef int (*initcall_t)(void); |
驱动程序是通过module_init宏来声明初始化函数的:
static int __init hello_init(void) |
__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) |
同__init类似,如果驱动被编译进内核,则__exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作。显然,__init和__exit对动态加载的模块是无效的。
· 版权信息
Linux内核是按照GPL发布的,同样Linux的驱动程序也要提供版权信息,否则当加载到内核中是系统会给出警告信息。Hello World例子中的版权信息是GPL。
MODULE_LICENSE("GPL"); |