欢迎来到学术参考网

Linux下的PCI-Express设备驱动程序的研究与实现

发布时间:2015-07-09 11:06
摘 要 本文了Linux下的PCI-Express高速数据采集卡的驱动程序,在内核空间申请了足够大的DMA循环缓冲区,从而满足了系统对高速数据实时采集处理的要求。分别实现了查询模式和中断模式下的驱动程序,并通过实验结果的表明了中断模式在其性能上的优异性,提出了PCI-Express设备驱动程序进一步优化的方向。
关键词 Linux;PCI-Express;DMA;设备驱动;中断

1 引言

PCI-Express已经成长为新一代的I/O总线技术的主流,已经在服务器、存储系统、通信系统、数据处理、传输系统等领域中得到广泛的。基于PCI-Express总线的高速数据采集卡是信息处理系统中一个非常重要的设备,而驱动程序是硬件和操作系统之间的桥梁,对硬件设备的性能有着很大的,高效的驱动程序是实现高速数据实时采集、处理的一个重要保证。因此本文针对PCI-Express高速数据采集卡在Linux系统下的驱动程序进行了研究,并就如何设计和实现Linux下的驱动程序进行了深入的探讨。

2 Linux设备驱动程序

2.1 驱动程序的功能

驱动程序的作用是应用程序与硬件之间的一个中间软件层,硬件通过驱动程序向应用程序展现其具有的功能。Linux将所有外部设备看成是一类特殊文件[1],称之为“设备文件”,如果说系统调用是Linux内核和应用程序之间的接口,那么设备驱动程序则可以看成是Linux内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节,使得应用程序可以像操作普通文件一样来操作外部设备。Linux操作系统抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是要实现这些系统调用函数。
Linux系统中的所有硬件设备都使用一个特殊的设备文件来表示,例如,系统中的第一个IDE硬盘使用/dev/hda表示。每个设备文件对应有两个设备号:一个是主设备号,标识该设备的种类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无法访问到设备驱动程序。

2.2 设备和模块的分类

Linux系统将设备分成字符设备(Char Device)、块设备(Block Device)、设备(Network Device)三种基本类型。字符设备是个能够像字节流(类似文件)一样被访问的设备,是以字节为单位逐个进行I/O操作,在对字符设备发出读写请求后,实际的硬件I/O紧接着就发生了;块设备和字符设备类似,两者之间的区别仅仅在于内核内部管理数据的方式上,也就是内核及驱动程序之间的软件接口上。块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。网络设备,任何网络事务都经过一个网络接口,即一个能够和其它主机交换数据的设备。
Linux系统下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态地卸载,不利于调试,因此推荐使用动态模块加载的方式。

3 PCI-Express驱动程序设计与实现

3.1 PCI-Express驱动程序实现的难点

传统的PCI/PCI-X总线传输最高突发速率是512Mbps,这远远不能满足对高速信号实时采集处理传输的需要,因此在设计时采用了PCI-Express总线来代替传统的PCI总线。PCI-Express与PCI在软件上兼容,但在硬件上改为串行差分传输,其主要优点是时延短、传输快、带宽提升潜力大和节省空间。PCI-Express采用的是点对点通信机制,各个插槽都将通过各自独享的通道发送和接收数据,这样就可以避免出现不同设备同时争抢系统和CPU资源的情况。PCI-Express有x1、x2、x4、x8、x16和x32多种线宽,如x1的传输带宽为2.5Gbps,x2为5Gbps,依次类推。
因此与以往PCI设备驱动程序在实现上不同的是基于Linux下的PCI-Express设备的驱动程序必须要注重考虑数据传输的速度和效率。以往的Liunx下PCI设备最大只能申请到11M的DMA内核缓冲区,显而易见这不能满足高速率数据传输的要求,同时在传输过程中对数据的抗抖动性也很差,因此为了能够很好的达到实际的应用要求,同时又能在数据传输过程中具有很好的抗抖动性,如何实现大容量的DMA内核缓冲区的申请、采用何种数据传输模式实现等等都是PCI-Express设备程序在设计时需要重点考虑和深入研究的。

