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

下载本文档

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

文档简介

1、目 录中文名目格式摘要1关键词1前言2第1章Linux设备驱动程序编写方式.31设备驱动程序的编写模式2 module的原理第2章Linux下的驱动设备类型211 Character Devices2 Block Devices第3章Linux设备驱动程序的框架251设备驱动程序的功能1. 1对设备初始化和释放1.2 把数据从内核传送到硬件和从硬件读取数据1.3读取应用程序传送给设备文件的数据和回送应用程序恳求的数据1.4检测和处理设备消灭的错误2. 设备驱动程序的组成部分2.1自动配置和初始化子程序2.2服务于I/O恳求的子程序2.3中断服务子程序3设备驱动程序的结构3. 1驱动程序注册与注

2、销3.2设备的打开与释放3.3设备的读写操作3.4设备的把握操作3.5设备的中断和轮询处理4设备驱动程序接口第4章Linux设备驱动程序的实现301PCI驱动程序实现的关键数据结构1. 1pci_driver结构1. 2pci_dev结构2. 写驱动程序的fuctoin问题2.1function的encode问题2.2function的export问题2.3两个常用function3设备驱动程序中的一些具体问题3. 1I/O Port3.2内存操作3.3中断处理4Realtek Fast Ethernet Driver rtl8139网卡驱动3个模块的改写4. 1设备指明模块4.2数据读写和把

3、握信息模块4.3中断处理模块参考文献.25附录.26致谢28论Linux Kernel Module之设备驱动程序【摘 要】驱动程序编写方法。由Linux设备驱动程序编写方式着手,转而争辩设备驱动程序的Kernel Module程序。在了解了Linux下的驱动设备类型之后,从Linux下设备驱动程序的功能,组成部分,结构,接口4个方面分析了设备驱动程的框架。在参看了很多的书籍和网络论坛的文章,资料,具体争辩了Linux下设备驱动程序的实现,具体争辩了驱动程序的数据结构,function问题和编写程序时各个部分可能消灭的难点,疑点问题。并依据Donald Becker 1999-2000年编写的

4、Realtek Fast Ethernet Driver rtl8139网卡驱动Linux版(内核版本2.0.24)改写了其中的3个模块:设备指明模块,数据读写和把握信息模块与中断处理模块。【关键词】Kernel Linux内核Kernel Module Linux的内核模块Character Devices字符设备Block Devices块设备Funtion函数前言 Linux是最受欢迎的自由电脑操作系统内核。它是一个用C语言写成,符合POSIX标准的类Unix操作系统。Linux最早是由芬兰黑客 Linus Torvalds为尝试在英特尔x86架构上供应自由免费的类Unix操作系统而开发

5、的。该方案开头于1991年, 从Linus Torvalds当时在Usenet新闻组comp.os.minix所登载了一分有名的贴子,标志了Linux方案的正式开头。在方案的早期有一些Minix 黑客供应了帮忙,而今日全球很多程序员正在为该方案无偿供应挂念。技术上说Linux是一个内核。“内核”指的是一个供应硬件抽象层、磁盘及文件系统把握、多任务等功能的系统软件。一个内核不是一套完整的操作系统。一套基于Linux内核的完整操作系统叫作Linux操作系统,或是GNU/Linux。今日Linux是一个一体化内核系统。设备驱动程序可以完全访问硬件。Linux内的设备驱动程序可以便利地以模块的形式设置

6、,并在系统运行期间可直接装载或卸载。第1章 Linux设备驱动程序编写方式1设备驱动程序的编写模式Linux下的设备驱动程序可以依据两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则是编译成可以动态加载的模块。假如编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态地卸载,不利于调试,全部推举使用模块方式。 从本质上来讲,模块也是内核的一部分,它不同于一般的应用程序,不能调用位于用户态下的C或者C+库函数,而只能调用Linux内核供应的函数,在/proc/ksyms中可以查看到内核供应的全部函数。2 module的原理 module的消灭是 Linux 的一大革新。有

