【摘要】:如果硬件不支持Cache共享一致性,那么PCI设备进行DMA操作时,必须使用软件指令维护存储器与Cache的同步,从而避免Cache与主存储器不一致的现象发生。在x86处理器系统中,也存在类似的Cache指令。但是这两条指令都是针对整个Cache,而不是针对某个Cache行。如果需要对Cache行进行刷新时,x86处理器可以使用CLFUSH指令操作某个Cache行,该指令所实现的功能与dcbf指令类似。假设在一个处理器系统中,Cache行长度为64B。
Linux系统还提供了一组sync函数,如dma_sync_single、dma_sync_sg等函数,这组sync函数的主要作用是为了支持“不进行Cache共享一致性”的DMA操作。
如果设备进行DMA操作时,不需要硬件进行Cache一致性操作[100],那么处理器在DMA操作之前,需要使用软件指令将操作的数据区域与Cache进行同步,之后进行DMA操作。PCIe设备启动DMA请求时,如果其TLP头部Attr字段的No Snoop Attribute位为1[101]时,驱动程序也需要进行这种同步操作。在PowerPC处理器中,有一些非PCIe设备,如QE(QUICC Engine)中的一些内嵌设备,这些设备可以通过设置snoop位决定在DMA传送过程中,是否需要硬件进行Cache一致性操作。
目前多数RC或者HOST主桥都可以通过总线监听,解决PCI设备进行DMA操作的Cache一致性问题。但是有些RC,如MPC8572处理器的可以通过设置Inbound寄存器决定当前访问是否支持Cache一致性操作。
如果硬件不支持Cache共享一致性,那么PCI设备进行DMA操作时,必须使用软件指令维护存储器与Cache的同步,从而避免Cache与主存储器不一致的现象发生。
系统软件程序员使用软件指令维护Cache时,务必深入理解这些指令的特点和使用方法。在处理器系统的设计中,有两类错误最难被发现,一类是Cache与存储器系统的不一致,另一类是数据传送的序。
即使是对资深的系统程序员,也很难从这些错误表现形式中,发现是Cache不一致或者数据传送的序引发的系统错误。因此系统程序员需要重视在一个处理器系统中的Cache一致性(Cache Cohenrency)和数据完成性(Data Consistency)。
因此虽然对于多数PCI设备,Cache一致性可以由硬件保证,本节也必须讲述如何通过软件指令维护Cache的一致性。Linux系统使用dma_sync_single函数维护Cache的一致性。dma_sync_single函数的实现如源代码12-12所示。
源代码12-12 __dma_sync函数

不同的处理器系统使用不同的ops->sync_single_for_cpu操作函数。值得注意的是,Linux x86并没有实现ops->sync_single_for_cpu函数,因为使用软件指令维护Cache一致性的情况在x86处理器系统中并不多见。而Linux PowerPC使用dma_direct_sync_single_range函数实现ops->sync_single_for_cpus函数,该函数最终将调用__dma_sync函数。这两个函数的实现如源代码12-13所示。
源代码12-13 __dma_sync函数


在Linux PowerPC[102]中,flush_dcache_range、invalidate_dcache_range和clean_dcache_range函数分别使用dcbf、dcbi和dcbst指令实现。dcbi/dcbf/dcbst指令的格式为dcbi/dcbf/dcbst rA,rB,如源代码12-14所示。
源代码12-14 dcbi/dcbf/dcbst指令格式

