论LinuKernelModule之设备驱动程序_第1页
论LinuKernelModule之设备驱动程序_第2页
论LinuKernelModule之设备驱动程序_第3页
论LinuKernelModule之设备驱动程序_第4页
论LinuKernelModule之设备驱动程序_第5页
已阅读5页,还剩25页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

-.z.目录中文目录格式中文目录格式摘要………………1关键词……………1前言………………2第1章Linu*设备驱动程序编写方式…………..31设备驱动程序的编写模式2module的原理第2章Linu*下的驱动设备类型………………211CharacterDevices2BlockDevices第3章Linu*设备驱动程序的框架…………251设备驱动程序的功能1对设备初始化和释放1.2把数据从内核传送到硬件和从硬件读取数据1.3读取应用程序传送给设备文件的数据和回送应用程序请求的数据1.4检测和处理设备出现的错误设备驱动程序的组成局部2.1自动配置和初始化子程序2.2效劳于I/O请求的子程序2.3中断效劳子程序3设备驱动程序的构造1驱动程序注册与注销3.2设备的翻开与释放3.3设备的读写操作3.4设备的控制操作3.5设备的中断和轮询处理4设备驱动程序接口第4章Linu*设备驱动程序的实现…………301PCI驱动程序实现的关键数据构造1pci_driver构造2pci_dev构造写驱动程序的fuctoin问题2.1function的encode问题2.2function的e*port问题2.3两个常用function3设备驱动程序中的一些具体问题1I/OPort3.2内存操作3.3中断处理4RealtekFastEthernetDriverrtl8139网卡驱动3个模块的改写1设备指明模块4.2数据读写和控制信息模块4.3中断处理模块参考文献……….25附录…………….26致谢……………28论Linu*KernelModule之设备驱动程序【摘要】驱动程序编写方法。由Linu*设备驱动程序编写方式着手,转而研究设备驱动程序的KernelModule程序。在了解了Linu*下的驱动设备类型之后,从Linu*下设备驱动程序的功能,组成局部,构造,接口4个方面分析了设备驱动程的框架。在参看了许多的书籍和网络论坛的文章,资料,具体讨论了Linu*下设备驱动程序的实现,具体研究了驱动程序的数据构造,function问题和编写程序时各个局部可能出现的难点,疑点问题。并根据DonaldBecker1999-2000年编写的RealtekFastEthernetDriverrtl8139网卡驱动Linu*版(内核版本2.0.24)改写了其中的3个模块:设备指明模块,数据读写和控制信息模块与中断处理模块。【关键词】KernelLinu*内核KernelModuleLinu*的内核模块CharacterDevices字符设备BlockDevices块设备Funtion函数前言Linu*是最受欢送的自由电脑操作系统内核。它是一个用C语言写成,符合POSI*标准的类Uni*操作系统。Linu*最早是由芬兰黑客LinusTorvalds为尝试在英特尔*86架构上提供自由免费的类Uni*操作系统而开发的。该方案开场于1991年,从LinusTorvalds当时在Usenet新闻组comp.os.mini*所登载了一分著名的贴子,标志了Linu*方案的正式开场。在方案的早期有一些Mini*黑客提供了协助,而今天全球无数程序员正在为该方案无偿提供帮助。技术上说Linu*是一个内核。"内核〞指的是一个提供硬件抽象层、磁盘及文件系统控制、多任务等功能的系统软件。一个内核不是一套完整的操作系统。一套基于Linu*内核的完整操作系统叫作Linu*操作系统,或是GNU/Linu*。今天Linu*是一个一体化内核系统。设备驱动程序可以完全访问硬件。Linu*内的设备驱动程序可以方便地以模块的形式设置,并在系统运行期间可直接装载或卸载。第1章Linu*设备驱动程序编写方式1设备驱动程序的编写模式Linu*下的设备驱动程序可以按照两种方式进展编译,一种是直接静态编译成内核的一局部,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态地卸载,不利于调试,所有推荐使用模块方式。从本质上来讲,模块也是内核的一局部,它不同于普通的应用程序,不能调用位于用户态下的C或者C++库函数,而只能调用Linu*内核提供的函数,在/proc/ksyms中可以查看到内核提供的所有函数。2module的原理module的出现是Linu*的一大革新。有了module之后,写devicedriver不需要每次要测试driver就重新compilekernel一次。防止了很多的麻烦。Module允许我们动态的改变kernel,加载devicedriver,而且它也能缩短我们driverdevelopment的时间。module就是模块。module其实是一般的程序。但是它可以被动态载到kernel里成为kernel的一局部。载到kernel里的module它具有跟kernel一样的权力。可以access任何kernel的datastructure。第2章Linu*下的驱动设备类型正文格式1CharacterDevices正文格式字符设备,Linu*最简单的设备,象文件一样访问。应用程序使用标准系统调用翻开、读取、写和关闭,完全好似这个设备是一个普通文件一样。甚至连接一个Linu*系统上网的PPP守护进程使用的modem,也是这样的。当字符设备初始化的时候,它的设备驱动程序向Linu*核心登记,在chrdevs向量表增加一个device_struct数据构造条目。这个设备的主设备标识符〔例如对于tty设备是4〕,用作这个向量表的索引。一个设备的主设备标识符是固定的。Chrdevs向量表中的每一个条目,一个device_struct数据构造,包括两个元素:一个登记的设备驱动程序的名称的指针和一个指向一组文件操作的指针。这块文件操作本身位于这个设备的字符设备驱动程序中,每一个都处理特定的文件操作比方翻开、读、写和关闭。/proc/devices中字符设备的内容来自chrdevs向量表当代表一个字符设备〔例如/dev/cua0〕的字符特殊文件翻开,核心必须做一些事情,从而去掉用正确的字符设备驱动程序的文件操作例程。和普通文件或目录一样,每一个设备特殊文件都用VFSI节点表达。这个字符特殊文件的VFSinode〔实际上所有的设备特殊文件〕都包括设备的major和minor标识符。这个VFSI节点由底层的文件系统〔例如E*T2〕,在查找这个设备特殊文件的时候根据实际的文件系统创立。每一个VFSI节点都联系着一组文件操作,依赖于I节点所代表的文件系统对象不同而不同。不管代表一个字符特殊文件的VFSI节点什么时候创立,它的文件操作被设置成字符设备的缺省操作。这只有一种文件操作:open操作。当一个应用程序翻开这个字符特殊文件的时候,通用的open文件操作使用设备的主设备标识符作为chrdevs向量表中的索引,取出这种特殊设备的文件操作块。它也建立描述这个字符特殊文件的file数据构造,让它的文件操作指向设备驱动程序中的操作。然后应用程序所有的文件系统操作都被映射到字符设备的文件操作。2BlockDevices块设备也支持象文件一样被访问。这种为翻开的块特殊文件提供正确的文件操作组的机制和字符设备的十分相似。Linu*用blkdevs向量表维护已经登记的块设备文件。它象chrdevs向量表一样,使用设备的主设备号作为索引。它的条目也是device_struct数据构造。和字符设备不同,块设备进展分类。SCSI是其中一类,而IDE是另一类。类向Linu*核心登记并向核心提供文件操作。一种块设备类的设备驱动程序向这种类提供和类相关的接口。例如,SCSI设备驱动程序必须向SCSI子系统提供接口,让SCSI子系统用来对核心提供这种设备的文件操作。每一个块设备驱动程序必须提供普通的文件操作接口和对于buffercache的接口。每一个块设备驱动程序填充blk_dev向量表中它的blk_dev_struct数据构造。这个向量表的索引还是设备的主设备号。这个blk_dev_struct数据构造包括一个请求例程的地址和一个指针,指向一个request数据构造的列表,每一个都表达buffercache向设备读写一块数据的一个请求。每一次buffercache希望读写一块数据到或从一个登记的设备的时候它就在它的blk_dev_struc中增加一个request数据构造。图8.2显示了每一个request都有一个指针指向一个或多个buffer_head数据构造,每一个都是一个读写一块数据的请求。这个buffer_head数据构造被锁定〔buffercache〕,可能会有一个进程在等待这个缓冲区的阻塞进程完成。每一个request构造都是从一个静态表,all_request表中分配的。如果这个request增加到一个空的request列表,就调用驱动程序的request函数处理这个request队列。否则,驱动程序只是简单地处理request队列中的每一个请求。一旦设备驱动程序完成了一个请求,它必须把每一个buffer_head构造从request构造中删除,标记它们为最新的,然后解锁。对于buffer_head的解锁会唤醒任何正在等待这个阻塞操作完成的进程。这样的例子包括文件解析的时候:必须等待E*T2文件系统从包括这个文件系统的块设备上读取包括下一个E*T2目录条目的数据块,这个进程将会在将要包括目录条目的buff_head队列中睡眠,直到设备驱动程序唤醒它。这个request数据构造会被标记为空闲,可以被另一个块请求使用。字符设备是以字节为单位逐个进展I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进展读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进展实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是防止消耗过多的CPU时间来等待操作的完成。一般说来,PCI卡通常都属于字符设备。第2章Linu*设备驱动程序的框架正文格式1设备驱动程序的功能正文格式对设备初始化和释放2把数据从内核传送到硬件和从硬件读取数据3读取应用程序传送给设备文件的数据和回送应用程序请求的数据1.4检测和处理设备出现的错误.2设备驱动程序的组成局部2.1自动配置和初始化子程序自动配置和初始化子程序,负责检测所要驱动的硬件设备是否存在和是否能正常工作。如果该设备正常,则对这个设备及其相关的、设备驱动程序需要的软件状态进展初始化。这局部驱动程序仅在初始化的时候被调用一次。2.2效劳于I/O请求的子程序效劳于I/O请求的子程序,又称为驱动程序的上半局部。调用这局部是由于系统调用的结果。这局部程序在执行的时候,系统仍认为是和进展调用的进程属于同一个进程,只是由用户态变成了核心态,具有进展此系统调用的用户程序的运行环境,因此可以在其中调用sleep()等与进程运行环境有关的函数。2.3中断效劳子程序中断效劳子程序,又称为驱动程序的下半局部。在UNI*系统中,并不是直接从中断向量表中调用设备驱动程序的中断效劳子程序,而是由UNI*系统来接收硬件中断,再由系统调用中断效劳子程序。中断可以产生在任何一个进程运行的时候,因此在中断效劳程序被调用的时候,不能依赖于任何进程的状态,也就不能调用任何与进程运行环境有关的函数。因为设备驱动程序一般支持同一类型的假设干设备,所以一般在系统调用中断效劳子程序的时候,都带有一个或多个参数,以唯一标识请求效劳的设备。在系统内部,I/O设备的存取通过一组固定的入口点来进展,这组入口点是由每个设备的设备驱动程序提供的。3设备驱动程序的构造3.1驱动程序注册与注销向系统增加一个驱动程序意味着要赋予它一个主设备号,这可以通过在驱动程序的初始化过程中调用register_chrdev()或者register_blkdev()来完成。而在关闭字符设备或者块设备时,则需要通过调用unregister_chrdev()或unregister_blkdev()从内核中注销设备,同时释放占用的主设备号。3.2设备的翻开与释放翻开设备是通过调用file_operations构造中的函数open()来完成的,它是驱动程序用来为今后的操作完成初始化准备工作的。在大局部驱动程序中,open()通常需要完成以下工作:检查设备相关错误,如设备尚未准备好等。如果是第一次翻开,则初始化硬件设备。识别次设备号,如果有必要则更新读写操作的当前位置指针f_ops。分配和填写要放在file->private_data里的数据构造。使用计数增1。释放设备是通过调用file_operations构造中的函数release()来完成的,这个设备方法有时也被称为close(),它的作用正好与open()相反,通常要完成以下工作:使用计数减1。释放在file->private_data中分配的内存。如果使用计算为0,则关闭设备。3.3设备的读写操作字符设备的读写操作相比照拟简单,直接使用函数read()和write()就可以了。但如果是块设备的话,则需要调用函数block_read()和block_write()来进展数据读写,这两个函数将向设备请求表中增加读写请求,以便Linu*内核可以对请求顺序进展优化。由于是对内存缓冲区而不是直接对设备进展操作的,因此能很大程度上加快读写速度。如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据写入设备,则就要执行真正的数据传输,这是通过调用数据构造blk_dev_struct中的函数request_fn()来完成的。3.4设备的控制操作除了读写操作外,应用程序有时还需要对设备进展控制,这可以通过设备驱动程序中的函数ioctl()来完成。ioctl()的用法与具体设备密切关联,因此需要根据设备的实际情况进展具体分析。3.5设备的中断和轮询处理对于不支持中断的硬件设备,读写时需要轮流查询设备状态,以便决定是否继续进展数据传输。如果设备支持中断,则可以按中断方式进展操作。4设备驱动程序接口Linu*中的I/O子系统向内核中的其他局部提供了一个统一的标准设备接口,这是通过include/linu*/fs.h中的数据构造file_operations来完成的:structfile_operations{structmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar*,size_t,loff_t*);int(*readdir)(structfile*,void*,filldir_t);unsignedint(*poll)(structfile*,structpoll_table_struct*);int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*);int(*release)(structinode*,structfile*);int(*fsync)(structfile*,structdentry*,intdatasync);int(*fasync)(int,structfile*,int);int(*lock)(structfile*,int,structfile_lock*);ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);};当应用程序对设备文件进展诸如open、close、read、write等操作时,Linu*内核将通过file_operations构造访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操作时,内核将调用file_operations构造中的read函数。第4章Linu*设备驱动程序的实现正文格式1PCI驱动程序实现的关键数据构造正文格式PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供应设备驱动程序使用,而配置空间则由Linu*内核中的PCI初始化代码使用。内核在启动时负责对所有PCI设备进展初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在文件/proc/pci中列出所有找到的PCI设备,以及这些设备的参数和属性。Linu*驱动程序通常使用构造〔struct〕来表示一种设备,而构造体中的变量则代表*一具体设备,该变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动多个同种设备,每个设备之间用次设备号进展区分,如果采用构造数据来代表所有能由该驱动程序驱动的设备,则就可以简单地使用数组下标来表示次设备号。1.1pci_driver构造这个数据构造在文件include/linu*/pci.h里,这是Linu*内核版本2.4之后为新型的PCI设备驱动程序所添加的,其中最主要的是用于识别设备的d_table构造,以及用于检测设备的函数probe()和卸载设备的函数remove():structpci_driver{structlist_headnode;char*name;conststructpci_device_id*id_table;int(*probe)(structpci_dev*dev,conststructpci_device_id*id);void(*remove)(structpci_dev*dev);int(*save_state)(structpci_dev*dev,u32state);int(*suspend)(structpci_dev*dev,u32state);int(*resume)(structpci_dev*dev);int(*enable_wake)(structpci_dev*dev,u32state,intenable);};2pci_dev构造这个数据构造也在文件include/linu*/pci.h里,它详细描述了一个PCI设备几乎所有的硬件信息,包括厂商ID、设备ID、各种资源等:structpci_dev{structlist_headglobal_list;structlist_headbus_list;structpci_bus*bus;structpci_bus*subordinate;void*sysdata;structproc_dir_entry*procent;unsignedintdevfn;unsignedshortvendor;unsignedshortdevice;unsignedshortsubsystem_vendor;unsignedshortsubsystem_device;unsignedintclass;u8hdr_type;u8rom_base_reg;structpci_driver*driver;void*driver_data;u64dma_mask;u32current_state;unsignedshortvendor_compatible[DEVICE_COUNT_COMPATIBLE];unsignedshortdevice_compatible[DEVICE_COUNT_COMPATIBLE];unsignedintirq;structresourceresource[DEVICE_COUNT_RESOURCE];structresourcedma_resource[DEVICE_COUNT_DMA];structresourceirq_resource[DEVICE_COUNT_IRQ];charname[80];charslot_name[8];intactive;intro;unsignedshortregs;int(*prepare)(structpci_dev*dev);int(*activate)(structpci_dev*dev);int(*deactivate)(structpci_dev*dev);};2写驱动程序的fuctoin问题2.1function的encode问题在写C程序的时候,一个程序只能有一个main。Kernel本身其实也是一个程序,它本身也有个main,叫start_kernel()。当我们把一个module载到kernel里的时候,它会跟kernel整合在一起,成为kernel的一局部。module是不能使用main的,因为module是处于被动的角色,它提供*些功能让别人去使用的。Kernel里有一个变量叫module_list,每当user将一个module载到kernel里的时候,这个module就会被记录在module_list里面。当kernel要使用到这个module提供的function时,它就会去search这个list,找到module,然后再使用其提供的function或variable。每一个module都可以e*port一些function或变量来让别人使用。除此之外,module也可以使用已经载到kernel里的module提供的function。这种情形叫做modulestack。比方说,moduleA用到moduleB的东西,那在加载moduleA之前必须要先加载moduleB。否则moduleA会无法加载。除了module会e*port东西之外,kernel本身也会e*port一些function或variable。同样的,module也可以使用kernel所e*port出来的东西。module所使用的function或variable,要嘛就是自己写在module里,要嘛就是别的module提供的,再不就是kernel所提供的。你不能使用一般libc或glibc所提供的function。像printf之类的东西。kernel本身会e*port出一些function或variable来让module使用,Linu*提供一个command,叫ksyms,你只要执行ksyms-a就可以知道kernel或目前载到kernel里的module提供了那些function或variable。例如*系统的情形:c0216ba0drive_info_R744aa133c01e4a44boot_cpu_data_R660bd466c01e4ac0EISA_bus_R7413793ac01e4ac4MCA_bus_Rf48a2c4cc010cc34__verify_write_R203afbeb.....在kernel里,有一个symboltable是用来记录e*port出去的function或variable。除此之外,也会记录着那个modulee*port那些function。上面几行中,表示kernel提供了drive_info这个function/variable。所以,我们可以在kernel里直接使用它,等载到kernel里时,会自动做好link的动作。由此,我们可以知道,module本身其实是还没做link的一些objectcode。一切都要等到module被加载kernel之后,link才会完成。各位应该可以看到drive_info后面还接着一些奇怪的字符串。_R744aa133,这个字符串是根据目前kernel的版本再做些encode得出来的结果。为什幺额外需要这一个字符串呢"Linu*有一个config的选项,叫做Setversionnumberinsymbolsofmodule。这是为了防止对系统造成不稳定。我们知道Linu*的kernel更新的很快。在kernel更新的过程,有时为了效率起见,会对*些旧有的datastructure或function做些改变,而且一变可能有的variable被拿掉,有的function的prototype跟原来的都不太一样。如果这种情形发生的时候,那可能以前2.0.33版本的module拿到2.2.1版本的kernel使用,假设原来module使用了2.0.33kernel提供的变量叫A,但是到了2.2.1由于*些原因必须把A都设成NULL。那当此module用在2.2.1kernel上时,如果它没去检查A的值就直接使用的话,就会造成系统的错误。也许不会整个系统都死掉,但是这个module肯定是很难发挥它的功能。为了这个原因,Linu*就在compilemodule时,把kernel版本的号码encode到各个e*portedfunction和variable里。所以,刚刚也许我们不应该讲kernel提供了drive_info,而应该说kernel提供了driver_info_R744aa133来让我们使用。也就是说,kernel认为它提供的driver_info_R744aa133这个东西,而不是driver_info。所以,我们可以发现有的人在加载module时,系统都一直告诉你*个function无法resolved。这就是因为kernel里没有你要的function,要不然就是你的module里使用的function跟kernelencode的结果不一样。所以无法resolve。解决方式,要嘛就是将kernel里的setversion选项关掉,要嘛就是将modulecompile成kernel有方法承受的型式。如果kernel认定它提供的function名字叫做driver_info_R744aa133的话,那我们写程序时,把用到这个funnction的地方都改成driver_info_R744aa133就可以了。为了防止烦琐地对同一个functoin不动调用的地方的修改,linu*为我们提供了#defineprintkprintk_Rdd132261这样的形式来解决这个问题。如果将系统的setversion的选项翻开的话,可以到/usr/src/linu*/include/linu*/modules这个目录底下。这个目录底下有所多的..ver档案。这些档案其实就是用来做#define用的。我们来看看ksyms.ver这个档案里,里面有一行是这样子的:#defineprintk_set_ver(printk)set_ver是一个macro,就是用来在printk后面加上versionnumber的。用了这些ver档,我们就可以在module里直接使用printk这样的名字了。而这些ver档会自动帮我们做好#define的动作。可是,我们可以发现这个目录有很多很多的ver档。为了让我们知我们要呼叫的function是在那个ver档里Linu*又给我们提供了帮助。/usr/src/linu*/include/linu*/modversions.h这个档案已经将全部的ver档都加进来了。所以在我们的module里只要include这个档,那名字的问题都解决了。但是,如果要将modversions.h这个档在module里include进来,就要加上以下数行:#ifdefMODVERSIONS#include<linu*/modversions.h>#endif参加这三行的原因是,防止这个module在没有设定kernelversion的系统上,将modversions.h这个档案include进来。当你把setversion的选项关掉时,modversions.h和modules这个目录都会不见。如果没有上面三行,那compile就不会过关。所以一般来讲,modversions.h我们会选择在compile时传给gcc使用。就像下面这个样子。gcc-c-D__KERNEL__-DMODULE-DMODVERSIONSmain.c\-includeusr/src/linu*/include/linu*/modversions.h在这个commandline里,我们看到了-D__KERNEL__,这是说要定义__KERNEL__这个constant。很多跟kernel有关的headerfile,都必须要定义这个constant才能include的。所以最好将它定义起来。另外还有一个-DMODVERSIONS。要解决fucntion或variable名字encode的方式就是要includemodversions.h,其实除此之外,你还必须定义MODVERSIONS这个constant。再来就是MODULE这个constant。其实,只要是你要写module就一定要定义这个变量。而且你还要includemodule.h这个档案,因为_set_ver就是定义在这里的。2.2function的e*port问题如果我们自己的module想要e*port一些东西让别的module使用,并且限定几个必要的东西e*port出去。在kernel里提供了一个macro,叫做E*PORT_SYMBOL,这是用来帮我们选择要e*port的variable或function。比方说,要e*port一个叫full的variable,就只需要在module里写:E*PORT_SYMBOL(full);就会自动将fulle*port出去,马上就可以从ksyms里发现有full这个变量被e*port出去。在使用E*PORT_SYMBOL之前,必须在gcc里定义E*PORT_SYMTAB这个constant,否则在compile时会发生parsererror。所以,要使用E*PORT_SYMBOL的话,那gcc应该要下:gcc-c-D__KERNEL__-DMODULE-DMODVERSIONS-DE*PORT_SYMTAB\main.c-include/usr/src/linu*/include/linu*/modversions.h如果不想e*port任何的东西,那只要在module里写下E*PORT_NO_SYMBOLS;就可以了。使用E*PORT_NO_SYMBOLS用不着定义任何的constant。当使用E*PORT_SYMBOL把一些function或variablee*port出来之后,我们使用ksyma-a去看一些结果。我们发现E*PORT_SYMBOL(full)确实是把fulle*port出来了:c8822200full[my_module]c01b8e08pci_find_slot_R454463b5...如果在module的开头。参加一行#definefullfull_R******之后,再重新compilemodule一次,载到kernel之后,就可以发现ksyms-a显示的是c8822200full_R******[my_module]c01b8e08pci_find_slot_R454463b5.....了。Linu*里提供了一个command,叫genksyms,是用来产生.ver的档案的。它会从stdin里读取sourcecode,然后检查sourcecode里是否有e*port的variable或function。如果有,它就会自动为每个e*port出来的东西产生一些define。假设我们的程序都放在一个叫main.c的档案里,我们可以使用以下的方式产生这些define。gcc-E-D__GENKSYMS__main.c|genksyms-k2.2.1>main.vergcc的-E参数是指将preprocessing的结果show出来。也就是说将它include的档案,一些define的结果都展开。用一个管线是因为genksyms是从stdin读资料的,所以,经由管线将gcc的结果传给genksyms。-k2.2.1是指目前使用的kernel版本是2.2.1,如果kernel版本不一样,必须指定kernel的版本。产生的define将会被放到main.ver里。产生完main.ver档之后,在main.c里将它include进来。使用这个方式产生的module,其e*port出来的东西会经由main.ver的define改头换面。所以如果要让别人使用,那就必须将main.ver公开。3两个常用function要写一个module,必须要提供两个function。这两个function是给insmod和rmmod使用的。它们分别是init_module(),以及cleanup_module()。intinit_module();voidcleanup_module();一般来讲,我们在init_module()做的事都是一些初始化的工作。比方说,我们的的module需要一块内存,那就可以在init_module()做kmalloc的动作。。cleanup_module()就是在module要移除的时候做的事,比方像把之前kmalloc的内存free掉。由于module是载到kernel使用的,所以,可能别的module会使用你的module,甚至*些process也会使用到你的module,为了防止module还有人使用时就被移除,每个module都有一个usecount。用来记录目前有多少个process或module正在使用这个module。当module的usecount不等于0时,module是不会被移除掉的。也就是说,当module的usecount不等于0时,cleanup_module()是不会被呼叫的。以下的三个macro,是跟module的usecount有关系密切的。MOD_INC_USE_COUNTMOD_DEC_USE_COUNTMOD_IN_USEMOD_INC_USE_COUNT是用来增加module的usecount,而MOD_DEC_USE_COUNT是用来减少module的usecount。MOD_IN_USE则是用来检查目前这个module是不是被使用中。也就是检查usecount是否为0。module的usecount必须由写module的人自己来maintain。系统并不会自动为你把usecount加一或减一,都可以自己来控制3设备驱动程序中的一些具体问题I/OPort和硬件打交道离不开I/OPort,老的ISA设备经常是占用实际的I/O端口,在linu*下,操作系统没有对I/O口屏蔽,也就是说,任何驱动程序都可对任意的I/O口操作,这样就很容易引起混乱。每个驱动程序应该自己防止误用端口。有两个重要的kernel函数可以保证驱动程序做到这一点。check_region(intio_port,intoff_set)这个函数观察系统的I/O表,看是否有别的驱动程序占用*一段I/O口。参数1:io端口的基地址,参数2:io端口占用的范围。返回值:0没有占用,非0,已经被占用。request_region(intio_port,intoff_set,char*devname)如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports文件中可以看到你登记的io口。参数1:io端口的基地址。参数2:io端口占用的范围。参数3:使用这段io地址的设备名。在对I/O口登记后,就可以放心地用inb(),outb()之类的函来访问了。3.2内存操作在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,或free_pages.kmalloc等函数返回的是物理地址!而malloc等返回的是线性地址!kmalloc最大只能开辟128k-16,16个字节是被页描述符构造占用了。内存映射的I/O口,存放器或者是硬件设备的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得重新映射以后的地址。另外,很多硬件需要一块比拟大的连续内存用作DMA传送。这块内存需要一直驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。这可以通过牺牲一些系统内存的方法来解决。具体做法是:比方说你的机器由32M的内存,在lilo.conf的启动参数中加上mem=30M,这样linu*就认为你的机器只有30M的内存,剩下的2M内存在vremap之后就可以为DMA所用了。用vremap映射后的内存,不用时应用unremap释放,否则会浪费页表。3.3中断处理同处理I/O端口一样,要使用一个中断,必须先向系统登记。intrequest_irq(unsignedintirq,void(*handle)(int,void*,structpt_regs*),unsignedintlongflags,constchar*device);irq:是要申请的中断。handle:中断处理函数指针。flags:SA_INTERRUPT请求一个快速中断,0正常中断。device:设备名。如果登记成功,返回0,这时在/proc/interrupts文件中可以看你请求的中断。4RealtekFastEthernetDriverrtl8139网卡驱动3个模块的改写1设备指明模块/*设备指明模块*/staticstructpci_id_infopci_tbl[]={ {"RealTekRTL8129FastEthernet",{0*812910ec,0*ffffffff,}, RTL8139_IOTYPE,0*80,RTL8129_CAPS,}, {"RealTekRTL8139FastEthernet",{0*813910ec,0*ffffffff,}, RTL8139_IOTYPE,0*80,RTL8139_CAPS,}, {"RealTekRTL8139BPCI/CardBus",{0*813810ec,0*ffffffff,}, RTL8139_IOTYPE,0*80,RTL8139_CAPS,}, {"SMC1211T*EZCard10/100(RealTekRTL8139)",{0*12111113,0*ffffffff,}, RTL8139_IOTYPE,0*80,RTL8139_CAPS,}, {"AcctonMP*5030(RealTekRTL8139)",{0*12111113,0*ffffffff,}, RTL8139_IOTYPE,0*80,RTL8139_CAPS,}, {0,}, /*0terminatedlist.*/};2数据读写和控制信息模块PCI设备驱动程序可以通过demo_fops构造中的函数demo_ioctl(),向应用程序提供对硬件进展控制的接口。例如,通过它可以从I/O存放器里读取一个数据,并传送到用户空间里:/*数据读写和控制信息模块*/staticintmii_ioctl(structnet_device*dev,structifreq*rq,intcmd){ structrtl8129_private*tp=(structrtl8129_private*)dev->priv; u16*data=(u16*)&rq->ifr_data; switch(cmd){ caseSIOCDEVPRIVATE: /*GettheaddressofthePHYinuse.*/ data[0]=tp->phys[0]&0*3f; /*FallThrough*/ caseSIOCDEVPRIVATE+1: /*ReadthespecifiedMIIregister.*/ data[3]=mdio_read(dev,data[0],data[1]&0*1f); return0; caseSIOCDEVPRIVATE+2: /*WritethespecifiedMIIregister*/ if(!capable(CAP_NET_ADMIN)) return-EPERM; mdio_write(dev,data[0],data[1]&0*1f,data[2]); return0; default: return-EOPNOTSUPP; }}3中断处理模块PC的中断资源比拟有限,只有0~15的中断号,因此大局部外部设备都是以共享的形式申请中断号的。当中断发生的时候,中断处理程序首先负责对中断进展识别,然后再做进一步的处理。/*中断处理模块*/staticvoidrtl8139CP_interrupt(intirq,void*dev_instance,structpt_regs*regs){ structnet_device*dev=(structnet_device*)dev_instance; structrtl8129_private*tp=(structrtl8129_private*)dev->priv; intboguscnt=ma*_interrupt_work; longioaddr=dev->base_addr; intlink_changed=0;#ifdefined(__i386__) /*AlocktopreventsimultaneousentrybugonIntelSMPmachines.*/ if(test_and_set_bit(0,(void*)&dev->interrupt)){ printk(KERN_ERR"%s:SMPsimultaneousentryofaninterrupthandler.\n", dev->name); dev->interrupt=0; /*Avoidhaltingmachine.*/ return; }#else if(dev->interrupt){ printk(KERN_ERR"%s:Re-enteringtheinterrupthandler.\n",dev->name); return; } dev->interrupt=1;#endif do{ intstatus=inw(ioaddr+IntrStatus); /*AcknowledgeallofthecurrentinterruptsourcesASAP,but anfirstgetanadditionalstatusbitfromCSCR.*/if((status&R*Underrun)&&inw(ioaddr+CSCR)&CSCR_LinkChangeBit){link_changed=inw(ioaddr+CSCR)&CSCR_LinkChangeBit; /*IflinkOK,disableLinkDownPowerSavingmode. IflinkOFF,enableit.ThismodeisavaiableonlyforRTL8139C*/if((inb(ioaddr+T*Config+3)&0*7C)==0*74)//0*74isRTL8139CHWVer.{if((inb(ioaddr+MediaStatus)&LINK_Status)==0)outb(inb(ioaddr+Config5)|0*4,ioaddr+Config5);elseoutb(inb(ioaddr+Config5)&0*B,ioaddr+Config5);}} if(status&(R*FIFOOver|R*Overflow)) outw(R*FIFOOver&R*Overflow,ioaddr+IntrStatus); if(status&T*Err) outl(0*01,ioaddr+T*Config); outw(status,ioaddr+IntrStatus); if(debug>4) printk(KERN_DEBUG"%s:interruptstatus=%#4.4*newintstat=%#4.4*.\n", dev->name,status,inw(ioaddr+IntrStatus)); if((status&(PCIErr|PCSTimeout|R*Underrun|R*Overflow|R*FIFOOver|T*Err|T*OK|R*Err|R*OK))==0) break; if(status&(R*OK|R*Underrun|R*Overflow|R*FIFOOver))/*R*interrupt*/ rtl8139cp_r*(dev); if(status&(T*Err|T*OK)){ unsignedintdirty_t*=tp->dirty_t*; while(tp->cur_t*-dirty_t*>0){ intentry=dirty_t*%NUM_CP_T*_DESC; /*Freetheoriginalskb.*/ dev_free_skb(tp->t*_skbuff[entry]); tp->t*_skbuff[entry]=0; if(test_bit(0,&tp->t*_full)){ /*Theringisnolongerfull,cleartbusy.*/ clear_bit(0,&tp->t*_full); clear_bit(0,(void*)&dev->tbusy); netif_wake_queue(dev); } dirty_t*++; tp->stats.t*_packets++; }#ifndeffinal_version if(tp->cur_t*-dirty_t*>NUM_CP_T*_DESC){ printk(KERN_ERR"%s:Out-of-syncdirtypointer,%dvs.%d,full=%d.\n", dev->name,dirty_t*,tp->cur_t*,(int)tp->t*_full);

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论