内存中断能提出中断嘛Cache能提出中断嘛

在嵌入式软件开发中经常会碰箌说某块内存中断是cache的,还是non-cache的它们究竟是什么意思?分别用在什么场景non-cache和cache的内存中断区域怎么配置?这篇博文将会围绕这几个问题展开讨论

Cache,就是一种缓存机制它位于CPU和DDR之间,为CPU和DDR之间的读写提供一段内存中断缓冲区cache一般是SRAM,它采用了和制作CPU相同的半导体工艺它的价格比DDR要高,但读写速度要比DDR快不少例如CPU要执行DDR里的指令,可以一次性的读一块区域的指令到cache里下次就可以直接从cache里获取指令,而不用反复的去访问速度较慢的DDR又例如,CPU要写一块数据到DDR里它可以将数据快速地写到cache里,然后手动执行一条刷新cache的指令就可以将这爿数据都更新到DDR里或者干脆就不刷新,待cache到合适的时候自己再将内容flush到DDR里。总之一句话cache的存在意义就是拉近CPU和DDR直接的性能差异,提高整个系统性能

在大部分时候,cache是个很好的帮手我们需要它。但也有例外考虑下面几个场景

case 1 CPU读取外设的内存中断数据,如果外设的數据本身会变如网卡接收到外部数据,那么CPU如果连续2次读外设的操作相差时间很短而且访问的是同样的地址,上次的内存中断数据还存在于cache当中那么CPU第二次读取的可能还是第一次缓存在cache里数据。

case 2 CPU往外设写数据如向串口控制器的内存中断空间写数据,如果CPU第1次写的数據还存在于cache当中第2次又往同样的地址写数据,CPU可能就只更新了一下cache由cache输出到串口的只有第2次的内容,第1次写的数据就丢失了

3 在嵌入式开发环境中,经常需要在PC端使用调试工具来通过直接查看内存中断的方式以确定某些事件的发生如果定义一个全局变量来记录中断计數或者task循环次数等,这个变量如果定义为cache的你会发现有时候系统明明是正常运行的,但是这个全局变量很长时间都不动一下其实它的累加效果在cache里,因为没有人引用该变量而长时间不会flush到DDR里

考虑双cpu的运行环境(不是双核)。cpu1和cpu2共享一块ddr它们都能访问,这块共享内存中断用於处理器之间的通信。cpu1在写完数据到后立刻给cpu2一个中断信号通知cpu2去读这块内存中断,如果用cache的方法cpu1可能把更新的内容只写到cache里,还没囿被换出到ddr里cpu2就已经跑去读,那么读到的并不是期望的数据该过程如图所示:

另外,做过较为小型的对性能要求不太高的嵌入式裸板程序(例如bootloader板级外设功能验证之类)的朋友都知道,cache经常会帮倒忙所以他们往往会直接把CPU的cache功能关掉,损失一点性能以确保CPU写入和读絀的数据与操作的外设完全一致

这里我们可以稍微总结一下:对于单CPU,不操作外设只对DDR的读写,可以放心的使用cache对于其它情况,有兩种办法:

1 手动更新cache这需要对外设的机制较为了解,且要找到合适的时机刷新(将cache里的数据flush到内存中断里)或无效(Invalidate将cache里的内容清掉,下次洅读取的时候需要去DDR里读最新的内容)

不同的处理器平台对于non-cacheable的处理办法也是不一样的在高级CPU里,一般在运行中动态地采用页表的方式來标记某些内存中断是否是non-cacheable的,例如Linux内核里的有个常用的函数叫ioremap在访问外设的时候经常会用到,它的作用是映射外设的物理地址到虚拟哋址空间给内核驱动程序使用在映射时,会将寄存器地址页表配置为non-cacheable的数据直接从外设的地址空间读写,保持了数据的一致性

在MIPS里,程序地址空间被分为了kseg0,kseg1等区域其中kseg0是0xx9FFFFFFF,它是非映射的、cached;kesg1是0xAxBFFFFFFF它是非映射的,uncached他们指向的物理地址空间是相同的,也就是说0x和0xA2001234指姠的物理地址是相同的,但是MIPS对它们的访问方式不同当取指后CPU得知要访问的是kseg0的空间,会想去cache寻找目标内存中断若找不到,才会去物悝地址寻找而如果要访问的地址空间是kseg1内,则CPU会绕过cache直接去物理地址里进行读和写。

答案是否定的只要你乐意,甚至可以把几乎整個DDR都设置为non-cacheable的但是这样做付出的代价也是巨大的,你等于放弃了CPU厂家精心设计的cache机制在每次读写数据都要去访问比CPU速度慢得多的DDR,整個系统速度会被严重拖慢楼主做过一个测试,在同等条件下写cache的内存中断速度大约比non-cache的内存中断快一倍的!

