1.设置测试系统:
 
①设置一套内核源码树,比如/usr/src/linux-2.6.x,参考 http://www.kernel.org/
 
eg:uname -r --->2.6.32-27-generic
 
wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.32.27.tar.gz
 
/lib/modules/$(shell uname -r),包含内核目标链接文件。
 
2.hello world模块
 
- #include <linux/init.h>
 - #include <linux/module.h>
 - MODULE_LICENSE("Dual BSD/GPL");
 - static int hello_init(void)
 - {
 - (KERN_ALERT"hello,world.\n");
 - return 0;
 - }
 - static void hello_exit(void)
 - {
 - (KERN_ALERT"Goodbye,cruel world.\n");
 - }
 - module_init(hello_init);
 - module_exit(hello_exit);
 
 
Makefile文件
 
- #如果已定义KERNELRELEASE,则说明是从内核构造系统调用的
 - #因此可利用其内建语句
 - ifneq ($(KERNELRELEASE),)
 - -m := hello.o
 - #否则,是直接从命令行调用的,这是要调用内核构造系统
 - else
 - ?= /lib/modules/$(shell uname -r)/build
 - := $(shell pwd)
 - default:
 - (MAKE) -C $(KERNELDIR) M=$(PWD) modules
 - endif
 
 
make编译,然后用insmod安装,用dmesg可以在终端看到输出信息,或者输出到某个日志文件里比如/var/log/messages.
 
真正的困难在于理解设备并最大化其性能
 
3.内核模块的特点。
 
①模块仅仅被链接到内核,因此能调用的函数仅仅是由内核导出的那些函数,不存在任何可链接的函数库。
 
②内核函数不支持浮点运算。
 
③Unix使用两个级别,内核态和用户态。当处理器有多个级别时,使用最高级别和最低级别。
 
④每当应用程序执行系统调用或者被硬件中断挂起时,Unix从用户空间切换到内核空间。
 
⑤系统调用的代码运行在进程上下文,可以访问进程所有数据。而处理硬件中断的内核代码和进程是异步的,与任一个特定进程无关。
 
⑥一个驱动程序要执行两类任务:模块中的某些函数作为系统调用的一部分执行(实现用户API),而其他函数则负责中断处理。
 
⑦内核具有非常小的栈,可能只有4K大小页。我们自己的函数必须和整个内核空间调用链一同共享这个栈,如果需要大的结构,应该在调用时动态分配。
 
4.内核中的并发
 
linux2.6中内核代码已经是可抢占的。
 
①编写内核代码时,时刻铭记:同一时刻,可能会有许多事情正在发生。
 
②linux内核代码(包括驱动代码)必须是可重入的。
 
5.当前进程current
 
内核代码可通过访问全局项current来获得当前进程,current在 中定义,是一个指向struct task_struct的指针。
 
在2.6中,current不再是一个全局变量,指向task_stuct结构的指针隐藏在内核栈中,current是一个可以获得这个结构的宏,包含 即可引用。
 
- #include <linux/sched.h>
 - printk(KERN_INFO"The process is \"%s\" (pid %i)\n",current->comm,current->pid);
 
 
6.编译和装载
 
对hello world模块,Makefile只要一行就可以了:obj-m := hello.o
 
如果要构造的模块名module.ko由两个源文件生成,file1.c,file2.c,则:
 
- obj-m := module.o
 - module.o-objs := file1.o file2.o
 
 
装载:sudo insmod hello.ko
 
查看:lsmod
 
卸载:sudo rmmod hello
 
7.内核符号表
 
公共内核符号表中包含了所有的全局内核项(函数和变量)的地址,模块被装载后,它所导出的任何符号都会变成内核符号表的一部分。
 
模块层叠技术在复杂项目中非常有用,modprobe是处理层叠技术的一个使用工具,功能类似insmod.通过层叠技术,可以将模块划分为多个层,通过简化每个层,可缩短开发时间。
 
.hello world模块代码分析
 
- #include <linux/init.h> //指定初始化和清除函数
 - #include <linux/module.h> //包含可装载模块需要的大量符号和函数的定义
 - //所有模块代码中都包含这两行
 - MODULE_LICENSE("Dual BSD/GPL");//制定许可证,一般用"GPL"或者"Dual BSD/GPL"
 - static int __init hello_init(void) //__init表示初始化函数执行完之后就释放内存
 - {
 - (KERN_ALERT"hello,world.\n");
 - return 0;
 - }
 - static void __exit hello_exit(void) //__exit表示在卸载是执行,编译器把这类函数放在特殊的ELF段中
 - {
 - (KERN_ALERT"Goodbye,cruel world.\n");
 - }
 - module_init(hello_init);
 - module_exit(hello_exit);//
 
 
