MIPS架构深入理解9-向MIPS移植软件之Cache管理

2022-08-15 16:25:28 浏览数 (1)

  • 1 Cache管理和DMA数据
  • 2 Cache管理和写指令数据
  • 3 Cache管理和非Cache或直写数据
  • 4 Cache重影和页着色
  • 5 总结

站在巨人的肩膀上,才能看得更远。 If I have seen further, it is by standing on the shoulders of giants. 牛顿

这是向MIPS架构移植软件的问题系列之第二篇。上一篇《MIPS架构深入理解8-向MIPS架构移植软件之大小端问题》中,我们讨论了大小端对于移植代码的影响。那么本文,我们再从Cache理解一下对于移植代码的影响,尤指底层代码或操作系统代码。

在之前的文章《MIPS高速缓存机制 》中,我们已经了解了初始化和正确操作Cache的方法。本段主要讲解一些可能出现的问题,并解释如何处理这些问题。

大部分时候,Cache对软件都是不可见的,只是一个加速系统执行的工具。也就是编程人员无需干预。但是,当需要处理DMA控制器及其类似的事物时,考虑把Cache作为一个独立的内存缓存会很有帮助,如下图所示:

我们知道,Cache和内存之间的传输总是以16字节或32字节对齐的内存块作为传输单元。即使CPU只是读取一个字节,仍然会加载这样的内存块到Cache行中。

理想情况下,内存的状态与CPU请求的所有操作都是最新的,每个有效的Cache行都保存一份正确的内存备份。不幸的是,实际的系统根本就达不到这种理想的情况。假设每次复位之后都初始化缓存,并且也不存在《MIPS高速缓存机制》一文中提到的Cache重影问题。Cache和内存之间还是会存在数据不一致的问题。如下:

  • Cache中的旧数据: 当CPU向Cache的内存区写入数据时,它会更新Cache中的备份,同时写入内存。但是,如果通过其它方式更新了内存,那么Cache中的备份就有可能成为旧数据。比如,DMA控制器写内存,或者,CPU往内存中写入新指令,I-Cache继续保持原先的指令等。所以,编程人员应该注意,硬件是不会自动处理这些情况的。
  • 内存中的旧数据: 当CPU写数据到Cache行中(回写),数据不会立即复制到内存中。稍后,CPU读取数据,直接读取Cache拷贝,一切没有问题。但是,如果不是CPU读取数据,而是其它控制器直接从内存读取数据,就会获取旧值。比如,向外传送的DMA。

为此,MIPS架构提供了Cache指令,可以根据需要调用它们,消除这种内存和Cache的不一致性。这些指令可以将最新的Cache数据写回到内存中,或者根据内存的最新状态失效对应的Cache行中的内容。

当然了,我们可以把数据映射到非Cache的内存区,比如kseg1区域。比如,网络控制器,映射一段非Cache的内存区保存读写的数据和标志位;这样可以方便快速的读取数据,因为不需要同步Cache和内存中的数据。相同的,内存映射的I/O寄存器,最好也映射到非Cache的内存区,通过kseg1或其它非Cache内存区中的指针进行访问。如果为I/O使用经过Cache的内存区,可能发生坏事情。

如果你需要使用TLB映射硬件寄存器,从而进行访问,你可以标记页转换为非Cache内存区(当然了,这不经常使用)。当I/O寄存器的内存地址不在低512M物理地址空间的时候,该方法是非常有用的。

还有的使用情况就是映射类似图像帧缓冲区为使用Cache的内存区,充分利用CPU的Cache充填和回写的block读写速度,提高像素帧的刷新频率。但是,每次图像帧的访问,都需要失效和回写Cache,显式地管理Cache。有一些嵌入式CPU,可能会提供一些奇怪但好用的Cache选项,请仔细检查对应芯片的手册。

1 Cache管理和DMA数据

Cache管理和DMA数据传输是一个很容易出错的地方,即使很有经验的编程者也常常会犯错。对此,不要犯怵;只要清晰地知道自己想干什么以及怎么干,就能让Cache和DMA传输正常工作。

比如,当从网络上接收到数据后,DMA设备会直接把数据存进内存,大部分MIPS系统不会更新Cache–即使某些Cache行中持有的地址落在DMA传输更新的内存区域中。随后,如果CPU读取这些Cache行的数据,将会读取Cache中旧的、过时的数据;就CPU而言,没有被告知内存中已经有了新数据,Cache中的数据仍然是合法的。

为了避免这种情况,你的程序必须在CPU尝试读取落在DMA缓冲区对应地址范围的数据前,主动失效对应Cache行中的内容。应该将DMA缓冲区的边界和Cache行的边界对齐,这样更容易管理。

对于通过DMA向外传输数据,比如网络通信,你必须在允许DMA设备传输数据之前,完全确保Cache中的数据都已经更新到对应的内存发送区域里了。也就是说,在你的程序写完需要由DMA发送的数据信息之后,必须强制写回所有的落在DMA控制器映射的内存地址范围的Cache行中的内容到内存中。只有这样,才能安全启动DMA传输。

有些MIPS架构CPU,为了避免显式的回写操作,配置为直写式Cache。但是,这种方案有一个缺点,直写式Cache会造成总体性能上更慢,也会增加系统的电源功耗。

