1. 请描述从用户态调用 read 函数读取文件数据,到获取数据,中间完整的内核处理过程是怎样的?
2. Linux 内核的内存管理机制是如何工作的?为什么要引入虚拟内存概念?
3. 你如何进行Linux内核的裁剪?具体步骤和考量是什么?
4. 简要说明Linux文件系统的底层原理和管理机制。
5. 编译器(Compiler)和连接器(Linker)在程序构建过程中分别起什么作用?
6. NUMA和非NUMA架构的区别?优势
7. 什么是缓存一致性协议,如何实现缓存一致性协议?
最著名、最基础的缓存一致性协议是MESI,也称为伊利诺伊协议。它通过为每个缓存行(Cache Line,缓存的基本单位)维护一个状态位,并通过核心之间的通信来维护状态。
MESI是四个状态的缩写:
M - 修改(Modified)
状态:该缓存行是“脏的”,即它的数据已被当前核心修改,与主内存中的数据不一致。这是该数据唯一的最新副本。
责任:当这个缓存行被替换时,必须将其写回主内存。
对应行:其他核心的缓存中没有该数据的副本。
E - 独占(Exclusive)
状态:该缓存行是“干净的”,其数据与主内存一致。但只有当前核心拥有这份副本。
特权:当前核心可以随时对其进行修改,而无需通知其他核心。一旦修改,状态会变为 M。
对应行:其他核心的缓存中没有该数据的副本。
S - 共享(Shared)
状态:该缓存行是“干净的”,数据与主内存一致。
限制:可能有多个核心的缓存中都存在该数据的副本。因此,当前核心不能直接修改它,必须先与其他核心沟通。
对应行:其他核心的缓存中可能有该数据的副本。
I - 无效(Invalid)
状态:该缓存行是无效的,不能使用。它要么是空的,要么里面的数据是过时的。
要求:如果核心要读取或写入一个处于 I 状态的缓存行,它必须先从其他核心或主内存中获取最新数据。
假设有两个核心,Core A 和 Core B。
初始:内存地址
X的值为 10。没有核心缓存它。Core A 读取 X:
Core A 缓存未命中,在总线上发送
Bus Read。无人响应,从内存读取值 10。
Core A 的缓存行状态变为 E(独占)。
Core B 读取 X:
Core B 缓存未命中,在总线上发送
Bus Read。Core A “嗅探”到这个请求,知道自己有副本。
Core A 将数据提供给 Core B,并将自己的状态从 E 变为 S。
Core B 接收到数据,状态设置为 S。
Core A 要写入 X(比如改为 20):
Core A 发现自己的状态是 S,不能直接写。
它在总线上发送
Bus Upgrade或Bus Read Exclusive消息。Core B “嗅探”到这个消息,知道自己副本要失效了,将自己的缓存行状态变为 I。
Core A 收到确认后,执行写入操作,并将自己的状态从 S 变为 M。此时,Core A 的缓存是最新值 20,Core B 的缓存已无效,内存中的数据(10)也已过时。
Core B 再次读取 X:
Core B 缓存行状态是 I,未命中。发送
Bus Read。Core A “嗅探”到请求,发现自己状态是 M(已修改)。
Core A 将数据(20)写回总线(或先写回内存再提供),并将自己的状态变为 S。
Core B 接收到最新数据 20,状态变为 S。
总线窥探(Snooping):上述机制依赖于所有核心监听总线上的所有消息,这在大规模多核系统中可能成为瓶颈。
目录协议(Directory Protocol):为了解决窥探的扩展性问题,更高级的系统使用“目录”。目录作为一个中心化的组件,记录每个缓存行被哪些核心缓存。当一个核心需要修改数据时,它只需查询目录,然后由目录直接通知那些持有副本的核心使其失效,而不是广播到所有核心。这在NUMA架构中非常常见。
存储缓冲区(Store Buffer):为了不让核心在写操作时总是等待其他核心的响应(例如,等待
Bus Upgrade的确认),CPU引入了存储缓冲区。核心可以先把写操作放入存储缓冲区,然后继续执行后续指令。这虽然提升了性能,但也引入了更复杂的内存一致性模型问题(如需要内存屏障指令)。