- 9.初始化过程中的错误处理
 - 如果注册设施时遇到任何错误,首先判断模块是否可以继续初始化,通常,在某个注册失败后可以通过降低功能来继续运行。因此,只要可能,模块应该继续向前并尽可能提供功能。
 - 若发生的特定类型错误之后无法继续装载模块,则出错之前的任何注册工作都要撤销(未撤销,内核可能不稳定)。
 - 一个典型的错误处理模板
 
- int __init my_init_function(void)
 - {
 - int err;
 - = register_this(ptr1,"skull");
 - if (err)
 - goto fail_this;
 - = register_that(ptr2,"skull");
 - if (err)
 - goto fail_that;
 - = register_those(ptr3,"skull");
 - if (err)
 - goto fail_those
 - return 0; // success
 - fail_those: unregister_that(ptr2,"skull");
 - fail_that:unreister_this(ptr1,"skull");
 - fail_this: return err;
 - }
 
- 清除函数撤销所有设施
 
- void __exit my_cleanup_function(void)
 - {
 - (ptr3,"skull");
 - (ptr2,"skull");
 - (ptr1"skull");
 - return;
 - }
 
- 一个典型的清除函数模板
 
- struct something * item1;
 - struct something * item2;
 - void my_cleanup(void)
 - {
 - if (item1)
 - (item1);
 - if (item2)
 - (item2);
 - if (stuff_ok)
 - ();
 - return;
 - }
 - int __init my_init(void)
 - {
 - int err = -ENOMEM;
 - = allocate_thing(arg);
 - = allocate_thing2(arg2);
 - if (!item1||!item2)
 - goto fail;
 - = register_stuff(item1,item2);
 - if (!err)
 - = 1;
 - else goto fail;
 - ;
 - fail:
 - ();
 - return err;
 - }
 
- 这种方式的初始化能够很好地扩展到对大量设施的支持。
 - 模块装载竞争:在用来支持某个设施的所有内部初始化完成之前,不要注册任何设施。
 - 10.模块参数
 - 内核允许对驱动程序指定参数,而这些参数可在装载驱动程序模块时改变。
 
- static char *whom = "world";
 - static int howmany = 10;
 - module_param(howmany,int ,S_IRUGO);
 - module_param(whom,charp,S_IRUGO);
 - module_param(name,type,num,perm);
 - name--数组名字
 - type--数组元素类型
 - num--数组个数
 - perm--访问许可值
 - insmod hello.ko whom=yuyunbo howmany=5
 
- 模块装载器会拒绝接受超过数组大小的值
 - 本章新符号总结(函数,变量,宏等)
 - insmod,modprobe,rmmod,lsmod,dmesg
 - 用来装载模块到正运行的内核和移除模块的用户空间工具
 - #include
 
module_init(init_function); 
module_exit(cleanup_function);
 
用于指定模块的初始化和清除函数的宏。
 
__init,__initdata,__exit,__exitdata
 
仅用于模块初始化或清除阶段的函数(__init和_exit)和数据(__initdta和__exitdata)标记。
 
#include
 
最重要的头文件之一,该文件包含驱动程序使用的大部分内核API的定义,包括睡眠函数以及各种变量声明。
 
struct task_struct *current;
 
当前进程
 
current->pid
 
current->com
 
当前进程的进程ID和命令名。
 
obj-m
 
由内核构造系统使用的makefile的符号,用来确定当前目录中应构造那些模块
 
/sys/module是sysfs目录层次结构中包含当前已装载模块信息的目录
 
/proc/modules是早期的用法,在单个文件中包含模块名称,每个模块内存总量以及引用计数等。
 
vermagic.o内核源代码目录中的一个目标文件,描述了模块的构造环境。
 
#include
 
必须的头文件,它必须包含在模块源代码中。
 
#include
 
LINUX_VERSION_CODE整数宏,用在处理版本以来的预处理条件语句中。
 
EXPORT_SYMBOL (symbol); 导出单个符号到内核的宏
 
EXPORT_SYMBOL_GPL(symbol);仅用于GPL许可证下的模块
 
MODULE_AUTHOR(author);
 
MODULE_DESCRIPTION(desctiption);
 
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);
 
用来创建函数模块的宏,在装载模块时调整参数
 
#include
 
int printk(const char * fmt,...);
 
函数printf的内核代码
  
 
 阅读(959) | 评论(0) | 转发(3) | 
  
0
 
 
上一篇:ldd3学习之一:设备驱动简介
下一篇:修改ubuntu10.10的对话框图标居右
  