3.2 PCI-Express逻辑结构图

图1是PCI-Express高速数据采集卡逻辑图。PCI-Express高速数据采集卡对接入的光信号经过光电转换模块进行转换成电信号后,再经过一系列的相关处理后经PCI-Express总线传输通过驱动程序传输至应用软件进行处理,传输带宽为PCI-Express x4。即单方向具有10Gbps的传输带宽。本项目中实现的PCI-Express高速数据采集卡在Linux系统中可以被认为一种字符设备。
在用模块方式实现PCI-Express设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块等。因此本文下面对如何分配和实现大容量的DMA循环缓冲区、及DMA数据传输处理模式的选择作了重点的研究并进行了详细的阐述。

图1 PCI-Express高速数据采集卡逻辑图

3.3 DMA循环缓冲区的分配与实现

对于高速数据信号的采集处理,需要在驱动程序的初始化模块中即系统成功加载驱动程序的过程中申请大量的DMA循环缓冲区,DMA缓冲区申请的大小直接关系着能否实时对高速数据处理的成败。直接内存访问或者叫DMA是实现高速数据快速传输处理的一种重要手段,DMA是一种硬件机制,它允许外围设备和主内存之间直接传输它们的I/O数据,而不需要系统处理器的参与,使用这种机制可以大大提高与设备通信的吞吐量,因为DMA的实现避免了大量的开销。
Linux内核把内存分为三个区段:可用于DMA的内存、常规内存以及高端内存。通常的内存分配都发生在常规内存区,但通过设置内存标志也可以请求在其它区段中分配。可用于DMA的内存是指存在于特别地址范围内的内存,
外设可以利用这些内存执行DMA访问,进行数据通信传输。因此,DMA缓冲区的分配要求是:物理连续、DMA可以访问、足够大。Linux系统是使用虚拟地址的系统,系统的内存分配函数提供的地址都是虚拟地址,必须经过virt_to_bus函数转换才能得到物理地址。分配内核内存空间的函数有三个: kmalloc 该函数实现小于128KB的内核内存的申请,所申请的空间是物理连续的;__get_free_pages实现最大4MB的内存申请(在Linux 2.6的内核下),以页为单位,一次申请或释放的页面数必须为2的幂,可一次允许申请到的最大页面依赖于体系结构,一般为2的(10或11次幂)页面,所申请的空间物理连续;vmalloc 虽然分配的虚拟地址空间是连续区域,但是在物理上可能是不连续的。此外,Linux内核还提供了一个专门用于PCI设备申请内核内存的函数pci_alloc_ consistent,该函数支持按字节长度申请。通过对内核源代码的,该函数是通过__get_free_pages函数实现对内核内存的分配,因此,其一次可能的最大分配空间也是4MB。
从上面的分析可以看出,Linux2.6内核中能够用于分配DMA缓冲区的函数有三个:kmalloc、__get_free_pages和pci_alloc_consistent,而且一次最大可分配的内存为4MB。
因此在高速信号采集处理系统中,必须实现超过4MB的内核内存申请。在本驱动程序的实现中采用了一种类似数组的内存管理机制,用__get_free_pages函数连续申请DMA内存块,然后从这些块地址排序中找到符合要求的连续内存块集,将这些块组成满足驱动程序要求的DMA缓冲区。使用完毕后,再通过这套管理机制释放这些内存块。
在这里需要说明的是,在大多数健全的系统上,申请DMA内存都是位于上面所述的区段,在x86平台上,DMA驱动是RAM的前16M;对于ISA等老的设备,进行DMA操作必须使用DMA内存区段,虽然对于PCI设备,没有这个限制,但是通过查证相关资料和在实际多次实验中可得,用以往的对PCI设备申请内存最大也只能申请到11M。因此必须对内存申请进行深入的和理解后大胆的进行各种尝试,通过研究发现在64位的系统上根据设置不同的内存分配标志位能申请到更大的符合驱动程序需求的DMA内存。所以在本驱动程序中实现了大小为512M的DMA内存申请,最大可申请到1G大小的
DMA内存(在实验中的操作系统为Linux 2.6.9-42、双CPU双核、内存8G),从而有效的减少了后端软件处理数据时带来的抖动。
另外,使用循环缓冲区能够有效地避免了系统对内存的并发访问,循环缓冲区使用一种叫做“生产者和消费者”的算法:一个进程通过DMA将数据放入缓冲区,另一个进程通过read函数将数据取出。由于在本驱动程序中,PCI-Express卡要求的DMA缓冲区大小相对固定,处理简单,缓冲区需要循环利用,所以在驱动程序的初始化阶段完成对DMA循环缓冲区的申请,卸载驱动程序时再释放DMA循环缓冲区。

