I/O体系结构和设备驱动程序(五)

4.5、直接内存访问(DMA)

最初的PC体系结构中,CPU是系统唯一的总线主控器,为了提取和存储RAM存储单元的值,CPU是唯一可以驱动地址/数据总线的硬件设备,随着诸如PCI这样的现代总线体系结构的出现,如果提供合适的电路,每一个外围设备都可以充当总线主控器。因此,现在所有的PC都包含一个辅助的DMA电路,它可以用来控制在RAM和I/O设备之间数据的传送。

 

DMA一旦被CPU激活,就可以自行传递数据;当数据传送完成之后,DMA发出一个中断请求。当CPU和DMA同时访问同一内存单元时,所产生的冲突由一个名为内存仲裁器的硬件电路来解决。

 

使用DMA最多的是磁盘驱动器和其他需要一次传送大量字节的设备,因为DMA设置时间较长,所以传送少量数据时直接使用CPU效率更高。

4.5.1、同步DMA和异步DMA

设备驱动程序可以采用两种方式使用DMA:同步DMA和异步DMA。第一种方式,数据的传送是由进程触发的;第二种方式,数据的传送是由硬件设备触发的。

 

采用同步DMA的如声卡,用户应用程序将声音数据写入与声卡数字信号处理器DSP对应的设备文件中,声卡驱动把写入的这些样本收集在内核缓冲区。同时,驱动程序命令声卡把这些样本从内核缓冲区拷贝到预先定时的DSP中。当声卡完成数据传送时,会引发一个中断,然后驱动程序会检查内核缓冲区是否还有要播放的样本;如果有,驱动程序就再启动一次DMA数据传送。

 

采用异步DMA的如网卡,它从一个LAN中接收帧,网卡将接收到的帧存储在自己的I/O共享存储器中,然后引发一个中断。其驱动程序确认该中断后,命令网卡将接收到的帧从I/O共享存储器拷贝到内核缓冲区。当数据传送完成后,网卡会引发新的中断,然后驱动程序将这个新帧通知给上层内核层。


4.5.2、DMA传送的辅助函数

当为使用DMA传送方式的设备设计驱动程序时,开发者编写的代码应该与体系结构和总线(就DMA传送方式来说)二者都不相关。由于内核提供了丰富的DMA辅助函数,因而现在上述目标是可以实现的。这些辅助函数隐藏了不同硬件体系结构的DMA实现机制的差异。

 

DMA辅助函数有两个子集:老式的子集为PCI设备提供了与体系结构无关的函数;新的子集则保证了与总线和体系结构两者都无关。介绍如下:

1.       总线地址

DMA的每次数据传送(至少)需要一个内存缓冲区,它包含硬件设备要读出或写入的数据。一般而言,启动一次数据传送前,设备驱动程序必须确保DMA电路可以直接访问RAM内存单元。

 

现已区分三类存储器地址:逻辑地址、线性地址以及物理地址,前两个在CPU内部使用,最后一个是CPU从物理上驱动数据总线所用的存储器地址。但还有第四种存储器地址,称为总线地址(bus address),它是除CPU之外的硬件设备驱动数据总线时所用的存储器地址。

 

从根本上说,内核为什么应该关心总线地址呢?这是因为在DMA操作中,数据传送不需要CPU的参与;I/O设备和DMA电路直接驱动数据总线。因此,当内核开始DMA操作时,必须把所涉及的内存缓冲区总线地址或写入DMA适当的I/O端口,或写入I/O设备适当的I/O端口。

 

在80x86体系结构中,总线地址与物理地址一致。然而,其他体系结构如Sun SPARC和HP Alpha都包括一个I/O存储器管理单元(IO-MMU)硬件电路,它类似微处理器分页单元,将物理地址映射为总线地址。使用DMA的所有I/O驱动程序在启动一次数据传送前必须设置好IO-MMU。