7、了 module 之后,写 device driver 不需要每次要测试 driver 就重新 compile kernel 一次。避开了很多的麻烦。Module 允许我们动态的转变 kernel,加载 device driver,而且它也能缩短我们 driver development 的时间。 module就是模块。module 其实是一般的程序。但是它可以被动态载到 kernel 里成为 kernel的一部分。载到 kernel 里的 module 它具有跟 kernel 一样的权力。可以 access 任何 kernel 的 data structure。第2章 Linux下的驱动设备

8、类型正文格式1 Character Devices字符设备, Linux 最简洁的设备,象文件一样访问。应用程序使用标准系统调用打开、读取、写和关闭,完全好像这个设备是一个一般文件一样。甚至连接一个 Linux 系统上网的 PPP 守护进程使用的 modem ,也是这样的。当字符设备初始化的时候,它的设备驱动程序向 Linux 核心登记,在 chrdevs 向量表增加一个 device_struct 数据结构条目。这个设备的主设备标识符(例如对于 tty 设备是 4 ),用作这个向量表的索引。一个设备的主设备标识符是固定的。 Chrdevs 向量表中的每一个条目,一个 device_struc

9、t 数据结构,包括两个元素:一个登记的设备驱动程序的名称的指针和一个指向一组文件操作的指针。这块文件操作本身位于这个设备的字符设备驱动程序中,每一个都处理特定的文件操作比如打开、读、写和关闭。 /proc/devices 中字符设备的内容来自 chrdevs 向量表当代表一个字符设备(例如 /dev/cua0 )的字符特殊文件打开,核心必需做一些事情,从而去掉用正确的字符设备驱动程序的文件操作例程。和一般文件或名目一样,每一个设备特殊文件都用 VFS I 节点表达。这个字符特殊文件的 VFS inode (实际上全部的设备特殊文件)都包括设备的 major 和 minor 标识符。这个 VFS

10、 I 节点由底层的文件系统(例如 EXT2 ),在查找这个设备特殊文件的时候依据实际的文件系统创建。 每一个 VFS I 节点都联系着一组文件操作,依靠于 I 节点所代表的文件系统对象不同而不同。不管代表一个字符特殊文件的 VFS I 节点什么时候创建,它的文件操作被设置成字符设备的缺省操作。这只有一种文件操作: open 操作。当一个应用程序打开这个字符特殊文件的时候,通用的 open 文件操作使用设备的主设备标识符作为 chrdevs 向量表中的索引,取出这种特殊设备的文件操作块。它也建立描述这个字符特殊文件的 file 数据结构,让它的文件操作指向设备驱动程序中的操作。然后应用程序全部的

11、文件系统操作都被映射到字符设备的文件操作。2 Block Devices 块设备也支持象文件一样被访问。这种为打开的块特殊文件供应正确的文件操作组的机制和字符设备的格外相像。 Linux 用 blkdevs 向量表维护已经登记的块设备文件。它象 chrdevs 向量表一样,使用设备的主设备号作为索引。它的条目也是 device_struct 数据结构。和字符设备不同,块设备进行分类。 SCSI 是其中一类,而 IDE 是另一类。类向 Linux 核心登记并向核心供应文件操作。一种块设备类的设备驱动程序向这种类供应和类相关的接口。例如, SCSI 设备驱动程序必需向 SCSI 子系统供应接口,让

12、 SCSI 子系统用来对核心供应这种设备的文件操作。 每一个块设备驱动程序必需供应一般的文件操作接口和对于 buffer cache 的接口。每一个块设备驱动程序填充 blk_dev 向量表中它的 blk_dev_struct 数据结构。这个向量表的索引还是设备的主设备号。这个 blk_dev_struct 数据结构包括一个恳求例程的地址和一个指针,指向一个 request 数据结构的列表,每一个都表达 buffer cache 向设备读写一块数据的一个恳求。 每一次 buffer cache 期望读写一块数据到或从一个登记的设备的时候它就在它的 blk_dev_struc 中增加一个 req

13、uest 数据结构。图 8.2 显示了每一个 request 都有一个指针指向一个或多个 buffer_head 数据结构,每一个都是一个读写一块数据的恳求。这个 buffer_head 数据结构被锁定( buffer cache ),可能会有一个进程在等待这个缓冲区的堵塞进程完成。每一个 request 结构都是从一个静态表, all_request 表中安排的。假如这个 request 增加到一个空的 request 列表,就调用驱动程序的 request 函数处理这个 request 队列。否则,驱动程序只是简洁地处理 request 队列中的每一个恳求。 一旦设备驱动程序完成了一个恳求