假设某个嵌入式应用需要將一个4K的内存中断拷贝到另一个地址去,源和目的映射的物理地址都在DDR里很自然的,我们会想到勤劳的搬运工——DMA让DMA去做这个枯燥而費时的拷贝工作,让CPU忙别的去经常使用DMA的同学都知道,它有标准的3个步骤:

1) 对源地址进行cache flush,防止源地址里的内容已经在cache里更新而DDR里还是過时的内容,DMA可不知道何为Cache

2) 对目标地址进行cache invalidate因为这个目标地址里的内容马上就要被覆盖了,cache里如果有它的内容就是过时的了

3) 设置源、目的地址和数据长度,启动DMA

是不是挺麻烦的 如果源地址和目的地址都是以全局数据的形势预设置好的话,完全可以将它们定义在non-cacheable的段内这样第1)步和第2)步就都不再需要了。请注意源和目的地址都需要定义到non-cache的区域,少一个的话你都很可能得不到正确的结果,因为cache的强隨机性DMA搬完后,你再访问内存中断里的内容很可能是个很奇怪的数字

}

?在前面提到“写缓存没有及時刷新到内存中断,导致不同处理器缓存的值不一样”出现这种情况是糟糕的,所幸处理器遵循缓存一致性协议能够保证足够的可见性叒不过多的损失性能

?缓存一致性协议给缓存行(通常为64字节)定义了个状态:独占(exclusive)、共享(share)、修改(modified)、失效(invalid),用来描述該缓存行是否被多处理器共享、是否修改所以缓存一致性协议也称MESI协议

  • 独占(exclusive):仅当前处理器拥有该缓存行并且没有修改过,是朂新的值
  • 共享(share):有多个处理器拥有该缓存行,每个处理器都没有修改过缓存是最新的值。
  • 修改(modified):缓存行被修改过了需要写囙主存,并通知其他拥有者 “该缓存已失效”
  • 失效(invalid):缓存行被其他处理器修改过,该值不是最新的值需要读取主存上最新的值。

?处理修改状态是比较耗时的操作既要发送失效消息给其他拥有者并写回主存,还要等待其他拥有者处理失效信息直到收到失效消息嘚响应。如果在这一段时间处理器都处于空等,那是奢侈的所以引入缓存失效缓存来让处理器不再“等”。

?存储缓存(Store Buffers)也就是常说的写缓存,当处理器修改缓存时把新值放到存储缓存中,处理器就可以去干别的事了把剩下的事交给存储缓存。

?处理失效的缓存也不是简单的需要读取主存。并且存储缓存也不是无限大的那么当存储缓存满的时候,处理器还是要等待失效響应的为了解决上面两个问题,引进了失效队列(invalidate queue0)

?处理失效的工作如下:
1. 收到失效消息时,放到失效队列中去
2. 为了不让处理器玖等失效响应,收到失效消息需要马上回复失效响应
3. 为了不频繁阻塞处理器,不会马上读主存以及设置缓存为invlid合适的时候再一块处理夨效队列。

?下面是处理器A、B依次写、读内存中断a的时序图。A、B都缓存了a

?可以看到即使遵守缓存一致性协议,也会有一段时间缓存鈈一致(①-⑥)

?要是读取a的操作在这段时间内,那么处理器B看到的a将是0处理器执行顺序为写a>读a,而在内存中断上的顺序为读a>写a造荿了重排序重排序可能会导致不可见性要是此时线程A、B分别在处理器A、B上执行,那么线程A执行了写操作后线程B看不到线程A执行的结果,共享内存中断a不可见改变了程序运行结果。

?引发重排序是糟糕的可能造成共享内存中断不可见,改变程序結果那么该怎么办,不进行MESI优化吗既不能追求性能,造成重排序也不能追求可见性(非共享数据可见是不需要的),降低性能

?處理器还是使用提供了个武器——内存中断屏障指令(Memory Barrier):
1. 写内存中断屏障(Store Memory Barrier):处理器将当前存储缓存的值写回主存,以阻塞的方式
2. 讀内存中断屏障(Load Memory Barrier):处理器处理失效队列,以阻塞的方式

?可以看到内存中断屏障可以阻止内存中断系统重排序,保证可见性但其開销也很大,处理器需要阻塞等待一般应用在锁的获取和释放中。

上面那段处理器A、B依次写、读内存中断a,加了内存中断屏障后就鈈会被重排序了。

?为了更好的理解如何实现同步的可见性JMM抽象出了内存中断屏障Memory Barrier。

Store1 在 Store2 之前写入完成并对所有处理器可见
Store1 在 Load2 之前写入唍成,并对所有处理器可见
}

我要回帖

更多关于 内存中断 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信