操作系统概念说
让我们看一个人为的但信息丰富的例子。假设页面大小为 128 个字。考虑一个 C 程序,其功能是将 128×128 数组的每个元素初始化为 0。下面的代码是典型的:
int i, j; int[128][128] data; for (j = 0; j < 128; j++) for (i = 0; i < 128; i++) data[i][j] = 0;
请注意,该数组存储的是行主数;即数组存储的是data[0][0],data[0][1],····,data[0][127],data[1][0],data[1][1] ,····,数据[127][127]。对于 128 个字的页面,每行占一页。因此,前面的代码将每一页中的一个字归零,然后将每一页中的另一个字归零,依此类推。如果操作系统为整个程序分配的帧数少于 128 个,则其执行将导致 128 × 128 = 16,384 个页面错误。
高亮中的这句话是否意味着在初始化数组的每个元素时,都会发生页面错误,并且在页面替换和元素初始化之后,该页面立即从RAM中移出?
“操作系统为整个程序分配少于128帧”并不一定意味着“操作系统为整个程序分配单个帧”。那么为什么文本如此确定,最近的页面在被访问后立即从 RAM 中移出呢?
假设操作系统为程序分配了“n”个帧(小于 128 个)。它可以只在 RAM 中保留“n-1”页(即行),并使用剩余的一页来处理所有页错误和替换吗?那么页面错误总数可以从 128*128 减少到 (n-1) + (128-(n-1))*128 吗?
答案1
那么为什么文本如此确定,最近的页面在被访问后立即从 RAM 中移出呢?
通常,最近最少访问的页面将被驱逐,但这确实会导致所描述的病态行为。第一次经过内循环,第一次n帧被分页;然后当页面n+1需要分页,页面1被调出,如此循环,确保每次循环时所有页面都需要调回。
然而,这个场景是真的不太可能。如果系统完全缺乏 RAM(物理和交换),内核将终止一个程序以释放一些内存;考虑到测试程序的行为,它不太可能成为候选程序。如果系统只是缺乏物理 RAM,内核将交换页面,或减少其缓存;如果它交换页面,则不太可能以测试程序为目标。在这两种情况下,测试程序都将有足够的 RAM 来适应其工作集。如果您确实设法仅使测试程序挨饿(例如通过增加它的工作集,使其占据系统的内存),在实践中,你更有可能看到它被 a 杀死,SIGSEGV
而不是看到它不断地将其工作集调入和调出。 (这很好,这是教科书上的思想实验。学习由此产生的原理,不一定要尝试将示例应用到实践中。)
它可以只在 RAM 中保留“n-1”页(即行),并使用剩余的一页来处理所有页错误和替换吗?
它可以,但系统这样做是不寻常的;系统如何知道未来的内存访问模式会是什么样子?一般来说,您会看到 LRU 驱逐,因此循环将表现出如上所述的病态行为。
如果您想尝试一下,请修复程序,使其与页面的 4KB 大小匹配(在 x86 上使用;我在这里假设 64 位 x86 上的 Linux),并实际编译:
int main(int argc, char **argv) {
int i, j;
int data[128][1024];
for (j = 0; j < 1024; j++)
for (i = 0; i < 128; i++)
data[i][j] = 0;
}
然后使用 运行它/usr/bin/time
,这将显示页面错误的数量:
0.00user 0.00system 0:00.00elapsed 100%CPU (0avgtext+0avgdata 1612maxresident)k
0inputs+0outputs (0major+180minor)pagefaults 0swaps
在实践中,与页面错误相比,这种数组处理会导致更多的缓存行驱逐问题。