3.4 共享中断处理

DMA数据传输有两种方式,一种是软件发起的数据请求(例如通过read函数调用);一种是硬件异步的将数据传递给系统。对于一个数据采集设备来讲,即使没有进程去读取数据,它也要不断的写入数据,随时等待进程的调用,因此,驱动程序应该维护一个循环缓冲区,当read调用时会随时返回给用户空间所要求的数据。
在高速数据采集设备上,常见的有查询模式和中断模式两种数据传输模式。对于查询模式,驱动程序需要在内核维护一个内核线程,动态的不间断的进行DMA,在发起一个新的DMA后,内核线程会持续的查询PCI-Express设备的相关寄存器,等待硬件将数据写入到DMA缓冲区完毕后,再发起新的DMA,周而复之。
在PCI-Express中向CPU发送中断请求的方法有两种:消息信号中断(Message Signaled Interrupt,MSI)和INTx虚拟中断(INTx Virtual Wire Interrupt)。在消息信号中断方式下,设备通过向操作系统预先分配的主存空间写入特定数据的方式来请求CPU的中断服务。消息信号中断是PCI Express系统中首选的中断信号机制。但是,在某些系统中可能会有不支持MSI机制的设备存在。对于PCI Express到PCI/PCI-X的桥接设备和不能使用MSI机制的传统端点设备,采用INTx虚拟中断机制。
PCI-Express设备注册中断时必须使用共享中断方式。Linux系统通过request_irq函数实现中断处理程序的注册,调用request_irq的正确位置应该是在设备第一次打开、硬件被告知产生中断之前,同样调用free_irq的时机应该是最后一次关闭设备、硬件被告知不用中断处理器之后释放中断例程。
中断处理例程的功能就是将有关中断接收的信息反馈给设备,并根据正在服务的中断的不同含义对数据进行相应的读或写操作,其处理流程如图2所示。当中断信号到来时,系统会调用相关的中断处理例程,中断服务程序首先判断该中断号是不是自己的中断,如果不是,则立即返回;如果是,则清除中断寄存器相关的位,即在驱动程序发起新的DMA之前设备将不会产生其它的中断,然后再进行相应的处理,比如更新DMA循环缓冲区的指针,发起新的DMA等。
图2 中断处理流程图
中断服务程序在执行时,会屏蔽同级中断甚至所有其它中断。因此,中断服务程序应该执行得越快越好,以免延误其它中断,而丢失信息。对于时间要求很严格的操作,应该在中断服务程序中执行,完成对硬件中断的即时响应。而对于时间要求不严格的操作,则应该移到中断服务程序之外再去完成,以便及时响应较低优先级的设备中断。这样,整个中断处理流程就被分为两个部分。第一部分是中断服务程序,另一部分称为下半部分处理(Bottom Half)。
在PCI-Express驱动程序的实现过程中,为了对比这两种传输模式在性能上的优劣,作者分别实现了查询模式和中断模式的驱动程序。通过实验分析表明,对于查询模式,驱动程序中需要始终维护着一个内核线程,运行时需要占用一个系统内核的资源,同时在高速数据处理传输过程中影响着应用级线程,占用系统资源的同时造成了处理数据的丢失;而中断模式,占用系统资源很少,同时对应用级线程的影响微乎其微,为今后更高速率的数据接入处理打下了良好的基础。

