您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 项目/工程管理 > Linux内核模块编程
Linux内核模块编程主要参考书目•《Linux设备驱动开发详解(第2版)》•宋宝华编著•人民邮电出版社主要参考书目•《Linux设备驱动程序(第三版)》•魏永明耿岳等译•中国电力出版社主要参考书目•《精通Linux驱动程序开发》•Sreekrishnan编著•人民邮电出版社操作系统主要功能:1.CPU管理2.存储管理3.设备管理4.文件管理5.网络与通信管理6.用户接口操作系统裸机应用程序编程接口(系统调用)终端用户操作接口应用程序POSIX表示可移植操作系统接口(PortableOperatingSystemInterface)是为解决应用程序平台移植性提出的一种标准。Linux是什么?•Linux是一个由几百万行源代码组成的庞大、复杂的程序,任何人都能从上下载。•Linux是一套免费的、源代码开放的、符合POSIX标准规范的操作系统。•严格来说,Linux只包含下图中内核与系统调用接口那两层。Linux内核的构成8Linux的虚拟内存管理Linux的虚拟内存管理机制为应用程序和驱动程序提供了两种服务:使每个进程都拥有自己独立的内存地址空间;对于32位Linux而言,每个任务可寻址的内存地址空间都为0x00000000~0xFFFFFFFF(232,4GB)当物理内存不够4GB时,虚拟内存管理模块会用外存空间模拟内存空间,并且该模拟过程对应用程序是透明的。9用户地址空间与内核地址空间1.Linux将每个进程的4GB的独立地址空间又划分为用户地址空间(0x00000000~0xBFFFFFFF)和内核地址空间(0xC0000000~0xFFFFFFFF)两部分。2.操作系统内核代码和数据存放在内核地址空间;每个进程自己私有的代码和数据存放在用户地址空间3.虽然Linux的内核代码和数据被映射到了每个进程的地址空间中(所有进程看到的内容是相同的),但在实际的物理内存中,只有内核代码和数据的一份拷贝。10用户地址空间与内核地址空间虚拟页物理页031null213null虚拟页物理页021null213null虚拟页物理页0null1null213null物理页taken进程id0Nnull1Yos2Y23Y1进程1的页表进程id虚拟页硬盘中的地址进程2的页表进程3的页表内页表外页表CR3寄存器CPU负责查表(虚拟地址-物理地址),查表失败时触发缺页中断(14号);OS负责填充各个表的内容,并提供缺页中断的中断服务器程序。用户态与核心态一般现代CPU都有几种不同的指令执行级别在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态用户态指相应的低级别执行状态,代码的掌控范围会受到限制,只能执行CPU指令集的一个子集举例:intelx86CPU有四种不同的执行级别0-3,Linux只使用了其中的0级和3级分别来表示内核态和用户态0xc0000000以上的内核地址空间只能在内核态下访问,0x00000000-0xbfffffff的用户地址空间在两种状态下都可以访问应用程序可以通过Linux系统调用由用户态进入内核态Linux进程状态•Linux中任务和进程是相同的术语,每个进程由task_struct结构来描述,即PCB(进程控制块)•Linux将进程状态主要分为五种:–TASK_RUNNING–TASK_INTERRUPTIBLE–TASK_UNINTERRUPTIBLE–TASK_STOPPED–TASK_ZOMBILE。•进程的状态随着进程的调度发生改变Linux进程状态转换可装载内核模块为了使系统功能能够更灵活的扩充,Linux支持内核的动态扩展,即在系统运行时给内核增加新的功能(即模块module)。模块(module)是一段可以被动态链接的目标代码(.ko),它可由insmod命令动态的装载并链接到正在运行的内核。链接后,它就成了内核的一部分,直到用rmmod命令解除链接并卸载。Linux驱动程序就是一种特殊的内核模块。内核模块与应用程序的不同内核模块工作在内核空间(supervisorspace),而应用程序工作在用户空间(userspace)内核模块是一个由多个回调函数组成的“被动”代码集合体,采用了“事件驱动模型”;而应用程序总是从头至尾的执行单个任务。内核模块不能调用C标准函数库(glibc库),只能调用linux内核导出的内核函数。内核模块在编程时必须考虑可重入性(reentrant)内核模块可使用的栈很小(一般只有4096字节)。内核模块程序源码的构成头文件#includelinux/init.h#includelinux/module.h必选许可声明MODULE_LICENSE(DualBSD/GPL);必选加载函数staticint__inithello_init(void)必选卸载函数staticvoid__exithello_exit(void)必选模块参数module_param(num,int,S_IRUGO)可选模块导出符号EXPORT_SYMBOL(add_integer)可选模块作者等信息声明MODULE_AUTHOR(“author_name”)可选最简单的KernelModule#includelinux/init.h#includelinux/module.hMODULE_LICENSE(DualBSD/GPL);staticint__inithello_init(void){//这是模块加载函数printk(KERN_ALERTHello,world\n);return0;}staticvoid__exithello_exit(void){//这是模块卸载函数printk(KERN_ALERTGoodbye,cruelworld\n);}module_init(hello_init);module_exit(hello_exit);模块加载函数通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成模块的相关初始化工作,通常包括:(1)向内核注册一些数据结构;(2)申请软硬件资源;(3)初始化硬件模块加载函数必须用宏“module_init”指定,它返回整型值。若初始化成功则返回0,若失败则返回一个负值作为错误码。“__init”和“__exit”都是宏,利用了gcc的扩展关键字,分别要求编译器将所声明函数的目标代码放入“init.text”段和“exit.text”段中(两个特殊的ELF段)。static关键字为了将该函数名的可见性控制在本文件内。#define__init__attribute__((__section__(“.init.text”)))#define__exit__attribute__((__section__(“.exit.text”)))module_init背后的秘密#definemodule_init(x)__initcall(x);#define__initcall(fn)device_initcall(fn)#definedevice_initcall(fn)__define_initcall(6,fn,6)#define__define_initcall(level,fn,id)\staticinitcall_t__initcall_##fn##id__used\__attribute__((__section__(.initcalllevel.init)))=fn所以,module_init(x)最终展开为:staticinitcall_t__initcall_##fn##id__used\__attribute__((__section__(.initcalllevel.init)))=fninitcall_t是一个指向函数的指针类型typedefint(*initcall_t)(void)所以module_init本质上是将一个函数指针变量放在了一名为.initcall6.initELF节中。ExecutableandLinkingFormat模块卸载函数模块卸载函数必须用宏“module_exit”指定,无返回值。当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块装载函数相反的功能。(注销一些内核数据结构,释放资源等)。内核打印函数printkprintk(fmt,args…)级别KERN_EMERG用于紧急消息,常常是那些崩溃前的消息.KERN_ALERT需要立刻动作的情形.KERN_CRIT严重情况,常常与严重的硬件或者软件失效有关.KERN_ERR用来报告错误情况;设备驱动常常使用KERN_ERR来报告硬件故障.KERN_WARNING有问题的情况的警告,这些情况自己不会引起系统的严重问题.KERN_NOTICE正常情况,但是仍然值得注意.在这个级别一些安全相关的情况会报告.KERN_INFO信息型消息.在这个级别,很多驱动在启动时打印它们发现的硬件的信息.KERN_DEBUG用作调试消息.不能打印浮点数printk消息流向/etc/syslog.conf中可配置syslogd的分发规则,例如可以加入:kern.*/tmp/kernel_debug.txt/proc/kernel/printk文件中设置了一个优先级,高于该优先级的消息才能显示到控制台中模块许可证声明模块许可证声(MODULE_LICENSE)明描述内核模块的许可权限如果不声明LICENSE,模块被加载时,将收到内核被污染(kerneltainted)的警告编译内核模块的条件1.已安装了GCC工具链2.有一份内核源码,且至少被编译过一次。3.内核模块程序在编译过程中要使用内核源码的头文件(在include目录)和编译内核时生成的符号文件。内核模块的编译可以编写一个最简单的Makefile:obj-m:=hello.o并采用如下命令编译:make–C/lib/modules/$(shelluname-r)/buildM=$(shellpwd)modules或采用如下较复杂的Makefile:ifneq($(KERNELRELEASE),)#callfromkernelbuildsystemobj-m:=hello.oelseKERNELDIR:=/lib/modules/$(shelluname-r)/buildPWD:=$(shellpwd)default:make-C$(KERNELDIR)M=$(PWD)modulesendif内核模块的编译如果我们想由两个源文件(比如file1.c和file2.c)构造出一个名称为module.ko的模块,则makefile的obj-m变量可如下编写:obj-m:=module.omodule-objs:=file1.ofile2.o内核模块的加载与卸载使用insmod命令或modprobe加载模块:insmod./hello.ko使用rmmod命令卸载模块:rmmodhello使用lsmod命令查看内核中已加载的内核模块的信息通过查看/proc/modules文件也可查看内核中已加载的内核模块的信息。通过查看/sys/module目录也可查看内核中已加载的内核模块的信息。modprobe会考虑要装载的模块是否引用了一些当前内核不存在的符号。如果有这类引用,modprobe会在当前模块路径中搜索定义了这些符号的其他模块,并同时将这些模块也装载到内核。(/lib/modulesmodules.dep/ect/modprob.conf)内核模块参数module_param(参数名,参数类型,参数读/写权限)内核支持的模块参数类型包括:byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool。staticchar*whom=world;s
本文标题:Linux内核模块编程
链接地址:https://www.777doc.com/doc-3392069 .html