本节书摘来自异步社区《现代体系结构上的UNIX系统:内核程序员的对称多处理和缓存技术(修订版)》一书中的第2章,第2.10节,作者:【美】Curt Schimmel著,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.10 独立的指令高速缓存和数据高速缓存
将指令高速缓存和数据高速缓存分开的做法目前在计算机系统中相当常见。这种做法能够有效地使高速缓存的带宽加倍,因为它能让CPU从指令高速缓存预取指令的同时把数据载入或者保存到数据高速缓存中。图2-17描绘了这样的一种组织结构。本章提到的所有处理器,除了Intel 80486之外,其片上高速缓存都有独立的指令高速缓存和数据高速缓存。
因为两块高速缓存都可以同时访问主存储器(缺失处理或者写操作),所以要由硬件来仲裁高速缓存对主存储器的访问,这对于软件来说是透明的。注意,没有办法直接把数据保存到指令高速缓存中。因此,指令高速缓存是只读高速缓存。
这种组织结构最重要的方面就是缺乏数据高速缓存和指令高速缓存之间的直接互连。如果在指令高速缓存中没有命中某个指令,那么指令高速缓存就无法到数据高速缓存中查找它。指令高速缓存始终都是从主存储器读取指令来完成缺失操作。类似地,数据高速缓存中的缺失也要从主存储器读取。把数据保存到数据高速缓存中不会影响指令高速缓存的内容。虽然这是一种最简单的实现,但是它可能会产生高速缓存的不一致性,因为主存储器的内容可能会被缓存在一个以上的地方。如果使用单一的、将指令和数据结合在一起的高速缓存,就不会出现这类不一致性。
考虑使用自身能够修改代码的程序时的情形。这类程序包括诸如LISP解释器这样的程序,因为对于它们来说,部分编译它们正在解释的程序并非鲜见(编译后的代码通常写入进程的数据区)。如果要执行的指令是在数据区动态生成的,那么有可能出现两种不一致性。第一,如果使用写回高速缓存机制,那么最近写的指令可能尚未写入主存储器。这意味着,如果程序试图执行这些新指令,那么指令高速缓存可能会从内存中取得过时的指令。第二,一旦动态生成的指令被缓存在指令高速缓存中,那么程序的任何写操作(以此用新指令来替换那些在指令高速缓存中的老指令)都不会对指令高速缓存造成影响。新指令将写入数据高速缓存,并且最终写入主存储器,但是指令高速缓存不知道要获取新值。它会继续执行过时的老指令,直到行替换删除这些指令为止。在这种情况下,对那些指令的下一次引用将会造成一次缺失,并且从主存储器读取新指令。
遗憾的是,操作系统不能把高速缓存隐藏起来,从而让这类程序无视高速缓存的存在,因为操作系统没有办法知道程序会在什么时候试图执行其数据空间的某个部分。唯一的解决方法是提供特殊的系统调用,以便在程序已经产生了一组它现在想要执行的指令时就通知操作系统。接着,如果在数据高速缓存中使用了写回高速缓存机制,那么操作系统就使主存储器有效,并且使指令高速缓存的内容无效(在提供特殊指令来冲洗高速缓存的体系结构上,如果应用程序能够直接执行高速缓存冲洗指令,那么就不一定要有特殊的系统调用)。对指令和数据高速缓存的冲洗通常作为分开的硬件操作来实现。
一般而言,只要操作系统需要使被缓存的数据无效来保持一致性,那么它也必须使被缓存的指令无效。各种特定的实例则取决于高速缓存的体系结构,下面的章节将讨论它们。
虽然有可能让构建的系统中的硬件自动保持指令高速缓存和数据高速缓存的同步,但是却很少这样做。这样的系统会要求每次在数据高速缓存上执行写操作的时候都检查指令高速缓存并可能使其无效。这些额外的对指令高速缓存的访问可能会干扰指令的获取操作,并使其变慢。那些能够修改自身的代码实例很少值得为其在硬件上增加复杂性。