




已阅读5页,还剩208页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
中软国际内部教材之基于ARM的LINUX设备驱动1目录第一章设备驱动程序开发基础511设备驱动程序的作用512从无操作系统的设备驱动到有操作系统的设备驱动5121无操作系统的设备驱动5122有操作系统的设备驱动713LINUX内核模块10131HELLOWORLD模块10132LINUX内核模块的程序结构11133内核模块的编译13134内核模块的加载和卸载15135模块参数17136模块导出符号17137模块的使用计数1814LINUX设备驱动19141LINUX设备驱动的分类19142设备号19143设备文件20144LINUX设备文件与设备驱动21第二章LINUX字符设备驱动程序2321字符设备驱动简介2322重要的数据结构24221FILE_OPERATIONS结构24222FILE结构体28223INODE结构3023字符设备驱动编写32231设备号的分配和释放33232字符设备的注册34233FILE_OPERATIONS结构体中函数的实现3623设备驱动的编译和加载4024设备驱动程序的测试4025开机自动创建设备节点41第三章简单的字符设备驱动试验4331S3C2440GPIO简介4332I/O内存读取4333蜂鸣器驱动试验44331蜂鸣器硬件电路45332蜂鸣器驱动程序设计45333蜂鸣器测试程序的设计4834LED驱动试验49341LED硬件电路49342利用LINUX内核提供的接口设置寄存器50中软国际内部教材之基于ARM的LINUX设备驱动2343LED驱动程序设计52333蜂鸣器测试程序的设计54第四章LINUX设备驱动的并发与竞争5741KBUF的缺陷5742并发与竞态5743中断屏蔽5844原子操作58441整型原子操作58442位原子操作6045内核锁60451自旋锁60452读写自旋锁62453顺序锁6446信号量65454信号量与自旋锁的区别67第五章阻塞与非阻塞I/O6951阻塞与非阻塞I/O介绍6952等待队列6953在KBUF实例汇总加入阻塞与非阻塞I/O7154对KBUF阻塞与非阻塞I/O的测试7255轮询操作74551设备驱动中POLL方法的实现74552在KBUF中加入对读轮询的支持75553在用户空间验证KBUF设备的读轮询76第六章中断处理7861中断的基本概念7862LINUX中断编程7863中断的/PROC接口8064中断实例81641硬件资源81642代码实现82643在用户空间测试中断8465中断的顶半部和底半部84651TASKLET85652工作队列8666中断共享87第七章LINUX设备模型8971SYSFS文件系统8972设备模型的构建93721KOBJECT93722KSET99723KOBJ_ATTRIBUTE102724MODULE的创建108725MODULE的撤销111中软国际内部教材之基于ARM的LINUX设备驱动3第八章输入子系统11381输入子系统架构OVERVIEW113811主要数据结构113812输入子系统架构示例图11482输入链路的创建过程114821硬件设备的注册114822EVENTHANDLER层11683设备的打开和读写118831OPEN118832READ119833WRITE11984INPUT子系统实例119第九章网络设备驱动程序开发13591网络协议原理135911TCP/IP协议135912报文的处理过程139913网卡与网卡驱动140914网络传输实例局域网内UDP报文发送和接收141915IP地址与MAC地址142916网络调试工具WIRESHARK14392DM9000芯片操作143921硬件连接143922DM9000的操作方式144923初始化DM9000网卡芯片145924DM9000发送和接受数据14693UDP协议149931报文格式149932UDP协议包的封装和发送153933UDP协议包的接收和解析155934测试方法15794ARP报文的发送和接受157941ARP协议简介157942ARP协议包的封装159943ARP协议报的接受和发送159944测试方法16095ICMP报文接收和发送160951ICMP协议简介160952ICMP协议包的接收和发送161第十章USB设备驱动程序开发163101USB系统概述1631011USB总线的发展1631012USB总线的特点1631013USB总线规范1641014USB设备概述164中软国际内部教材之基于ARM的LINUX设备驱动41015USB系统模型1641016USB硬件系统总体结构1651017USB系统层次结构166102USB主机1671021USB主机端构成1681022客户软件1681022USB系统软件169103USB集线器171104USB设备1721041USB设备的逻辑结构1721042USB设备物理结构1731043USB数据传输1741044USB协议栈框架175105LINUX下USB系统架构1761051USB系统文件节点1761052USB驱动结构1771053LINUX下USB驱动程序数据传输流程178106LINUX下USB设备驱动程序开发1791061LINUX下USB设备驱动概述1791062USB设备基础1801063USBURB1821063编写USB设备驱动程序194第十一章内核定时器和内核延时206111时钟中断和JIFFIES计数器206112内核定时器206113内核延时211中软国际内部教材之基于ARM的LINUX设备驱动5第一章设备驱动程序开发基础11设备驱动程序的作用计算机系统是由硬件和软件构成的,硬件是躯体,软件是灵魂。没有硬件的软件是空中楼阁,没有软件的硬件是一堆废铁。软件较灵活,硬件较固定,软件工程师通过代码完成了各种功能以满足客户的需求。但软件的执行的最终都会落实到硬件的行为上,因此,软件和硬件之间必然存在某种联系。为了尽可能的缩短产品的开发周期,软硬件之间不应该相互渗透。也就是说,软件工程师看到的是纯软件的东西,而硬件工程师也不必关心上层的代码如何编写,这样软件硬件工程就能专注于自己的模块,加快产品开发的速度。比如一个简单的蜂鸣器实验,硬件工程师只需要设计一个电路并在硬件层次提供一个控制蜂鸣器的接口,硬件工程师以文档的形式说明往某个寄存器里写1蜂鸣器会响,往这个寄存器里写0蜂鸣器就停;而软件软件工程是也应该只知道一个纯软件的接口,调用RING_ON蜂鸣器就响,调用RING_OFF蜂鸣器就停。那么,函数调用(RING_ON、RING_OFF)和操作寄存器是怎么联系起来的,这个任务就交给了设备驱动,由驱动工程师来完成。设备驱动的作用就是通过访问硬件工程师提供的硬件接口来完成对硬件的控制,同时为上层的应用程序提供一个纯软件的接口。设备驱动程序是连接应用程序和物理硬件的纽带,正是有了设备驱动的存在,应用软件工程师在设计软件时才能不必去关心具体的硬件细节,而硬件工程师也不必顾及上层的软件。12从无操作系统的设备驱动到有操作系统的设备驱动121无操作系统的设备驱动一些应用到简单领域的计算机系统是不需要操作系统的,比如电冰箱、微波炉、空调等。在无操作系统时,设备驱动程序主要实现两个任务,即向下完成对硬件的访问和控制,向上为上层的应用程序提供接口。设备驱动工程师可以完全按照自己的规范向应用程序工程师提供函数接口,只要设备驱动工程师向上层的应用程序工程师提供了这些接口,那么应用程序工程师就能够通过这些接口利用我们的设备驱动,从而完成对设备的控制。中软国际内部教材之基于ARM的LINUX设备驱动6比如,一个简单的蜂鸣器实验,设备驱动驱动工程师完全可以按照这样的框架编写设备驱动程序。/RINGH/INTRING_ONVOIDINTRING_OFFVOID/RINGC/INTRING_ONVOID/控制硬件,使蜂鸣器响/INTRING_OFFVOID/控制硬件,使蜂鸣器停/驱动程序工程师可以将自己的驱动程序编译成静态库或动态库,只向应用程序工程师提供头文件,应用程序工程师就可以通过这些函数调用完成对蜂鸣器的控制。测试代码实例如下/RING_TESTC/INCLUDEINTMAINVOIDINTRET,IFORI0IINCLUDEMODULE_LICENSE“DUALDSB/GPL“STATICINTPARAM0STATICINT_INITHELLO_INITVOIDPRINTKKERN_ALERT“HELLO,WORLDN“PRINTKKERN_ALERT“PARAMDN“,PARAMRETURN0STATICVOID_EXITHELLO_EXITVOIDPRINTKKERN_ALERT“GOODBYE,CRUELWORLDN“MODULE_INITHELLO_INITMODULE_EXITHELLO_EXITMODULE_AUTHOR“SAIF”MODULE_DESCRIPTION“ASIMPLEHELLOWORDLMODULE”PAGE11OF213MODULE_ALIAS“ASIMPLESTMODULE”这个简单的内核模块只包括了内核加载函数、卸载函数和许可权限的声明和一些描述信息。“LINUX/INITH、LINUX/MODULEH”头文件是所有模块都需要的头文件。MODULE_LICENSE“DUALDSB/GPL”是内核该模块所采用的许可协议。PRINTK的用法和用户空间的PRINTF的用法类似,PRINTK和PRINTF的用法相同,但可以定义输出的级别。PRINTK可以作为一种最基本的内核调试手段。KERN_ALERT的作用是告知内核该打印消息的优先级,具体的优先级情况如下表所示MODULE_INITHELLO_INIT的作用是告诉系统,当该模块被加载时,首先执行HELLO_INIT这个函数;MODULE_EXITHELLO_EXIT的作用是告诉系统,当该模块被卸载时,执行HELLO_EXIT这个函数。132LINUX内核模块的程序结构一个内核模块主要有一下几部分组成模块加载函数函数原型为STATICINT_INITINITIALIZATION_FUNCTIONVOID/初始化代码/MODULE_INITINITIALIZATION_FUNCTIONLINUX内核模块注册函数必须用“MODULE_INIT函数名”的形式指定。LINUX内核模块注册函数没有参数,返回值为INT型,若初始化成功返回0,初始化失败返回错误码。在LINUX内核中错误码是一个负值(在中定义),包含PAGE12OF213ENODEV、ENOMEM之类的符号值。在LINUX内核编程中返回相应的错误码是一个非常好的习惯,只有返回了错误码,应用程序才能调用PERROR等函数判断错误类型。_INIT表明此函数只能在模块加载是执行,用_INIT标识的函数在连接时会放在INITTEXT段内,此外,在INITCALLINIT段中还保留了一份函数指针,在初始化时内核会通过INITCALLINIT段中的函数指针调用保存在INITTEXT段中的函数,并在初始化完成后释放INIT区段(包括INITTEXT,INITCALLINIT等)。在模块加载函数中一般实现必要的初始化操作。模块卸载函数函数原型为STATICVOID_EXITCLEANUP_FUNCTIONVOID/释放代码/MODULE_INITCLEANUP_FUNCTION模块卸载函数的参数和返回值都为VOID。_EXIT同_INIT,表示函数在运行完后自动回收内存。模块卸载函数中实现对在模块注册函数中申请资源的回收,完成和模块注册函数相反的操作。模块许可声明模块许可声明(LICENCE)用来描述内核的许可权限,如果不加模块许可声明,模块加载时会有内核被污染(KERNELTAINTED)的警告。内核可接受的LICENSE包括“GPL”、“GPLV2”、“GPLANDADDITIONALRIGHTS”、“DUALBSD/GPL”、“DUALMPL/GPL”、“PROPRIETARY”。大多数情况下,内核模块应遵循GPL兼容许可权,最常见的是用MODULE_LICENSE“DUALDSB/GPL“声明模块采用BSD/GPL双许可。模块参数(可选)模块参数是指在模块加载的时候可以传给它值得模块全局变量。关于模块参数我们将在后面详细讲解。模块导出符号(可选)内核模块可以导出符号,这样其他模块就可以使用本模块的变量和函数。关于模块导出符号将在后面详细讲解。PAGE13OF213模块声明与描述(可选)LINUX内核提供了一系列的宏用来声明模块的作者、描述、版本、设备名和别名等。MODULE_AUTHORAUTHORMODULE_DESCRIPTIONDESCRIPTIONMODULE_VERSIONVERSION_STRINGMODULE_DEVICE_TABLETABLE_INFOMODULE_ALIASALTERNATE_NAME关于MODULE_DEVICE_TABLETABLE_INFO会在讲解USB设备驱动时详细讲解。133内核模块的编译LINUX设备驱动是于内核模块的形式存在的,因此,掌握LINUX内核模块的编译方法是编写LINUX设备驱动编程的必备知识。LINUX内核模块可以在编译内核时把代码加入到内核里面和内核一块儿编译,也可以单独编译。接下来就与我们自己实现的HELLOWORLD模块为实例来详细的讲解这两种编译方法。在动手编译之前首先要搭建好编译环境。内核模块编译环境的搭建内核源码的准备编译LINUX内核模块时必须要有LINUX内源码,而且内核源码的版本必须要和内核模块运行环境的版本一致,否则,模块在编译时可能不会出错,在运行时会出现意想不到的错误。如果,内核模块的运行环境是本机,LINUX个发行版本一般已经包含了内核源码,将不用去准备内核源码。我们将在本机和开发板上分别测试HELLOWORLD模块。笔者本机环境为UBUNTU1004,开发班上运行的内核版本为26287。因此,首先要在准备LINUX26287源码。读者可以到WWWKERNELORG网站上下载内核源码,把下载到的内核源码解压到/OPT目录下(如何解压请参考相关资料)。配置内核并编译,确保编译后的内核映像可以在开发板上正常运行,关于配置编译内核在这里不详细介绍,请读者参考相关资料。交叉编译器的准备因为内核模块模块需要在ARM体系机构的开发上运行,因此需要交叉编译工具,笔者的开发版基于S3C2440处理区,S3C2440是一款三星的基于ARM920T核的处理器,属于ARMV4版本,因此,我们需要一个ARMV4版本的交叉编译器,读者可以用网上下载,也可以自己制作(请参考相关资料)。把获得的交叉编译器解压到/OPT/VIRTARM目录下。单独编译内核模块为了编译内核模块,我们需要编写一个MAKEFILE,如下所示PAGE14OF213第4行HELLOO必须对应内核模块源码的文件名HELLOC。当内核模块源码不止一个文件是需要将第3行的注释去掉。第3行的FILE1O、FILE2O分别对应内核模块源码的文件名。第3行的HELLO必须和第4行的HELLOO一致。第6行和第7行指定内核源码的路径。第7行通过SHELL环境变量指定了本机内核源码的路径,实际内核源码保存在/USR/SRC/路径下。实际编译过程中只需要修改第3行、第4行和第6行。第6行和第7行根据模块的运行环境选中其中的某一行。运行MAKE命令,结果如下从中可以看出,编译时先进入LINUX的内核目录,并编译出HELLOO文件。运行MODPOST会生成临时的HELLOMODC文件,而后根据此文件编译出HELLOMODO,之后连接HELLOO和HELLOMODO文件得到模块目标文件HELLOKO,最后离开LINUX内核所在的目录。把内核模块编译进内核我们自己编写的内核模块程序也可以加入到内核源码和内核一块儿编译。下面以笔者的开发环境为例详细讲解把内核模块编译进内核的过程。PAGE15OF2131把我们自己编写的内核代码HELLOC拷贝到LINUX26287/DRIVER/CHAR/目录下2在DRIVER/CHAR/KCONFIG文件的适当位置加入CONFIGHELLOTRISTATE“SPECIALCHARDRIVERSUPPORT“DEPENDSONDEFAULTYHELPTHISIFFORMYCHARTESTONLYFORSAIFIFYOUABOUTUSETHISDRIVERPLEASESELECTIT,TOCOMPILETHISDRIVERASAMODULE,CHOOSEMHERETHEMODULEWILLBECALLEDSTALLIONDEFAULTSETTINGTHISTOYKCONFIG文件的CONFIGHELLO会在当前目录下MAKEFILE中有相应宏的替换,如OBJCONFIG_HELLO;TRISTATE或BOOL后的“”里的内容会显示在配置菜单中。TRISTATE表示三模式(不编译、编译进内核、编译成模块)选择,BOOL表示两模式(编译进内核、不编译)先择;DEPENDON模块的依赖关系;DEFAULTY默认直接编译为内核;HELP相关语句,会出现在HELP菜单中。注意命令前要使用TALBLE键3在DRIVER/CHAR/MAKEFILE文件中加入OBJCONFIG_HELLOHELLOO注意1CONFIG_HELLO和加入KCONFIG中的HELLO一致。2HELLOO与驱动源码文件名相同,后缀不同。4重新配置内核,配置SPECIALCHARDERIVERSUPORT选项选择将驱动程序编译进内核。选择M把驱动编译成模块。选择空格不编译该驱动程序。5编译内核把驱动编译进内核,系统启动时自动加载驱动模块。编译成模块需要手动加载KO模块。134内核模块的加载和卸载把内核模块编译称模块后需要手动加载到系统,而且可以从系统中动态的卸载。PAGE16OF213加载内核模块用命令INSMOD或MODPROBE。执行INSMOD后可执行DMESG命令查看内核在加载模块时打印的信息。加载HELLOWORLD模块的信息如下图所示从上图中可以看出运行INSMOD命令时内核模块的加载函数HELLO_INIT被执行。用MODPROBE加载模块时会自动检测该模块依赖的其他模块,并自动加载,在嵌入式产品中一般不需要建立模块间的依赖关系,所以MODPROBE命令不常用。可以用LSMOD命令查看当前系统中有那些模块被加载。卸载模块时执行RMMOD命令,卸载HELLOWORLD模块的信息如下图所示从图中可以看出执行RMMOD命令时内核模块的卸载函数HELLO_EXIT被执行。PAGE17OF213135模块参数模块参数是指在模块加载的时候可以传给它值得模块全局变量。加载时用“INSMODMODPROBE模块名参数名参数值”的形式给模块参数传值,如果加载时不传值,模块参数将使用模块定义是的默认值。模块参数需要用“MODULE_PARAM参数名,参数类型,参数读/写权限”的形式声明,例如在HELLOWORLD模块中加入“MODULE_PARAMPARAM,INT,S_IRUGO”把参数PARAM生命为模块参数,在加在模块是给参数PARAM传值,如下图所示内核模块支持的参数类型有BYTE、SHORT、USHORT、INT、UINT、LONG、ULONG、CHARP字符指针、BOOL或INVBOOL布尔的反,在模块被编译时会将MODULE_PARAM中声明的类型与变量定义的类型进行比较,判断是否一致。模块参数也可以指定为数组,形式为“MODULE_PARAM_ARRAY数组名,数组类型,数组长,参数读/写权限”。需将数组长变量的指针赋给“数组长”参数,当不需要保存实际输入的数组元素个数时,可以设置“数组长”为NULL。加载模块时给数组赋值应该使用逗号分割输入的数组元素。模块加载后,在/SYS/MODULE/目录下将出现以模块名命名的目录。当“参数读/写权限”为0时,表示此函数不存在SYSFS文件系统下对应的文件节点,如果此模块存在“参数读/写权限”不为0的命令行参数,在此模块的目录下还将出现PARAMETERS目录,包含一系列以参数名命名的文件节点,这些文件的权限就是传入MODULE_PARAM的“参数读/写权限”,而文件的内容为参数的值。136模块导出符号内核模块可以导出符号,这样其他模块就可以使用本模块的变量和函数。LINUX26内核的“/PROC/KALLSYMS”文件对应着内核符号表,它记录了符号以及符号所在的内存地址。在内核模块中可以使用一下宏导出符号到内核符号表EXPORT_SYMBOL符号名EXPORT_SYMBOL_GPL符号名EXPORT_SYMBOL宏导出的导出的符号可以被其他模块使用;EXPORT_SYMBOL_GPL只适用于包含GPL许可权限的模块。一个简单的模块符号导出实例INCLUDEPAGE18OF213INCLUDEMODULE_LICENSE“DUALDSB/GPL“INTMY_ADDINTX,INTYRETURNXYINTMY_SUBINTX,INTYRETURNXYEXPORT_SYMBOLMY_ADDEXPORT_SYMBOL_GPLMY_SUB从“/PROC/KALLSYMS”文件中查看该模块的导出符号信息137模块的使用计数在LINUX内核中有一种机制用来记录内核模块被使用的计数,有了这个机制内核模块就不会在正在使用的情况下被卸载,LINUX26内核提供了两个接口函数来管理模块的使用计数INTTRY_MODULE_GETSTRUCTMODULEMODULEVOIDMODULE_PUTSTRUCTMODULEMODULEINTTRY_MODULE_GETSTRUCTMODULEMODULE函数用于增加模块使用计数,若函数返回值为0,表示调用失败,失败的原因是希望使用的模块没有被加载或正在被卸载。VOIDMODULE_PUTSTRUCTMODULEMODULE函数用于减少模块的实用计数。LINUX26内核为不同类型的设备定义了STRUCTMODULEOWNER域,用来指向管理此设备PAGE19OF213的模块。当开始使用这个设备时,内核使用TRY_MODULE_GETDEVOWNER增加管理此设备的OWNER模块的实用计数,当不使用此设备时,内核使用MODULE_PUTDEVOWNER减少管理此设备的OWNER模块的使用计数。这样,在设备被使用时,管理此设备的模块允许卸载,只有模块使用计数为0时该模块才允许被卸载。在LINUX26内核中,对驱动工程师而言,很少需要亲自调用TRY_MODULE_GET、MODULE_PUT管理模块的使用计数。在LINUX26内核中驱动工程师所写的驱动通常为支持具体设备的OWMER模块,对此设备OWNER模块的计数管理由内核更底层的代码来实现,驱动工程师只需要将DEVOWNER初始化成THIS_MODULE指针即可。14LINUX设备驱动141LINUX设备驱动的分类LINUX系统将设备分成三种类型字符设备块设备网络设备字符设备字符设备是个能够像字节流一样访问的设备,大多数字符设备是个只能顺序访问的数据通道,不能前后移动访问指针。比如串口驱动,只能顺序的读写设备。然而,也存在和数据区或者文件特性类似的字符设备,访问它们时可前后移动访问指针。块设备块设备通常是按照块为单位来访问数据,一块通常是几KBYTES或者是1BYTE的2次幂,如128BYTES或者4KBYTES。块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核和驱动程序的接口不同。块设备除了给内核提供和字符设备一样的接口外,还提供了专门面向块设备的接口,块设备的接口必须支持挂装文件系统,通过此接口,块设备能够容纳文件系统,因此应用程序一般通过文件系统来访问块设备上的内容。网络设备网络设备驱动不同于字符设备和块设备,不在/DEV下以文件节点为代表,而是通过单独的网络接口来代表。任何网络事务都要经过一个网络接口,即一个能够和其它主机交换数据的设备。通常接口代表一个硬件设备如网卡,但也可能是个纯软件设备。内核和网络驱动程序间的通讯完全不同于内核和字符设备以及块设备驱动程序之间的通信,内核调用一套和数据包传输相关的函数。内核提供SOCKET接口来访问网络设备。PAGE20OF213142设备号LINUX系统是利用主次设备号把上层的系统调用和底层的驱动一一对应的。主设备号把驱动程序和外部设备对应起来,每种类型的设备需要对应一个主设备号,系统通过主设备号把应用程序和启动程序关联起来;次设备号表示了驱动程序控制的设备实例,如不同的串口使用同一个驱动程序,主设备号相同而次设备号不同。主次设备号是由设备驱动程序指定的,可静态制定,也可动态分配,静态制定的设备号不能与系统已使用的设备号冲突。关于设备好的申请与分配将在后面详细讲解。在内核中,用DEV_T类型(在中定义)来保存设备编号,包含主设备号和次设备号。在LINUX26内核中,DEV_T是32位的变量,高12位用来保存主设备号,低20位用来保存次设备号。驱动工程师不应该直接给DEV_T结构的变量赋值,因为,随着内核发展DEV_T结构可能会有变换。为了保证代码的可移植性我们应该使用在中定义的及格宏来操作DEV_T机构。MKDEVINTMAJOR,INTMINORMAJORDEV_TDEVMINORDEV_TDEVMKDEVINTMAJOR,INTMINOR用来把主次设备号转化成DEV_T结构的设备号。MAJORDEV_TDEV、MINORDEV_TDEV分别用来从DEV_T结构的设备号中获得主设备号和次设备号。143设备文件LINUX系统用户态的程序是通过设备文件来通过设备驱动访问具体的设备的。设备文件通常也成为设备节点,一般位于/DEV目录下。用LS/DEVL命令查看/DEV目录下的设备节点信息从实例总可以看出设备节点的格式为CRWRWRW1ROOTROOT4,0201006301708TTY0第一个字母表示设备类型,C代表字符设备,B代表块设备。PAGE21OF2134和0分别是主设备号和次设备号。最后一个字段TTY0是设备名称。设备文件需要手动创建,也可以在驱动程序用内核提供的函数创建。手动创建用命令MKNODMAKNOD设备名设备类型主设备号次设备号例MAKNODTTYS0C641驱动程序应该用下面这些函数来进行设备文件的创建和删除工作。/创建设备目录/DEVFS_HANDLE_TDEVFS_MK_DIRDEVFS_HANDLE_TDIR,CONSTCHARNAME,VOIDINFO/创建设备文件/DEVFS_HANDLE_TDEVFS_REGISTERDEVFS_HANDLE_TDIR,CONSTCHARNAME,UNSINGEDINTFLAGS,UNSIGNEDINTMAJOR,UNSIGNEDINTMINOR,UMODE_TMODE,VOIDOPS,VOIDINFO/撤销设备文件/VOIDDEVFS_UNREGISTERDEVFS_HANDLE_TDE144LINUX设备文件与设备驱动图示串口驱动的设备文件和设备驱动PAGE22OF213以串口设备为例,如上图所示最上层的应用程序通过设备文件来完成对串口的读写,因此,在应用程序层对设备的访问和对普通文件的访问完全一样。虚拟文件系统(VFS)为应用程序层提供了标准的系统调用,如OPEN、CLOSE、READ、WRITE等。设备节点包括主次设备号等信息,通过主设备号可以找到注册在CHARDEVICELIST中的设备驱动;通过次设备号确定要访问的硬件设备。注册在CHARDEVICELIST中的设备驱动SERIALDRIVER实现对硬件的访问。PAGE23OF213第二章LINUX字符设备驱动程序21字符设备驱动简介字符设备驱动是嵌入式LINUX最基本、也是应用最多的驱动程序。它的功能非常强大,几乎可以描述不涉及挂载文件系统的所有设备。字符设备驱动程序的架构相对比较简单,是LINUX设备驱动学习的重点。本书主要以字符设备驱动动为模型来讲解驱动程序中用到知识要点,例如并发与竞态、阻塞I/O与非阻塞I/O、内核定时器和中断处理等。学好字符设备驱动程序是继续学习其他类型设备驱动程序的必备条件。LINUX字符设备驱动程序接口流程图从上图中可以看出,应用程序通过标准系统调用(OPEN、CLOSE、READ、WRITE等)访问设备文件;内核中的FILE_OPRATIONS结构将标准的系统调用和驱动程序自己实现的函数PAGE24OF213一一对应。例如执行系统调用OPEN会关联到驱动程序的DEVICE_OPEN函数执行。编写字符设备驱动主要工作就是在一定规则下实现自己的DEVICE_OPEN等函数。当然,在写第一个字符设备驱动程序前还需要掌握一些必要的基础知识。22重要的数据结构与无操作系统时的设备驱动程序不同,LINUX系统的设备驱动程序是向内核提供接口的,函数原型必须是内核认可的,而且还要按照内核的规范向内核注册自己,因此,LINUX设备驱动要用到内核定义的数据结构。221FILE_OPERATIONS结构FILE_OPERATIONS结构定义在中,其中包含了一组函数指针。每一个函数指针对应一个上层的系统调用。FILE_OPERATIONS的清单如下STRUCTFILE_OPERATIONSSTRUCTMODULEOWNERLOFF_TLLSEEKSTRUCTFILE,LOFF_T,INTSSIZE_TREADSTRUCTFILE,CHAR_USER,SIZE_T,LOFF_TSSIZE_TWRITESTRUCTFILE,CONSTCHAR_USER,SIZE_T,LOFF_TSSIZE_TAIO_READSTRUCTKIOCB,CONSTSTRUCTIOVEC,UNSIGNEDLONG,LOFF_TSSIZE_TAIO_WRITESTRUCTKIOCB,CONSTSTRUCTIOVEC,UNSIGNEDLONG,LOFF_TINTREADDIRSTRUCTFILE,VOID,FILLDIR_TUNSIGNEDINTPOLLSTRUCTFILE,STRUCTPOLL_TABLE_STRUCTINTIOCTLSTRUCTINODE,STRUCTFILE,UNSIGNEDINT,UNSIGNEDLONGLONGUNLOCKED_IOCTLSTRUCTFILE,UNSIGNEDINT,UNSIGNEDLONGLONGCOMPAT_IOCTLSTRUCTFILE,UNSIGNEDINT,UNSIGNEDLONGINTMMAPSTRUCTFILE,STRUCTVM_AREA_STRUCTINTOPENSTRUCTINODE,STRUCTFILEINTFLUSHSTRUCTFILE,FL_OWNER_TIDINTRELEASESTRUCTINODE,STRUCTFILEINTFSYNCSTRUCTFILE,STRUCTDENTRY,INTDATASYNCINTAIO_FSYNCSTRUCTKIOCB,INTDATASYNCINTFASYNCINT,STRUCTFILE,INTINTLOCKSTRUCTFILE,INT,STRUCTFILE_LOCKSSIZE_TSENDPAGESTRUCTFILE,STRUCTPAGE,INT,SIZE_T,LOFF_T,INTUNSIGNEDLONGGET_UNMAPPED_AREASTRUCTFILE,UNSIGNEDLONG,UNSIGNEDLONG,PAGE25OF213UNSIGNEDLONG,UNSIGNEDLONGINTCHECK_FLAGSINTINTDIR_NOTIFYSTRUCTFILEFILP,UNSIGNEDLONGARGINTFLOCKSTRUCTFILE,INT,STRUCTFILE_LOCKSSIZE_TSPLICE_WRITESTRUCTPIPE_INODE_INFO,STRUCTFILE,LOFF_T,SIZE_T,UNSIGNEDINTSSIZE_TSPLICE_READSTRUCTFILE,LOFF_T,STRUCTPIPE_INODE_INFO,SIZE_T,UNSIGNEDINTINTSETLEASESTRUCTFILE,LONG,STRUCTFILE_LOCKFILE_OPERATIONS结构包含很多字段,每一个字段都必须指向驱动程序中实现特定操作的函数,对于不支持的操作,对应的字段可以置为NULL值。OWNER字段一般置为THIS_MODULE值,以实现内核对管理该设备驱动的模块的计数。FILE_OPERATIONS结构重要字段的含义STRUCTMODULEOWNER指向拥有这个结构的模块的指针,内核使用这个模块以避免在模块的操作正在被使用时卸载该模块。通常该字段被初始化成THIS_MODULE,它是在中定义的一个宏。LOFF_TLLSEEKSTRUCTFILE,LOFF_T,INTLLSEEK方法用来修改文件的当前读写位置,对应LSEEK系统调用。成功返回新的位置(与文件头的偏移量)返回,失败返回赋值;函数的返回值是一个长整型数,即使在32位的机器上也占64位的数据宽度。如果该字段是NULL,对SEEK的调用将会以不可意料的方式修改FILE结构在“FILE结构”一节中描述中的位置计数器。SSIZE_TREADSTRUCTFILE,CHAR_USER,SIZE_T,LOFF_T该字段对应READ系统调用,用来设备中读取数据。函数成功返回实际读到的字节数,失败返回负的错误码。若该字段为NULL,将导致READ系统调用出错并返回EINVAL(INVALIDARGUMENT)。SSIZE_TAIO_READSTRUCTKIOCB,CHAR_USER,SIZE_T,LOFF_T对系统调用异步读取的支持,即在读操作没用完成前函数可以返回。若该字段为NULL,则所有的读操作都同多READ(同步)处理。SSIZE_TWRITESTRUCTFILE,CONSTCHAR_USER,SIZE_T,LOFF_TPAGE26OF213该字段对应WRITE系统调用,用来向设备写数据。函数成功返回实际写的字节数,失败返回负的错误码。若该字段为NULL,将导致WRITE系统调用出错并返回EINVAL(INVALIDARGUMENT)。SSIZE_TAIO_WRITESTRUCTKIOCB,CONSTCHAR_USER,SIZE_T,LOFF_T对系统调用异步写的支持,即在写操作没用完成前函数可以返回。若该字段为NULL,则所有的读操作都同多WRITE(同步)处理。INTREADDIRSTRUCTFILE,VOID,FILLDIR_T对于设备文件,这个字段应该为NULL。它用来读取目录,仅对文件系统有用。UNSIGNEDINTPOLLSTRUCTFILE,STRUCTPOLL_TABLE_STRUCTPOLL方法是3个系统调用POLL,EPOLL和SELECT的底层支持。POLL方法应该返回一个位掩码,用来指出是否能够非阻塞的读取或写入,并且也会向内核提供将调用进程置于休眠直到I/O变为可能时的信息。如果该字段为NULL,则设备会认为既可读也可写,并且不会被阻塞。INTIOCTLSTRUCTINODE,STRUCTFILE,UNSIGNEDINT,UNSIGNEDLONG该字段对应IOCTL系统调用,用来完成对硬件的控制。用户空间的IOCTL原型为INTIOCTLINTD,INTREQUEST,。系统调用IOCTL函数的第二个参数REQUEST会传给驱动程序IOCTL方法的第三个函数,系统调用IOCTL的变参会传给驱动程序IOCTL方法的第四个函数。另外,内核还能识别一部分IOCTL命令,而不必调用我们实现的IOCTL方法。若给字段为NULL,IOCTL系统调用会返回ENOTTR(NOSUCHIOCTLFORDEVICE,该设备无此IOCTL命令)的错误码。INTMMAPSTRUCTFILE,STRUCTVM_AREA_STRUCT该方法对应MMAP系统调用,用来请求将设备内存映射到进程的地址空间。若该字段为NULL,MMAP系统调用返回ENODEV。INTOPENSTRUCTINODE,STRUCTFILE该方法对用OPEN系统调用,尽管这常常是对设备文件进行的第一个操作,但并不要求驱动程序一定要实现OPEN方法。若该字段为NULL,打开设备总会成功,但是驱动程序不会得到通知。INTFLUSHSTRUCTFILE在进程关闭设备文件副本时将调用此方法。该方法应该执行(并等待)设备上尚未完成的操作。若该字段为NULL,内核将简单的忽略用户应用程序的请求。INTRELEASESTRUCTINODE,STRUCTFILEPAGE27OF213当FILE结构被释放时,将调用这个操作。与OPEN相似,也可以将该字段置为NULL。INTFASYNCINT,STRUCTFILE,INT该方法对应FSYNC系统调用,用户使用它来刷新待处理的数据。若该字段为NULL,FSYNC系统调用将返回EINVAL。INTAIO_FSYNCSTRUCTKIOCB,INT该方法是FSYNC的异步版本。INTLOCKSTRUCTFILE,INT,STRUCTFILE_LOCKLOCK方法用来实现文件加锁。加锁对常规文件是必不可少的特性,但是设备驱动几乎从不实现该方法。SSIZE_TREADVSTRUCTFILE,CONSTSTRUCTIOVEC,UNSIGNEDLONG,LOFF_T该方法对应READV系统调用,可实现对多个内存区域进行单次的读操作而不必进行额外的数据拷贝工作。若字段为NULL,就会调用READ方法(可能多次)。SSIZE_TWRITEVSTRUCTFILE,CONSTSTRUCTIOVEC,UNSIGNEDLONG,LOFF_T该方法对应WRITEV系统调用,可实现对多个内存区域进行单次的写操作而不必进行额外的数据拷贝工作。若字段为NULL,就会调用WRITE方法(可能多次)。SSIZE_TSENDFILESTRUCTFILE,LOFF_T,SIZE_T,READ_ACTOR_T,VOID这个方法实现SENDFILE系统调用的读取部分。SENDFILE系统调用以最小的复制操作将数据从一个文件描述符移动到另一个文件描述符。例如,WEB服务器可以利用这个方法将某个文件的内容到网络连接。设备驱动程序中该字段通常为NULL。SSIZE_TSENDPAGESTRUCTFILE,STRUCTPAGE,INT,SIZE_T,LOFF_T,INTSENDPAGE是SENDFILE系统调用的另一半,内核调用该方法用来将数据发送发到对应的文件,每次一个数据页。设备驱动程序中该字段通常为NULL。UNSIGNEDLONGGET_UNMAPPED_AREASTRUCTFILE,UNSIGNEDLONG,UNSIGNEDLONG,UNSIGNEDLONG,UNSIGNEDLONG这个方法的目的是在进程的地址空间找一个合适的位置,以便将底层设备中的内存映射到该位置。这个任务通常由内存管理代码完成,但该法可允许驱动程序强制满足特定设备需要的任何对齐需求。设备驱动程序中该字段通常为NULL。INTCHECK_FLAGSINTPAGE28OF213这个方法允许模块检查传递给FNCTLF_SETFL调用的标志。INTDIR_NOTIFYSTRUCTFILE,UNSIGNEDLONG将应用程序使用FCNTL来请求目录改变通知时,该方法将被调用。该方法仅对文件系统有用。设备驱动程序中该字段通常为NULL。在字符设备驱动中一般直需要实现OPEN、RELEASE、READ、WRITE和IOCTL方法。实现方法如下所示STRUCTFILE_OPERATIONSDEVICE_FOPSOWNERDEVICE_MODULE,LLSEEKDEVICE_LLSEEK,READDEVICE_READ,WRITEDEVICE_WRITE,IOCTLDEVICE_IOCTL,OPENDEVICE_OPEN,RELEASEDEVICE_RELEASE,222FILE结构体FILE结构体在中定义,清单如下STRUCTFILE/FU_LISTBECOMESINVALIDAFTERFILE_FREEISCALLEDANDQUEUEDVIAFU_RCUHEADFORRCUFREEING/UNIONSTRUCTLIST_HEADFU_LISTSTRUCTRCU_HEADFU_RCUHEADF_USTRUCTPATHF_PATHDEFINEF_DENTRYF_PATHDENTRYDEFINEF_VFSMNTF_PATHMNTCONSTSTRUCTFILE_OPERATIONSF_OPATOMIC_LONG_TF_COUNTUNSIGNEDINTF_FLAGSFMODE_TF_MODELOFF_TF_POSPAGE29OF213STRUCTFOWN_STRUCTF_OWNERUNSIGNEDINTF_UID,F_GIDSTRUCTFILE_RA_STATEF_RAU64F_VERSIONIFDEFCONFIG_SECURITYVOIDF_SECURITYENDIF/NEEDEDFORTTYDRIVER,ANDMAYBEOTHERS/VOIDPRIVATE_DATAIFDEFCONFIG_EPOLL/USEDBYFS/EVENTPOLLCTOLINKALLTHEHOOKSTOTHISFILE/STRUCTLIST_HEADF_EP_LINKSSPINLOCK_TF_EP_LOCKENDIF/IFDEFCONFIG_EPOLL/STRUCTADDRESS_SPACEF_MAPPINGIFDEFCONFIG_DEBUG_WRITECOUNTUNSIGNEDLONGF_MNT_WRITE_STATEENDIF注意,内核空间的FILE结构和用户空间的FILE结构没有任何关系。FILE是在C库中定义的,不会出现在内核空间;而STRUCTFILE是由内核定义的结构体,它不会出现在用户空间。每调用一次OPEN函数会创建一个FILE结构,不仅是设备驱动,系统中每打开一个文件也是如此。内核会把该数据结构传递给在该文件进行操作的所有函数,直到CLOSE函数被调用该结构体才会被释放。STRUCTFILE结构体的主要成员罗列如下MODE_TF_MODE文件模式。通过位FMODE_READ和FMODE_WRITE来表示文件是否可读或可写(或可读写)。内核在调用驱动程序的READ和WRITE之前已经检查了访问权限,在对没有相应权限的文件进行读写时将会被拒接,因此驱动程序无需对该字段做额外的判断。LOFF_TF_POSPAGE30OF213当前读写位置。驱动程序一般只读取这个值而不去修改它。READ/WRITE方法会使用它们接受到的最后那个指针来更新这个字段,而不是直接修改。LLSEEK方法可以直接修改该字段。UNSIGNEDINTF_FLAGS文件标志。包括O_RDONLY,O_NONBLOCK和O_SYNC等(这些标志在中定义)。驱动
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025四川省水电投资经营集团有限公司所属电力公司员工招聘6人备考练习试题及答案解析
- 2025云南玉溪市江川区医共体招聘编制外人员9人备考练习题库及答案解析
- 2025山西运城绛县社区工作者招聘10人备考练习试题及答案解析
- 2025山东潍坊市青州圣维科技高中教师招聘3人备考练习试题及答案解析
- 2025年西安交通工程学院招聘(30人)备考练习题库及答案解析
- 2025年精神科抑郁症诊断与治疗综合考试答案及解析
- 2025年外科学术研究方法论考察答案及解析
- 2025年疼痛科学知识及镇痛技术综合测试答案及解析
- 2025年肾脏病学尿液分析与疾病诊断模拟考试卷答案及解析
- 2025年白花前胡乙素行业研究报告及未来行业发展趋势预测
- 大唐集团两票管理制度
- 《天然植物化学成分与抗肿瘤机制》课件
- 肿瘤危重症护理从急救到安宁的全程管理2025
- 自行车比赛课件
- 开利30HXY-HXC螺杆冷水机组开机、运行维护手册
- 儿童暴发性心肌炎诊治专家建议(2025)解读课件
- 托育服务政策法规与职业伦理 课件全套 黄鑫 第1-8章 绪论、托育服务政策法规概述-托育职业伦理教育、修养与评价
- 3.2《做自尊的人》课件-2024-2025学年统编版道德与法治七年级下册
- 全陪导游工作流程
- 高层次人才引进协议合同范本
- 2025年心理辅导:声音疗愈《听听声音》课件设计
评论
0/150
提交评论