我知道读/加载操作理论上应该如何在操作系统中工作。读取指令会导致 TLB 查找,然后查找高速缓存,然后查找主内存,最后如果在前一级别不满足则从磁盘读取。
新文件的写操作如何进行?显然,当写入现有文件时,可能会先读取该文件,然后写入相应的缓存行。但新文件不会有任何可写入的缓存行。
CPU 是否能够“创建”一个尚未由写入支持内存的新缓存行?或者CPU是否必须告诉RAM创建一些空内存,然后将空内存加载到缓存行中,以便它可以写入那些空缓存行?这意味着所有写入操作都需要事先进行加载操作。
我知道读/加载操作理论上应该如何在操作系统中工作。读取指令会导致 TLB 查找,然后查找高速缓存,然后查找主内存,最后如果在前一级别不满足则从磁盘读取。
新文件的写操作如何进行?显然,当写入现有文件时,可能会先读取该文件,然后写入相应的缓存行。但新文件不会有任何可写入的缓存行。
CPU 是否能够“创建”一个尚未由写入支持内存的新缓存行?或者CPU是否必须告诉RAM创建一些空内存,然后将空内存加载到缓存行中,以便它可以写入那些空缓存行?这意味着所有写入操作都需要事先进行加载操作。
当写入一个新文件时,内核必须分配一页物理内存来保存文件数据。保存文件内容(包括干净数据和脏数据(尚未写入磁盘))的内核页面称为“页面缓存”。这与CPU缓存无关。
物理内存是连续存在的,并不是分配时产生的。分配只是一种软件机制,用于决定哪些存储/负载将去往何处。CPU缓存基于物理地址进行缓存。(一些旧的CPU已经使用了虚拟索引虚拟标记的L1缓存,至少是一些旧的非x86 CPU,因此当页表映射发生变化时,软件内存分配必须使虚拟缓存失效。现代Intel uop缓存实际上是这样寻址的,失效是在硬件中完成的。)
是的,在该核心拥有缓存行的 MESI 独占所有权之前,存储无法将存储缓冲区提交到缓存。通常,这涉及执行“读取所有权”以便可以更新内容。如果一次存储整个高速缓存行,则 CPU 核心可能会使其他高速缓存中的副本无效,而无需花费 DRAM 带宽来读取旧值,例如使用 x86 NT 存储(非临时的,例如 )
_mm_stream_ps
或rep stos
或rep movs
。有关 no-RFO 存储的更多信息,请参阅memcpy 的增强型 REP MOVSB 。在像 Linux 这样的 POSIX 系统上,您的 shell 将进行两个系统调用:
fd = open("new_file.txt", O_CREAT|O_TRUNC|O_WRONLY, 0666)
。如果 inode 尚不存在,则会创建它(如果不是 tmpfs,文件系统会将一些元数据 I/O 排队以在某个时刻进入磁盘,通常是在超时后,以防稍后发生更多元数据操作)。操作系统还缓存 VFS(虚拟文件系统)结构,而不仅仅是文件数据。无论如何,文件大小仍然为零,因此甚至还不需要分配数据的页面缓存页面。如果之前有数据,O_TRUNC 会丢弃它,并释放页面缓存中所有热页。write(fd, "hello\n", 6);
- 再次假设Linux,sys_write
内核函数(使用用户空间传递的参数调用)将注意到它需要分配一个物理页作为保存该文件数据的页缓存页。(也许将其归零,或者至少将其不打算复制到的部分归零,因为块设备的 I/O 通常在 512B 或 4K 扇区中工作,并且最好不要将过时的内核数据复制到磁盘上,特别是如果它可能会是非特权用户的 USB 记忆棒。)然后它会调用
copy_from_user
从用户空间缓冲区(包含“hello\n”)复制到该页面。我们在内核中,因此页面不需要映射到用户空间虚拟地址,并且 Linux 的内存映射(例如,对于 x86-64)将所有物理内存直接映射到一系列内核虚拟地址- 空间,所以virt_address = phys_address + page_offset_base
. (除了像 32 位 x86 这样的系统,它可能拥有比内核虚拟地址空间更多的物理内存,因此 highmem 恶作剧......Linus Torvalds 对 PAE 对于内核软件来说有多糟糕有一个很好的咆哮,一路解释了一些操作系统原理. 并非所有内核都希望始终保持所有内存映射,但 Linux 确实这样做,因为它简单且高效。)copy_from_user
将检查用户地址的有效性(例如,用户空间无法传递内核地址以使内核将任意数据复制到文件中!),并且它当前存在于硬件页表中,因此读取它不会导致 #PF 页面错误异常。实际复印将作为
rep movsb
一份memcpy
(除非copy_from_user
特殊情况小副本)。对于这个小的 6 字节副本,它可能不会尝试执行任何无 RFO 特殊操作,因此它本质上相当于mov rax, [rsi]
/mov [rdi], rax
如果它是 8 字节副本。内核将其存储到新分配的页面中并不是“特殊的”,这就像当您的用户空间代码将一段时间没有触及的内存存储到内存中时发生的情况(但不会触发页面)过错)。
假设该物理页最近没有被该核心写入,则该存储将至少在 L1d 和 L2 缓存中丢失,并且该核心将发送一个 RFO 以获取该行的副本以及所有权。收到回复后,存储缓冲区条目可以提交到 L1d 缓存。RFO由硬件完成;执行的代码只是
rep movsb
(或等效的mov [rdi], rax
)实际上,如果内核在分配该页时只是将该页清零,
rep stosb
则会使用无 RFO 存储协议来使任何其他可能已缓存该页行的内核中的任何高速缓存行无效,但是(与 NT 存储不同)数据在缓存中将是热的,已经处于 MESI 修改状态,因此存储者copy_from_user
将已经能够提交而无需进一步的核外通信。(AMD 有一条
clzero
指令,将整个缓存行清零为 NT 存储,这样不会污染缓存,但页面通常在清零后立即使用,就像在本例中一样。)