0
点赞
收藏
分享

微信扫一扫

ldd3学习之二:构造和运行模块


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模块


  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. MODULE_LICENSE("Dual BSD/GPL");

  4. static int hello_init(void)
  5. {
  6. (KERN_ALERT"hello,world.\n");
  7. return 0;
  8. }
  9. static void hello_exit(void)
  10. {
  11. (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. -m := hello.o
  5. #否则,是直接从命令行调用的,这是要调用内核构造系统
  6. else
  7. ?= /lib/modules/$(shell uname -r)/build
  8. := $(shell pwd)
  9. default:
  10. (MAKE) -C $(KERNELDIR) M=$(PWD) modules
  11. 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是一个可以获得这个结构的宏,包含 即可引用。


  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.通过层叠技术,可以将模块划分为多个层,通过简化每个层,可缩短开发时间。


.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. (KERN_ALERT"hello,world.\n");
  8. return 0;
  9. }
  10. static void __exit hello_exit(void) //__exit表示在卸载是执行,编译器把这类函数放在特殊的ELF段中
  11. {
  12. (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. = register_this(ptr1,"skull");
  5. if (err)
  6. goto fail_this;
  7. = register_that(ptr2,"skull");
  8. if (err)
  9. goto fail_that;
  10. = 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. }
  1. 清除函数撤销所有设施
  1. void __exit my_cleanup_function(void)
  2. {
  3. (ptr3,"skull");
  4. (ptr2,"skull");
  5. (ptr1"skull");
  6. return;
  7. }
  1. 一个典型的清除函数模板
  1. struct something * item1;
  2. struct something * item2;

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

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

  16. = allocate_thing(arg);
  17. = allocate_thing2(arg2);
  18. if (!item1||!item2)
  19. goto fail;
  20. = register_stuff(item1,item2);
  21. if (!err)
  22. = 1;
  23. else goto fail;
  24. ;
  25. fail:
  26. ();
  27. return err;
  28. }
  1. 这种方式的初始化能够很好地扩展到对大量设施的支持。
  2. 模块装载竞争:在用来支持某个设施的所有内部初始化完成之前,不要注册任何设施。
  3. 10.模块参数
  4. 内核允许对驱动程序指定参数,而这些参数可在装载驱动程序模块时改变。
  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
  1. 模块装载器会拒绝接受超过数组大小的值

  2. 本章新符号总结(函数,变量,宏等)
  3. insmod,modprobe,rmmod,lsmod,dmesg
  4. 用来装载模块到正运行的内核和移除模块的用户空间工具
  5. #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的对话框图标居右​​


举报

相关推荐

0 条评论