1.设置测试系统:
①设置一套内核源码树,比如/usr/src/linux-2.6.x,参考
eg:uname -r --->2.6.32-27-generic
wget
②直接用发行版的内核源码包,比如/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)
- {
- printk(KERN_ALERT"hello,world.\n");
- return 0;
- }
- static void hello_exit(void)
- {
- printk(KERN_ALERT"Goodbye,cruel world.\n");
- }
- module_init(hello_init);
- module_exit(hello_exit);
Makefile文件
- #如果已定义KERNELRELEASE,则说明是从内核构造系统调用的
- #因此可利用其内建语句
- ifneq ($(KERNELRELEASE),)
- obj-m := hello.o
- #否则,是直接从命令行调用的,这是要调用内核构造系统
- else
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- PWD := $(shell pwd)
- default:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- endif
make编译,然后用insmod安装,用dmesg可以在终端看到输出信息,或者输出到某个日志文件里比如/var/log/messages.
linux的模块并不复杂,真正的困难在于理解设备并最大化其性能
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.通过层叠技术,可以将模块划分为多个层,通过简化每个层,可缩短开发时间。
8.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表示初始化函数执行完之后就释放内存
- {
- printk(KERN_ALERT"hello,world.\n");
- return 0;
- }
- static void __exit hello_exit(void) //__exit表示在卸载是执行,编译器把这类函数放在特殊的ELF段中
- {
- printk(KERN_ALERT"Goodbye,cruel world.\n");
- }
- module_init(hello_init);
- module_exit(hello_exit);//
- 9.初始化过程中的错误处理
- 如果注册设施时遇到任何错误,首先判断模块是否可以继续初始化,通常,在某个注册失败后可以通过降低功能来继续运行。因此,只要可能,模块应该继续向前并尽可能提供功能。
- 若发生的特定类型错误之后无法继续装载模块,则出错之前的任何注册工作都要撤销(未撤销,内核可能不稳定)。
- 一个典型的错误处理模板
-
- int __init my_init_function(void)
- {
- int err;
- err = register_this(ptr1,"skull");
- if (err)
- goto fail_this;
- err = register_that(ptr2,"skull");
- if (err)
- goto fail_that;
- err = 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)
- {
- unregister_those(ptr3,"skull");
- unregister_that(ptr2,"skull");
- unregister_this(ptr1"skull");
- return;
- }
- 一个典型的清除函数模板
-
- struct something * item1;
- struct something * item2;
- 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(arg);
- item2 = allocate_thing2(arg2);
- if (!item1||!item2)
- goto fail;
- err = register_stuff(item1,item2);
- if (!err)
- stuff_ok = 1;
- else goto fail;
- retrun 0;
- fail:
- my_cleanup();
- 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的内核代码
阅读(127) | 评论(0) | 转发(0) |