14、,它必需把每一个 buffer_head 结构从 request 结构中删除,标记它们为最新的,然后解锁。对于 buffer_head 的解锁会唤醒任何正在等待这个堵塞操作完成的进程。这样的例子包括文件解析的时候:必需等待 EXT2 文件系统从包括这个文件系统的块设备上读取包括下一个 EXT2 名目条目的数据块,这个进程将会在将要包括名目条目的 buff_head 队列中睡眠,直到设备驱动程序唤醒它。这个 request 数据结构会被标记为空闲,可以被另一个块恳求使用。 字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写恳求时,实际的硬件I/O紧接着就发生了,一般来说字符设备

15、中的缓存是可有可无的,而且也不支持随机访问。块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写恳求时,驱动程序先查看缓冲区中的内容,假如缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的恳求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是避开耗费过多的CPU时间来等待操作的完成。一般说来,PCI卡通常都属于字符设备。第2章 Linux设备驱动程序的框架正文格式1设备驱动程序的功能1.1 对设备初始化和释放1. 2把数据从内核传送到硬件和从硬件读取数据1. 3读取应用程序传送给设备文件的数据和回送应用程序恳求的数据1.4检测和处理设备消灭的错误

16、.2设备驱动程序的组成部分2.1 自动配置和初始化子程序自动配置和初始化子程序,负责检测所要驱动的硬件设备是否存在和是否 能正常工作。假如该设备正常,则对这个设备及其相关的、设备驱动程序 需要的软件状态进行初始化。这部分驱动程序仅在初始化的时候被调用一 次。2.2 服务于I/O恳求的子程序服务于I/O恳求的子程序,又称为驱动程序的上半部分。调用这部分是由 于系统调用的结果。这部分程序在执行的时候,系统仍认为是和进行调用 的进程属于同一个进程,只是由用户态变成了核心态,具有进行此系统调 用的用户程序的运行环境,因此可以在其中调用sleep()等与进程运行环 境有关的函数。2.3 中断服务子程序中

17、断服务子程序,又称为驱动程序的下半部分。在UNIX系统中,并不是 直接从中断向量表中调用设备驱动程序的中断服务子程序,而是由UNIX 系统来接收硬件中断,再由系统调用中断服务子程序。中断可以产生在任 何一个进程运行的时候,因此在中断服务程序被调用的时候,不能依靠于 任何进程的状态,也就不能调用任何与进程运行环境有关的函数。由于设 备驱动程序一般支持同一类型的若干设备,所以一般在系统调用中断服务 子程序的时候,都带有一个或多个参数,以唯一标识恳求服务的设备。 在系统内部,I/O设备的存取通过一组固定的入口点来进行,这组入口点是 由每个设备的设备驱动程序供应的。3设备驱动程序的结构3.1 驱动程序

18、注册与注销向系统增加一个驱动程序意味着要赐予它一个主设备号,这可以通过在驱动程序的初始化过程中调用register_chrdev( )或者register_blkdev( )来完成。而在关闭字符设备或者块设备时,则需要通过调用unregister_chrdev( )或unregister_blkdev( )从内核中注销设备,同时释放占用的主设备号。3.2 设备的打开与释放打开设备是通过调用file_operations结构中的函数open( )来完成的,它是驱动程序用来为今后的操作完成初始化预备工作的。在大部分驱动程序中,open( )通常需要完成下列工作:检查设备相关错误,如设备尚未预备好等

19、。假如是第一次打开,则初始化硬件设备。识别次设备号,假如有必要则更新读写操作的当前位置指针f_ops。安排和填写要放在file-private_data里的数据结构。使用计数增1。释放设备是通过调用file_operations结构中的函数release( )来完成的,这个设备方法有时也被称为close( ),它的作用正好与open( )相反,通常要完成下列工作:使用计数减1。释放在file-private_data中安排的内存。假如使用计算为0,则关闭设备。3.3 设备的读写操作字符设备的读写操作相对比较简洁,直接使用函数read( )和write( )就可以了。但假如是块设备的话,则需要调