不同的总线具有不同的总线地址大小,ISA的总线地址是24位长,因此在80x86体系结构中,可在物理内存的低16MB中完成DMA传送----这就是为什么DMA使用的内存缓冲区分配在ZONE_DMA内存区中(设置了GFP_DMA标志)。原来的PCI标准定义了32位总线地址;但是,一些PCI硬件设备最初是为ISA总线设计的,因此它们仍然访问不了物理地址0x00ffffff以上的RAM内存单元。新的PCI-X标准采用64位的总线地址并允许DMA电路可以直接

寻址更高的内存。

 

在Linux中,数据类型dma_addr_t代表一个通用的总线地址。在80x86体系结构中,dma_addr_t对应一个32位长的整数,除非内核支持PAE,在这种情况下,dma_addr_t代表一个64位整数。

 

pci_set_dma_mask()和dma_set_mask()辅助函数用于检查总线是否可以接收给定大小的总线地址(mask),如果可以,则通知总线层给定的外围设备将使用该大小的总线地址。

2.       高速缓存的一致性

系统体系结构没有必要在硬件级为硬件高速缓存与DMA电路之间提供一个一致性协议,因此,执行DMA映射操作时,DMA辅助函数必须考虑硬件高速缓存。Why?假设设备驱动把一些数据填充到内存缓冲区中,然后立刻命令硬件设备利用DMA传送方式读取该数据。如果DMA访问这些物理RAM内存单元,而相应的硬件高速缓存行(CPU与RAM之间)的内容还没有写入RAM,则硬件设备读取的就是内存缓冲区中的旧值。

 

设备驱动开发人员可采用2种方法来处理DMA缓冲区,即两种DMA映射类型中进行选择:

l  一致性DMA映射

CPU在RAM内存单元上所执行的每个写操作对硬件设备而言都是立即可见的。反之也一样。

l  流式DMA映射

这种映射方式,设备驱动程序必须注意小心高速缓存一致性问题,这可以使用适当的同步辅助函数来解决,也称为“异步的”

在80x86体系结构中使用DMA,不存在高速缓存一致性问题,因为设备驱动程序本身会“窥探”所访问的硬件高速缓存。因此80x86体系结构中为硬件设备所设计的驱动程序会从前述的两种DMA映射方式中选择一个:它们二者在本质上是等价的。

而在MIPS、SPARC以及PowerPC的一些模型体系中,硬件设备通常不窥探硬件高速缓存,因而就会产生高速缓存一致性问题。

总的来讲,为与体系结构无关的驱动程序选择一个合适的DMA映射方式是很重要的。

 

一般来说,如果CPU和DMA处理器以不可预知的方式去访问一个缓冲区,那么必须强制使用一致性DMA映射方式(如,SCSI适配器的command数据结构的缓冲区)。其他情形下,流式DMA映射方式更可取,因为在一些体系结构中处理一致性DMA映射是很麻烦的,并可能导致更低的系统性能。

  • l  一致性DMA映射的辅助函数

通常,设备驱动程序在初始化阶段会分配内存缓冲区并建立一致性DMA映射;在卸载时释放映射和缓冲区。为分配内存缓冲区和建立一致性DMA映射,内核提供了依赖体系结构的pci_alloc_consistent()和dma_alloc_coherent()两个函数。它们均返回新缓冲区的线性地址和总线地址。在80x86体系结构中,它们返回新缓冲区的线性地址和物理地址。为了释放映射和缓冲区,内核提供了pci_free_consistent()和dma_free_coherent()两个函数。

  • l  流式DMA映射的辅助函数

流式DMA映射的内存缓冲区通常在数据传送之前被映射,在传送之后被取消映射。也有可能在几次DMA传送过程中保持相同的映射,但是在这种情况下,设备驱动开发人员必须知道位于内存和外围设备之间的硬件高速缓存。

为了启动一次流式DMA数据传送,驱动程序必须首先利用分区页框分配器或通用内存分配器来动态地分配内存缓冲区。然后驱动程序调用pci_map_single()或者dma_map_single()建立流式DMA映射,这两个函数接收缓冲区的线性地址作为其参数并返回相应的总线地址。为了释放该映射,驱动程序调用相应的pci_unmap_single()或dma_unmap_single()函数。

 

