Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1694892
  • 博文数量: 174
  • 博客积分: 5493
  • 博客等级: 上校
  • 技术积分: 5802
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-05 15:13
个人简介

炼狱,是为追逐光芒

文章分类

全部博文(174)

文章存档

2017年(1)

2016年(3)

2015年(9)

2014年(5)

2013年(23)

2012年(56)

2011年(45)

2010年(32)

分类: LINUX

2011-02-20 16:28:39

1.设置测试系统:
①设置一套内核源码树,比如/usr/src/linux-2.6.x,参考
eg:uname -r  --->2.6.32-27-generic
wget
②直接用发行版的内核源码包,比如/lib/modules/$(shell uname -r),包含内核目标链接文件。
2.hello world模块
  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. MODULE_LICENSE("Dual BSD/GPL");

  4. static int hello_init(void)
  5. {
  6.     printk(KERN_ALERT"hello,world.\n");
  7.     return 0;
  8. }
  9. static void hello_exit(void)
  10. {
  11.     printk(KERN_ALERT"Goodbye,cruel world.\n");
  12. }
  13. module_init(hello_init);
  14. module_exit(hello_exit);
 Makefile文件
  1. #如果已定义KERNELRELEASE,则说明是从内核构造系统调用的
  2. #因此可利用其内建语句
  3. ifneq ($(KERNELRELEASE),)
  4.     obj-m := hello.o
  5. #否则,是直接从命令行调用的,这是要调用内核构造系统
  6. else
  7.     KERNELDIR ?= /lib/modules/$(shell uname -r)/build
  8.     PWD := $(shell pwd)
  9. default:
  10.     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
  11. 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是一个可以获得这个结构的宏,包含即可引用。
  1. #include <linux/sched.h>

  2. 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,则:
  1. obj-m := module.o
  2. module.o-objs := file1.o file2.o
装载:sudo insmod hello.ko
查看:lsmod
卸载:sudo rmmod hello
7.内核符号表
公共内核符号表中包含了所有的全局内核项(函数和变量)的地址,模块被装载后,它所导出的任何符号都会变成内核符号表的一部分。
模块层叠技术在复杂项目中非常有用,modprobe是处理层叠技术的一个使用工具,功能类似insmod.通过层叠技术,可以将模块划分为多个层,通过简化每个层,可缩短开发时间。
8.hello world模块代码分析
  1. #include <linux/init.h>    //指定初始化和清除函数
  2. #include <linux/module.h>  //包含可装载模块需要的大量符号和函数的定义
  3. //所有模块代码中都包含这两行

  4. MODULE_LICENSE("Dual BSD/GPL");//制定许可证,一般用"GPL"或者"Dual BSD/GPL"

  5. static int __init hello_init(void)     //__init表示初始化函数执行完之后就释放内存
  6. {
  7.     printk(KERN_ALERT"hello,world.\n");
  8.     return 0;
  9. }
  10. static void __exit hello_exit(void) //__exit表示在卸载是执行,编译器把这类函数放在特殊的ELF段中
  11. {
  12.     printk(KERN_ALERT"Goodbye,cruel world.\n");
  13. }
  14. module_init(hello_init);
  15. module_exit(hello_exit);//
  1. 9.初始化过程中的错误处理
  2. 如果注册设施时遇到任何错误,首先判断模块是否可以继续初始化,通常,在某个注册失败后可以通过降低功能来继续运行。因此,只要可能,模块应该继续向前并尽可能提供功能。
  3. 若发生的特定类型错误之后无法继续装载模块,则出错之前的任何注册工作都要撤销(未撤销,内核可能不稳定)。
  4. 一个典型的错误处理模板
    1. int __init my_init_function(void)
    2. {
    3.     int err;

    4.     err = register_this(ptr1,"skull");
    5.     if (err)
    6.         goto fail_this;
    7.     err = register_that(ptr2,"skull");
    8.     if (err)
    9.         goto fail_that;
    10.     err = register_those(ptr3,"skull");
    11.     if (err)
    12.         goto fail_those

    13.     return 0; // success

    14. fail_those: unregister_that(ptr2,"skull");
    15. fail_that:unreister_this(ptr1,"skull");
    16. fail_this: return err;
    17. }
  5. 清除函数撤销所有设施
    1. void __exit my_cleanup_function(void)
    2. {
    3.     unregister_those(ptr3,"skull");
    4.     unregister_that(ptr2,"skull");
    5.     unregister_this(ptr1"skull");
    6.     return;
    7. }
  6. 一个典型的清除函数模板
    1. struct something * item1;
    2. struct something * item2;

    3. void my_cleanup(void)
    4. {
    5.     if (item1)
    6.         release_thing(item1);
    7.     if (item2)
    8.         release_thing2(item2);
    9.     if (stuff_ok)
    10.         unregister_stuff();
    11.     return;
    12. }

    13. int __init my_init(void)
    14. {
    15.     int err = -ENOMEM;

    16.     item1 = allocate_thing(arg);
    17.     item2 = allocate_thing2(arg2);
    18.     if (!item1||!item2)
    19.         goto fail;
    20.     err = register_stuff(item1,item2);
    21.     if (!err)
    22.         stuff_ok = 1;
    23.     else goto fail;
    24.     retrun 0;
    25. fail:
    26.     my_cleanup();
    27.     return err;
    28. }
  7. 这种方式的初始化能够很好地扩展到对大量设施的支持。
  8. 模块装载竞争:在用来支持某个设施的所有内部初始化完成之前,不要注册任何设施。
  9. 10.模块参数
  10. 内核允许对驱动程序指定参数,而这些参数可在装载驱动程序模块时改变。
    1. static char *whom = "world";
    2. static int howmany = 10;
    3. module_param(howmany,int ,S_IRUGO);
    4. module_param(whom,charp,S_IRUGO);
    5. module_param(name,type,num,perm);
    6. name--数组名字
    7. type--数组元素类型
    8. num--数组个数
    9. perm--访问许可值
    10. insmod hello.ko whom=yuyunbo howmany=5
  11. 模块装载器会拒绝接受超过数组大小的值

  12. 本章新符号总结(函数,变量,宏等)
    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的内核代码
阅读(2306) | 评论(0) | 转发(3) |
给主人留下些什么吧!~~