全部博文(57)
分类: 嵌入式
2010-02-28 00:24:17
第一个例程:hello.c
|
引导之前某些东西就会残留在系统中;
5、 处理器的多种工作模式(级别)其实就是为了操作系统的用户空间和内核空间设计的。在Unix类的操作系统中只用到了两个级别:最高和最低级别;
6、 要注意驱动程序的并发处理;
7、 内核API中具有双下划线(_ _)的函数,通常是接口的底层组件,应慎用;
8、 Makefile文件分析
ifneq ($(KERNELRELEASE),)判断是否在内核根目录下,也就是判断该Makefile是否在内核目录树中,从内核顶层的Makefile可以看到KERNELRELEASE的定义为
# Read KERNELRELEASE from include/config/kernel.release (if it exists)
KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null 在看看include/config/kernel.release里面是什么东西
[root@localhost linux-2.6.25.8]# cat include/config/kernel.release 2.6.25.8
include/config/kernel.release里面放的是该内核的版本号,也就是说如果存在include/config/kernel.release这个文件,就把内核版本号给KERNELRELEASE。如果版本号就从
内核目录树编译。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变量中设定的模块。
说明:在一个典型的构造过程中,Makefile将被调用2次,第一次是构造内核目录树,
# If KERNELRELEASE is defined, we’ve been invoked from the
# kernel build system and can use its language.
FILENAME = hello
ifneq ($(KERNELRELEASE),)
obj-m := $(FILENAME).o
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
Else
KERNELDIR ?= /root/kernel/linux-2.6.25.8/
到此即可找到内核目录树,此时KERNELRELEASE已被定义
第二次调用时发现KERNELRELEASE已被定义,那么才定义obj-m := $(FILENAME).o这个模块,然后才执行$(MAKE) –C $(KERNELDIR) M=$(PWD) modules
第一次看的时候发现个问题,如果Makefile只调用一次,那么未定义KERNELRELEASE的时候obj-m := $(FILENAME).o这个模块就不能增加,后来仔细阅读了LDD3才发现一个典型的
构造Makefile需要被调用2次。这才解决了疑问。
9、 insmod使用公共内核符号表来解析模块中未定义的符号。公共内核符号表中包含了所有的全局内核项(即函数和变量的地址),这是实现模块化驱动程序所必须的;
10、 Linux使用模块层叠技术,我们可以将模块划分为多个层,通过简化每个层可缩短开发周期。如果一个模块需要向其他模块导出符号,则使用下面的宏:
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
符号必须在模块文件的全局变量部分导出,因为这两个宏将被扩展为一个特殊变量的声明,而该变量必须是全局的;
11、 所有模块代码中都包含一下两个头文件:
#include
#include
12、 所有模块代码都应该指定所使用的许可证:
MODULE_LICENSE(“Dual BSD/GPL”);
此外还有可选的其他描述性定义:
MODULE_AUTHOR(“”); 声明谁编写了模块
MODULE_DESCRIPTION(“”); 一个人可读的关于模块做什么的声明
MODULE_VERSION(“”); 一个代码修订版本号
MODULE_ALIAS(“”); 模块为人所知的另一个名子
MODULE_DEVICE_TABLE(“”); 来告知用户空间, 模块支持那些设备
上述MODULE_声明习惯上放在文件最后
13、 初始化和关闭
初始化
static int __init initialization_function(void)
{
/* Initialization code here */
}
module_init(initialization_function);
清除函数
static void __exit cleanup_function(void)
{
/* Cleanup code here */
}
module_exit(cleanup_function);
这里首次接触在模块内使用static void __exit cleanup_function(void),说明static的一种用法:1)在模块内,一个被声明为静态的函数只可被这一模块内的其他函数调用。
那就是,这个函数被限制在声明他的模块的本地范围内使用;
另外两种用法;
2)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
3) 在模块内(但在函数体外),一个被声明为静态的变量能够被模块内所用函数访问,但不能被模块外其他函数访问。他是个本地的全局变量。
14、 错误恢复有时用 goto 语句处理是最好的. 我们通常不愿使用 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、 模块参数: 内核允许对驱动程序指定参数,而这些参数可在装载驱动程序模块时改变。以下是我的实验程序:
|
136不传递参数执行后:
[root@gfy-S3C2440 /tmp]# insmod hello.ko
0array[0] = a
1array[1] = b
2array[2] = c
3array[3] = d
Test string
whom = world
Hello, world,I am gufeiyang!
2)传递参数执行后:
[root@gfy-S3C2440 /tmp]# insmod hello.ko whom=gufeiyang howmany=6
0array[0] = a
1array[1] = b
2array[2] = c
3array[3] = d
4array[4] =
5array[5] =
Test string
whom = gufeiyang
Hello, world,I am gufeiyang!
说明:以上主要测试了module_param_array(name,type,num,perm); num这个参数没有起到限制提供给用户参数的个数,而是array实际长度确定,正如Tekkaman Ninja证实那样
。
Perm字段是一个权限值,需使用
快速参考:以下摘自《linux设备驱动程序(第三版)》第一章:快速参考
本节总结了我们在本章接触到的内核函数, 变量, 宏定义, 和 /proc 文件. 它的用意是作为一个参考. 每一项列都在相关头文件的后面, 如果有. 从这里开始, 在几乎每章的结
尾会有类似一节, 总结一章中介绍的新符号. 本节中的项通常以在本章中出现的顺序排列:
insmod
modprobe
rmmod
用户空间工具, 加载模块到运行中的内核以及去除它们.
#include
module_init(init_function);
module_exit(cleanup_function);
指定模块的初始化和清理函数的宏定义.
__init
__initdata
__exit
__exitdata
函数( __init 和 __exit )和数据 (__initdata 和 __exitdata)的标记, 只用在模块初始化或者清理时间. 为初始化所标识的项可能会在初始化完成后丢弃; 退出的项可能被丢
弃如果内核没有配置模块卸载. 这些标记通过使相关的目标在可执行文件的特定的 ELF 节里被替换来工作.
#include
最重要的头文件中的一个. 这个文件包含很多驱动使用的内核 API 的定义, 包括睡眠函数和许多变量声明.
Struct task_struct *current;
当前进程.
Current->pid
current->comm
进程 ID 和 当前进程的命令名.
Obj-m
一个 makefile 符号, 内核建立系统用来决定当前目录下的哪个模块应当被建立.
/sys/module
/proc/modules
/sys/module 是一个 sysfs 目录层次, 包含当前加载模块的信息. /proc/moudles 是旧式的, 那种信息的单个文件版本. 其中的条目包含了模块名, 每个模块占用的内存数量, 以及使用计数. 另外的字串追加到每行的末尾来指定标志, 对这个模块当前是活动的.
Vermagic.o
来自内核源码目录的目标文件, 描述一个模块为之建立的环境.
#include
必需的头文件. 它必须在一个模块源码中包含.
#include
头文件, 包含在建立的内核版本信息.
LINUX_VERSION_CODE
整型宏定义, 对 #ifdef 版本依赖有用.
EXPORT_SYMBOL (symbol);
EXPORT_SYMBOL_GPL (symbol);
宏定义, 用来输出一个符号给内核. 第 2 种形式输出没有版本信息, 第 3 种限制输出给 GPL 许可的模块.
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
放置文档在目标文件的模块中.
Module_init(init_function);
module_exit(exit_function);
宏定义, 声明一个模块的初始化和清理函数.
#include
module_param(variable, type, perm);
宏定义, 创建模块参数, 可以被用户在模块加载时调整( 或者在启动时间, 对于内嵌代码). 类型可以是 bool, charp, int, invbool, short, ushort, uint, ulong, 或者 intarray.
#include
int printk(const char * fmt, …);
内核代码的 printf 类似物。