20、用函数block_read( )和block_write( )来进行数据读写,这两个函数将向设备恳求表中增加读写恳求,以便Linux内核可以对恳求挨次进行优化。由于是对内存缓冲区而不是直接对设备进行操作的,因此能很大程度上加快读写速度。假如内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据写入设备,那么就要执行真正的数据传输,这是通过调用数据结构blk_dev_struct中的函数request_fn( )来完成的。3.4 设备的把握操作除了读写操作外,应用程序有时还需要对设备进行把握,这可以通过设备驱动程序中的函数ioctl( )来完成。ioctl( )的用法与具体设备亲密关联,因此需

21、要依据设备的实际状况进行具体分析。3.5 设备的中断和轮询处理对于不支持中断的硬件设备,读写时需要轮番查询设备状态,以便打算是否连续进行数据传输。假如设备支持中断,则可以按中断方式进行操作。4设备驱动程序接口Linux中的I/O子系统向内核中的其他部分供应了一个统一的标准设备接口,这是通过include/linux/fs.h中的数据结构file_operations来完成的: struct file_operations struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (s

22、truct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); i

23、nt (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct fi

24、le *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmap

25、ped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);当应用程序对设备文件进行诸如open、close、read、write等操作时,Linux内核将通过file_operations结构访问驱动程序供应的函数。例如,当应用程序对设备文件执行读操作时,内核将调用file_operations结构中的read函数。第4章 Linux设备驱动程序的实现正文格式1 PCI驱动程序实现的关键数据结构 PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。CP

26、U可以访问PCI设备上的全部地址空间,其中I/O空间和存储空间供应应设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负责对全部PCI设备进行初始化,配置好全部的PCI设备,包括中断号以及I/O基址,并在文件/proc/pci中列出全部找到的PCI设备,以及这些设备的参数和属性。 Linux驱动程序通常使用结构(struct)来表示一种设备,而结构体中的变量则代表某一具体设备,该变量存放了与该设备相关的全部信息。好的驱动程序都应当能驱动多个同种设备,每个设备之间用次设备号进行区分,假如接受结构数据来代表全部能由该驱动程序驱动的设备,那么就可以简洁地使用数组下标

27、来表示次设备号。1.1pci_driver结构这个数据结构在文件include/linux/pci.h里,这是Linux内核版本2.4之后为新型的PCI设备驱动程序所添加的,其中最主要的是用于识别设备的d_table结构,以及用于检测设备的函数probe( )和卸载设备的函数remove( ): struct pci_driver struct list_head node; char *name; const struct pci_device_id *id_table; int (*probe) (struct pci_dev *dev, const struct pci_device_i

28、d *id); void (*remove) (struct pci_dev *dev); int (*save_state) (struct pci_dev *dev, u32 state); int (*suspend)(struct pci_dev *dev, u32 state); int (*resume) (struct pci_dev *dev); int (*enable_wake) (struct pci_dev *dev, u32 state, int enable);1. 2pci_dev结构 这个数据结构也在文件include/linux/pci.h里,它具体描述了一个

29、PCI设备几乎全部的硬件信息,包括厂商ID、设备ID、各种资源等: struct pci_dev struct list_head global_list; struct list_head bus_list; struct pci_bus *bus; struct pci_bus *subordinate; void *sysdata; struct proc_dir_entry *procent; unsigned int devfn; unsigned short vendor; unsigned short device; unsigned short subsystem_vendor

30、; unsigned short subsystem_device; unsigned int class; u8 hdr_type; u8 rom_base_reg; struct pci_driver *driver; void *driver_data; u64 dma_mask; u32 current_state; unsigned short vendor_compatibleDEVICE_COUNT_COMPATIBLE; unsigned short device_compatibleDEVICE_COUNT_COMPATIBLE; unsigned int irq; stru