3.5 数据读写和ioctl控制模块

数据读写模块相对简单,在应用进程不需要数据时,驱动程序动态的维护着DMA循环缓冲区,一旦应用进程向驱动程序请求数据时驱动程序立即响应,通过Linux 内核提供 copy_from_user()/copy_to_user() 函数来实现内核态与用户态之间的数据拷贝。
除了读取和写入设备之外,大部分驱动程序还需要另外一种能力,即通过设备驱程序执行各种类型的硬件控制。简单数据传输之外,大部分设备可以执行一些其它操作,比如,用户空间经常会请求设备锁门、报告错误信息、设置寄存器等等,这些操作通常都是通过ioctl方法来支持。在本驱动程序中,通过ioctl提供了对PCI-Express卡给定的寄存器进行配置、管理操作接口。

3.6 驱动程序开发中所遇到的问题

在基于PCI-Express驱动程序的实际开发过程中,需要考虑的问题还很多,在对如何实现大量DMA循环缓冲区的申请及数据传输模式的选择、Ioctl怎样对硬件进行控制等重点难点问题充分考虑的同时,也不能忽略一些细节问题,比如对前端接入的信号是否已进入采集卡的判别、信号中断时的报警、信号经过FPGA的逻辑处理后传输是否正常等等,虽然这些都是一些小问题,但是在实际的工程实现时马虎不得,需要我们在实际开发中不断的积累经验,避免在以后的设计实现中出现类似的错误。

4 结束语

综上所述,本文在Linux系统下实现了基于PCI-Express总线的高速信号数据采集卡的驱动程序和各种实用功能。通过对Linux 2.6内核源码的分析和理解,在多次尝试后实现了在内核空间中申请到足够大的可用来作DMA的循环缓冲区,这为实现高速数据采集处理打下了坚实的基础。同时通过两种不同数据传输模式的实现结果表明,中断模式下的驱动程序更好的节省了系统的资源,提高了数据传输效率,在实际工程应用中显示出其突出的优异性。
另外,随着PCI-Express互连技术的向前,第三代I/O传输总线PCI-Express 2.0规范已经正式发布,其每根串行线的传输速率由2.5Gbps提高至5Gbps,对驱动程序和应用软件的实时处理又无形之中增加了压力,同时在实际的驱动程序研究和开发中也了解到,在内核空间与用户空间数据交换中伴随着大量的系统调用,需要耗费大量的系统资源,而内存映射能够进一步提高用户程序直接访问设备内存的能力,实现真正意义上的内核空间到应用空间的数据“零拷贝”传输,这也是今后驱动程序优化的一个重要方向。

[1] JonathanCorbet,GregKroah-Hartman,AlessandroRubini着. Linux Device Drivers,3rd Edition O’Reilly February 2005
[美]Ravi Budruk,DonAnderson,TonShanley着.系统体系结构标准教材.田玉敏,王崧,张波译.PCI Express 出版社,2005.11
Michael Beck,Harald Bohme,Mirko Dziadzka,Ulrich Kunitz,Robert Magnus,Claus Schroter,Dirk Verworner着. Linux 内核编程指南(第三版).张瑜,杨继萍等译.清华大学出版社,2004.10
陈俊楷,冯穗力,叶梧. Linux下PCI设备驱动程序研究.机应用研究,P23~26 2002.11

上一篇:微机常见硬盘故障分析及检测

下一篇:基于B/S模式的医院信息管理系统的实现