为避免高速缓存一致性问题,驱动程序在开始从RAM到设备的DMA数据传送之前,如果有必要,应该调用pci_dma_sync_single_for_device()或dma_sync_single_for_device()刷新与DMA缓冲区对应的高速缓存行。同样的,从设备到RAM的一次DMA数据传送完成之前设备驱动程序是不可以访问内存缓冲区的:相反,如果有必要,在读缓冲区之前,驱动程序应该调用pci_dma_sync_single_for_cpu()或dma_sync_single_for_cpu()使相应的硬件高速缓存行无效。在80x86体系结构中,上述函数几乎不做任何事情,因为硬件高速缓存和DMA之间的一致性是由硬件来维护的

即使是高端内存的缓冲区也可以用于DMA传送;开发人员使用pci_map_page()或dma_map_page()函数,给其传递的参数为缓冲区所在页的描述符地址和页中缓冲区的偏移地址。相应地,为了释放高端内存缓冲区的映射,开发人员使用pci_unmap_page()或dma_unmap_page()函数。

4.6、内核支持的级别

Linux内核并不完全支持所有可能存在的I/O设备,一般来说,有三种可能方式支持硬件设备:

l  根本不支持

应用程序使用适当的in和out汇编语言指令直接与设备的I/O端口进行交互。

l  最小支持

内核不识别硬件设备,但能识别它的I/O接口。用户程序把I/O接口视为能够读写字符流的顺序设备。

l  扩展支持

内核识别硬件设备,并处理I/O接口本身。事实上,这种设备可能就没有对应的设备文件。

第一种方式与内核设备驱动程序毫无关系,最常见的例子是X Window系统对图形显示的传统处理方式,这种方式效率高,但限制了X服务器使用I/O设备产生的硬件中断。为让X服务器访问所请求的I/O端口,这种方式仍需做一些其他努力,如,iopl()和ioperm()系统调用给进程授权访问I/O端口,只有root权限用户才可调用这两个系统调用。但是通过设置可执行文件的setuid标志,普通用户也可以使用这些程序。

 

新Linux版本支持几种广泛使用的图形卡。/dev/fb设备文件为图形卡的帧缓冲区提供了一种抽象,并允许应用软件无需知道图形接口的I/O端口的任何事情就可以访问它。此外,内核提供了直接绘制基本架构(Direct Rendering Infrastructure, DRI),DRI允许应用软件充分挖掘3D加速图形卡的硬件特性。

 

 

最小支持方法是用来处理连接到通用I/O接口上的外部硬件设备的。内核通过提供设备文件来处理I/O接口,应用程序通过读写设备文件来处理外部硬件设备。

 

最小支持优于扩展支持,因为它保持内核尽可能小。但PC中,仅串/并口处理使用了这种方法。最小支持的应用范围是有限的,因为当外设必须频繁地与内核内部数据结构进行交互时不能使用这种方法。这种情况下就必须使用扩展支持。

 

一般情况下,直接连接到I/O总线上的任何硬件设备(如内置硬盘)都要根据扩展支持方法进行处理:内核必须为每个这样的设备提供一个设备驱动程序。USB、PCMCIA或者SCSI接口,简而言之,除串口和并口之外的所有通用I/O接口之上连接的外部设备都需要扩展支持。

值得注意的是,与标准文件相关的系统调用,如open()、read()和write(),并不总让应用程序完全控制底层硬件设备。事实上,VFS的“最小公分母”方法没有包含某些设备所需的特殊命令,或不让应用程序检查设备是否处于某一特殊的内部状态。

 

已引入的ioctl()系统调用可以满足这样的需要。这个系统调用除了设备文件的文件描述符和另一个表示请求的32位参数之外,还可以接收任意多个额外的参数。例如,特殊的ioctl()请求可以用来获得CD-ROM的音量或弹出CD-ROM介质。应用程序可以用这类ioctl()请求提供一个CD播放器的用户接口。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值