dcbf、dcbi和dcbst指令的详细说明如下。
●dcbi指令首先在Cache中检查EA。如果EA在Cache中命中,将直接将EA所对应的Cache行的状态改变为I,无论这个Cache行原来的状态是什么,都不将数据回写到存储器中。
●dcbf指令首先在Cache中检查EA。如果EA在Cache中命中,则继续检查Cache的状态,如果为M,则将Cache行刷新到内存,然后Invalidate该Cache行,将Cache行的状态改变为I;否则直接Invalidate该Cache行。
●dcbst指令首先在Cache中检查EA。如果地址在Cache中命中,则继续检查Cache的状态,如果为M,则将Cache行回写到内存,然后将Cache行的状态改变为E;否则不做任何操作。(www.chuimin.cn)
在x86处理器系统中,也存在类似的Cache指令。如INVD、WBINVD和CLFLUSH指令。其中INVD指令的作用是Invalidate处理器中的内部Cache,并通过FSB总线周期Invali-date外部Cache;而WBINVD指令是在Invalidate内部和外部Cache之前,先将Cache中的数据回写,然后进行Invalidate操作。
但是这两条指令都是针对整个Cache,而不是针对某个Cache行。如果需要对Cache行进行刷新时,x86处理器可以使用CLFUSH指令操作某个Cache行,该指令所实现的功能与dcbf指令类似。单从操作Cache行的指令的角度上看,PowerPC处理器比x86处理器好得多,因此本节以PowerPC处理器为例说明这些Cache指令在不同情况下的使用方法。
下文将分别介绍在DMA写和DMA读操作中__dma_sync函数的工作流程。假设在一个单处理器系统[103]中,Cache行长度为512b,而且PCI设备进行DMA读写操作时,硬件不进行Cache一致性操作。
1.DMA写
在外部设备进行DMA写操作之前,需要使用__dma_sync函数同步Cache与存储器中的数据。有许多书籍包括Linux系统中的DMA-API文档,都认为在DMA写操作完成后,调用__dma_sync函数Invalidate数据区域所对应的Cache行,处理器就可以使用来自设备的数据。这种说法是基于设备访问的数据区域头尾都是Cache行对界的情况而言的,如果数据区域并不是Cache行对界时,这种做法将引发系统错误。
假设在一个处理器系统中,Cache行长度为64B。当一个PCI设备通过DMA写操作,访问0x1001~10FE这段数据区域时,这段数据区域将占用4个Cache行,而且并不是Cache行对界的,如图12-9所示。

图12-9 DMA写访问的数据区域不对界
如果在0x1000~0x10FF这段数据区域中,0x1000和0x10FF字节曾经被改写过,那么0x1000~103F和0x10C0~10FF这两个Cache行的状态为M,因此图12-9中阴影部分的数据与存储器不一致,而且为处理器系统中最新的数据。
而DMA写结束后,0x1001~10FE这段数据区域被PCI设备改写,且为处理器系统中最新的数据,此时这段数据区域对应的Cache行状态仍然为M。此时如果处理器Invalidate0x1001~10FE这段数据区域对应的Cache行,即Invalidate0x1000~0x10FF这段数据区域的Cache行时,将会丢失0x1000和0x10FF这两个字节中保存的合法数据。如果处理器刷新0x1001~10FE这段数据区域对应的Cache行,即刷新0x1000~0x10FF这段数据区域的Cache行时,将丢失所有来自PCI设备的数据,采用这种方法问题更大。
通过上述分析可以发现,如果在DMA写完成后,再对访问的数据区域进行Cache同步操作,将可能引发严重的Cache一致性问题,从而导致整个系统异常。
为此正确的方法是在DMA写操作之前,将其访问的数据区域与Cache进行一致性操作,如源代码12-13中“case DMA_FROM_DEVICE”所示。但是这段源代码仍然有较大的问题,因为这段代码在处理数据区域不对界的情况时,将刷新整个数据区域对应的Cache行。
这种做法不会产生错误,但是会影响效率。假设与0x1000~0x10FF这段数据区域对应的Cache行的状态都为M,那么这段程序将0x1000~103F、0x1040~0x107F、0x1080~0x10BF和0x10C0~10FF对应的Cache行都刷新到存储器中,然后再Invalidate这些Cache行。而实际上0x1040~0x10C0这段数据区域将由PCI设备重新填写,因而将这部分区域进行刷新然后再Invalidate是没有意义的。
正确的做法是刷新不对界的数据区域0x1000~1040和0x10C0~10FF,即将这段数据区域的头尾刷新即可,而直接Invalidate中间的数据区域0x1040~10BF。采用这种方法将有效地提高系统效率。
2.DMA读
外部设备进行DMA读操作之前,处理器必须保证该设备访问的数据区域与Cache一致。因此需要调用__dma_sync函数(dir参数为DMA_TO_DEVICE)将Cache中的数据回写到存储器。该函数执行完毕后,Cache中的所有数据都与存储器一致,而之前状态位为M的Cache行将更改为E。经过这个Cache同步操作后,设备进行DMA读操作就可以从存储器中获得正确的数据。
此处还有一个细节问题值得考虑,就是DMA读操作是调用dcbst指令回写Cache行,还是调用dcbf指令刷新Cache行。如上文所述dcbf和dcbst指令都将状态位为M的Cache行与存储器进行同步,只是dcbf将Cache行的M位更新为I,而dcbst指令将Cache行的状态更新为E。使用这两个指令都可以保证DMA读的数据不会出现一致性问题。
此时在处理器系统中,如果DMA读的数据将不会被处理器使用时,应该使用dcbf指令,Invalidate该Cache行,从而该Cache行可以被其他进程使用;如果DMA读的数据将很快被处理器使用时,应该使用dcbst指令,回写该Cache行,从而处理器使用该数据时,可以从Cache而不是存储器中获得。
相关推荐