31、ct resource resourceDEVICE_COUNT_RESOURCE; struct resource dma_resourceDEVICE_COUNT_DMA; struct resource irq_resourceDEVICE_COUNT_IRQ; char name80; char slot_name8; int active; int ro; unsigned short regs; int (*prepare)(struct pci_dev *dev); int (*activate)(struct pci_dev *dev); int (*deactivate)(s

32、truct pci_dev *dev);2写驱动程序的fuctoin问题2.1 function的encode问题在写 C 程序的时候,一个程序只能有一个 main。Kernel 本身其实也是一个程序,它本身也有个 main,叫 start_kernel()。当我们把一个 module 载到 kernel 里的时候,它会跟 kernel 整合在一起,成为 kernel 的一部分。module是不能使用main的,由于 module 是处于被动的角色,它供应某些功能让别人去使用的。 Kernel 里有一个变量叫 module_list,每当 user 将一个 module 载到 kernel 里

33、的时候,这个 module 就会被记录在 module_list 里面。当 kernel 要使用到这个 module 供应的 function 时,它就会去 search 这个 list,找到 module,然后再使用其供应的 function 或 variable。每一个 module 都可以 export 一些 function 或变量来让别人使用。除此之外,module 也可以使用已经载到 kernel 里的 module 供应的 function。这种情形叫做 module stack。比方说,module A 用到 module B 的东西,那在加载 module A 之前必需要先加

34、载 module B。否则 module A 会无法加载。除了 module 会 export 东西之外,kernel 本身也会 export 一些 function 或 variable。同样的,module 也可以使用 kernel 所 export 出来的东西。module 所使用的 function 或 variable,要嘛就是自己写在 module 里,要嘛就是别的 module 供应的,再不就是 kernel 所供应的。你不能使用一般 libc 或 glibc所供应的 function。像 printf 之类的东西。 kernel 本身会 export 出一些function 或

35、 variable 来让 module 使用, Linux 供应一个command,叫 ksyms,你只要执行 ksyms -a 就可以知道 kernel 或目前载到 kernel 里的 module 供应了那些 function 或 variable。例如某系统的情形: c0216ba0 drive_info_R744aa133 c01e4a44 boot_cpu_data_R660bd466 c01e4ac0 EISA_bus_R7413793a c01e4ac4 MCA_bus_Rf48a2c4c c010cc34 _verify_write_R203afbeb . . . . . 在

36、kernel 里,有一个 symbol table 是用来记录 export 出去的 function 或 variable。除此之外,也会记录着那个 module export 那些 function。上面几行中,表示 kernel 供应了 drive_info 这个 function/variable。所以,我们可以在 kernel 里直接使用它,等载到 kernel 里时,会自动做好 link 的动作。由此,我们可以知道,module 本身其实是还没做 link 的一些 object code。一切都要等到 module 被加载 kernel 之后,link 才会完成。各位应当可以看到

37、drive_info 后面还接着一些惊异的字符串。_R744aa133,这个字符串是依据目前 kernel 的版本再做些 encode 得出来的结果。为什幺额外需要这一个字符串呢 ? Linux 有一个 config 的选项,叫做 Set version number in symbols of module。这是为了避开对系统造成不稳定。我们知道 Linux 的 kernel 更新的很快。在 kernel 更新的过程,有时为了效率起见,会对某些旧有的 data structure 或 function 做些转变,而且一变可能有的 variable 被拿掉,有的 function 的 prot

38、otype 跟原来的都不太一样。假如这种情形发生的时候,那可能以前 2.0.33 版本的 module 拿到 2.2.1 版本的 kernel 使用,假设原来 module 使用了 2.0.33 kernel 供应的变量叫 A,但是到了 2.2.1 由于某些缘由必需把 A 都设成 NULL。那当此 module 用在 2.2.1 kernel 上时,假如它没去检查 A 的值就直接使用的话,就会造成系统的错误。或许不会整个系统都死掉,但是这个 module 确定是很难发挥它的功能。为了这个缘由,Linux 就在 compile module 时,把 kernel 版本的号码 encode 到各个

39、 exported function 和 variable 里。 所以,刚才或许我们不应当讲 kernel 供应了 drive_info,而应当说 kernel 供应了 driver_info_R744aa133 来让我们使用。也就是说,kernel 认为它供应的 driver_info_R744aa133 这个东西,而不是 driver_info。所以,我们可以发觉有的人在加载 module 时,系统都始终告知你某个 function 无法 resolved。这就是由于 kernel 里没有你要的 function,要不然就是你的 module 里使用的 function 跟 kernel