当然,你也可以通过映射DMA的传输数据区到非Cache内存地址区,避免显式的失效和回写操作。这也是不推荐的一种方式,因为从整体上会降低系统的性能。因为使用Cache读写内存的速度肯定要快于直接从内存读取数据。最好的建议就是使用Cache,只有下面的情况避免使用Cache:

  • I/O寄存器: MIPS架构没有专门的I/O指令。所以,所有的外设寄存器都必须被映射到一段内存地址空间上。如果使用Cache,会发生一些奇怪的事情。
  • DMA描述符数组: 复杂的DMA控制器和CPU共享控制和状态信息,这些信息保存在内存中的描述符数据结构中。通常,CPU使用这些描述符结构体创建一个待发送数据信息的列表,然后,只需告诉DMA控制器开始工作即可。如果你的系统使用描述符结构,请将其映射到非Cache内存地址区域。

移植性比较好的操作系统,比如Linux,不管是复杂的、不可见的Cache,还是简单的Cache,都能很好的适配。即,Linux一般提供一组很完备的API,供驱动编写者使用。

2 Cache管理和写指令数据

假设,我们想在程序的执行过程中,产生新的代码,然后跳转到新代码中执行。常见的示例有在线更新程序。必须确保正确的Cache行为。

如果不注意,这个过程中,可能会在两个阶段带来非预期的结果:

  • 首先,如果你使用的是回写式D-Cache,你写的指令数据在没有触发相关Cache行的回写操作之前,一直停留在Cache中,并没有写入到内存中。如果,此时CPU尝试执行这些新的代码指令,因为仍然在D-Cache中,CPU无法访问到它们。所以,当CPU写完新指令数据后,首先要做的就是执行回写操作,保证数据写入到内存中。
  • 其次,不管你使用的是哪种类型的D-Cache,在你把指令数据写入到内存中后,你的I-Cache里仍然持有着这些地址之前的数据。所以,在CPU执行新写的代码指令之前,软件首先应该失效I-Cache中的相关行。

当然了,你也可以使用非Cache区域保存新的代码指令,然后执行它们。但是,这毕竟放弃了Cache的加速效果不是。

我们在《MIPS高速缓存机制》一文中描述的Cache管理指令都是协处理器CP0指令,只有特权级的代码才能使用。一般情况下,DMA操作也是内核完成的,这些都没有异议存在。但是,当用户态的应用程序也想要这样写指令,然后执行的话(比如,现在的即时性的解释性语言),却无法访问这些指令。

所以,MIPS32/64提供了synci指令,它可以执行D-Cache的回写操作和I-Cache的失效操作。具体可以参考MIPS指令集参考。

3 Cache管理和非Cache或直写数据

如果你混合使用Cache和非Cache程序地址访问同一段物理内存空间,你需要清楚这意味什么。使用非Cache程序地址往物理内存中写入数据,可能会造成D-Cache或I-Cache中保留一份过时的拷贝(相同地址)。使用非Cache程序地址直接从内存中加载数据,可能是旧数据,而最新的数据还停留在Cache中。

上电复位后,在引导系统进入一个已知状态的底层代码中,使用Cache和非Cache程序地址引用同一段物理地址空间是非常有用,甚至是有非常有必要的。但是,对于运行中的代码,一般不要这样做。而且,不管是使用Cache程序地址,还是使用非Cache程序地址访问物理内存,一定要保证它的一致性。

4 Cache重影和页着色

我们在《MIPS高速缓存机制》一文中已经描述了Cache重影的根源。L1级Cache使用虚拟地址作为索引,而使用物理地址作为Tag标签,如果索引的范围大于、等于2个page页,就可能发生Cache重影。索引范围等于一组Cache的大小,所以,使用4KB大小的page页的话,在8KB大小的直接映射Cache或着32KB大小的4路组相关的Cache上就可能会发生Cache重影。

发生Cache重影会有什么后果呢?在进程上下文切换的时候,必须首先清空Cache,要不然,上个进程映射的物理地址,可能与新进程映射的物理地址相同,导致同一物理地址在Cache上有2份拷贝,可能会导致意想不到的后果。再比如,使用共享内存的时候,多个进程的虚拟地址都可能引用这个数据,如果发生Cache重影,那么也会导致共享内存中的数据不正确。

为此,聪明的软件工程师们想了一个巧妙的技巧:页着色技术,又称为Cache着色,其实都是一回事,叫法不一样而已。具体的做法就是,假定page页的大小是4K,然后给每一个page页分配一个颜色(此处的颜色就是一种区分叫法而已,没有任何实际动作),使用虚拟地址的某几个比特位来标记颜色。当然,也可以选择使用物理地址中的某些比特位标记颜色。相同颜色的虚拟地址对应一组Cache。所以,两个虚拟地址想要指向同一个物理地址的数据,必须具有不同的页颜色。也就是说,页着色技术要求页分配程序把任一给定的物理地址映射到具有相同颜色的虚拟地址上。

颜色数是否与Cache的way数相等?应该是相等的。

比如说,Linux操作系统,多个虚拟地址可能都会访问一个物理页(共享库)。

大部分时候,操作系统OS对于共享数据的虚拟地址的对齐肯定满足要求-共享进程也可以不使用相同的地址,但是,我们必须保证不同的虚拟地址必须是64K的倍数,所以不同的虚拟地址具有相同的颜色。也就避免了Cache重影。这可能消耗更多的虚拟内存,但是虚拟内存又不值钱,对吧?

0 人点赞