40、encode 的结果不一样。所以无法 resolve。解决方式,要嘛就是将 kernel 里的 set version 选项关掉,要嘛就是将 module compile 成 kernel 有方法接受的型式。 假如kernel 认定它供应的 function 名字叫做 driver_info_R744aa133 的话,那我们写程序时,把用到这个funnction 的地方都改成 driver_info_R744aa133 就可以了。为了避开烦琐地对同一个functoin不动调用的地方的修改,linux为我们供应了 #define printk printk_Rdd132261 这样的形式来解决这

41、个问题。 假如将系统的 set version 的选项打开的话,可以到 /usr/src/linux/include/linux/modules 这个名目底下。这个名目底下有所多的 .ver档案。这些档案其实就是用来做 #define 用的。我们来看看 ksyms.ver 这个档案里,里面有一行是这样子的 : #define printk _set_ver(printk) set_ver 是一个 macro,就是用来在 printk 后面加上 version number 的。用了这些ver 档,我们就可以在 module 里直接使用 printk 这样的名字了。而这些 ver 档会自动帮我们

42、做好 #define 的动作。可是,我们可以发觉这个名目有很多很多的 ver 档。为了让我们知我们要呼叫的 function 是在那个 ver 档里Linux 又给我们供应了挂念。/usr/src/linux/include/linux/modversions.h 这个档案已经将全部的 ver 档都加进来了。所以在我们的 module 里只要 include 这个档,那名字的问题都解决了。但是,假如要将modversions.h 这个档在 module 里 include 进来,就要加上以下数行: #ifdef MODVERSIONS #include #endif 加入这三行的缘由是,避开这

43、个 module 在没有设定 kernel version 的系统上,将 modversions.h 这个档案 include 进来。当你把 set version 的选项关掉时,modversions.h 和 modules 这个名目都会不见。假如没有上面三行,那 compile 就不会过关。所以一般来讲,modversions.h 我们会选择在 compile 时传给 gcc 使用。就像下面这个样子。 gcc -c -D_KERNEL_ -DMODULE -DMODVERSIONS main.c -include usr/src/linux/include/linux/modversion

44、s.h 在这个 command line 里,我们看到了 -D_KERNEL_,这是说要定义 _KERNEL_ 这个 constant。很多跟 kernel 有关的 header file,都必需要定义这个 constant 才能 include 的。所以最好将它定义起来。另外还有一个 -DMODVERSIONS。要解决 fucntion 或 variable 名字 encode 的方式就是要 include modversions.h,其实除此之外,你还必需定义 MODVERSIONS 这个constant。再来就是 MODULE 这个 constant。其实,只要是你要写 module 就

45、肯定要定义这个变量。而且你还要 include module.h 这个档案,由于 _set_ver 就是定义在这里的。 2.2 function的export问题假如我们自己的 module 想要 export 一些东西让别的 module 使用,并且限定几个必要的东西 export 出去。在kernel 里供应了一个 macro,叫做 EXPORT_SYMBOL,这是用来帮我们选择要 export 的 variable 或 function。比方说,要export 一个叫 full 的 variable,就只需要在 module 里写: EXPORT_SYMBOL(full); 就会自动将

46、full export 出去,马上就可以从 ksyms 里发觉有 full 这个变量被 export 出去。在使用 EXPORT_SYMBOL 之前,必需在 gcc 里定义 EXPORT_SYMTAB 这个 constant,否则在 compile 时会发生 parser error。所以,要使用 EXPORT_SYMBOL 的话,那 gcc 应当要下: gcc -c -D_KERNEL_ -DMODULE -DMODVERSIONS -DEXPORT_SYMTAB main.c -include /usr/src/linux/include/linux/modversions.h 假如不想

47、export 任何的东西,那只要在 module 里写下 EXPORT_NO_SYMBOLS; 就可以了。使用 EXPORT_NO_SYMBOLS 用不着定义任何的 constant。 当使用 EXPORT_SYMBOL 把一些 function 或 variable export 出来之后,我们使用 ksyma -a 去看一些结果。我们发觉 EXPORT_SYMBOL(full) 的确是把 full export出来了 : c8822200 full my_module c01b8e08 pci_find_slot_R454463b5 . . . 假如在 module 的开头。加入一行 #d

48、efine full full_Rxxxxxx 之后,再重新 compile module 一次,载到 kernel 之后,就可以发觉 ksyms -a 显示的是 c8822200 full_Rxxxxxx my_module c01b8e08 pci_find_slot_R454463b5 . . . . . 了。 Linux 里供应了一个 command,叫 genksyms,是用来产生 .ver 的档案的。它会从 stdin 里读取 source code,然后检查 source code 里是否有 export 的 variable 或 function。假如有,它就会自动为每个 ex

49、port 出来的东西产生一些 define。 假设我们的程序都放在一个叫 main.c 的档案里,我们可以使用下列的方式产生这些 define。 gcc -E -D_GENKSYMS_ main.c | genksyms -k 2.2.1 main.ver gcc 的 -E 参数是指将 preprocessing 的结果 show 出来。也就是说将它 include 的档案,一些 define 的结果都开放。用一个管线是由于 genksyms 是从 stdin 读资料的,所以,经由管线将 gcc 的结果传给 genksyms。-k 2.2.1 是指目前使用的 kernel 版本是 2.2.1,

50、假如 kernel 版本不一样,必需指定 kernel 的版本。产生的 define 将会被放到 main.ver 里。产生完 main.ver 档之后,在 main.c 里将它 include 进来。使用这个方式产生的 module,其 export 出来的东西会经由 main.ver 的 define 改头换面。所以假如要让别人使用,那就必需将 main.ver 公开。 2. 3两个常用function要写一个 module,必需要供应两个 function。这两个 function 是给 insmod 和 rmmod 使用的。它们分别是 init_module(),以及 cleanup_

51、module()。 int init_module(); void cleanup_module(); 一般来讲,我们在 init_module() 做的事都是一些初始化的工作。比方说,我们的的 module 需要一块内存,那就可以在 init_module() 做 kmalloc 的动作。cleanup_module() 就是在 module 要移除的时候做的事,比方像把之前 kmalloc 的内存 free 掉。 由于 module 是载到 kernel 使用的,所以,可能别的 module 会使用你的 module,甚至某些 process 也会使用到你的 module,为了避开 mod

52、ule 还有人使用时就被移除,每个 module 都有一个 use count。用来记录目前有多少个 process 或 module 正在使用这个 module。当 module 的 use count 不等于 0 时,module 是不会被移除掉的。也就是说,当 module 的 use count 不等于 0 时,cleanup_module() 是不会被呼叫的。 以下的三个 macro,是跟 module 的 use count 有关系亲密的。 MOD_INC_USE_COUNT MOD_DEC_USE_COUNT MOD_IN_USE MOD_INC_USE_COUNT 是用来增加

53、module 的 use count,而 MOD_DEC_USE_COUNT 是用来削减 module 的 use count。 MOD_IN_USE 则是用来检查目前这个 module 是不是被使用中。也就是检查 use count 是否为 0。module 的 use count 必需由写 module 的人自己来 maintain。系统并不会自动为你把 use count 加一或减一,都可以自己来把握3设备驱动程序中的一些具体问题3.1 I/O Port和硬件打交道离不开I/O Port,老的ISA设备经常是占用实际的I/O端口,在linux下,操作系统没有对I/O口屏蔽,也就是说,任何

54、驱动程序都可对任意的I/O口操作,这样就很简洁引起混乱。每个驱动程序应当自己避开误用端口。 有两个重要的kernel函数可以保证驱动程序做到这一点。 check_region(int io_port, int off_set) 这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。 参数1:io端口的基地址, 参数2:io端口占用的范围。 返回值:0 没有占用, 非0,已经被占用。 request_region(int io_port, int off_set,char *devname) 假如这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用之前,必需向系统登记,

55、以防止被其他程序占用。登记后,在/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的内存。 这可以通过牺牲一些系统

温馨提示

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

最新文档

评论

0/150

提交评论