4. 目录协议基础实现
任务目标
实现一个基于目录的缓存一致性协议核心逻辑,使用位向量记录每个缓存块在各核心中的共享状态,处理读写请求并维护一致性状态机。本案例聚焦目录协议中最核心的状态跟踪与请求转发逻辑,不涉及总线仲裁、存储层次或具体缓存替换策略。
核心设计
目录表以缓存块地址为索引,每个条目包含:
- 状态:UNCACHED(无缓存)、SHARED(共享)、EXCLUSIVE(独占/已修改)
- 共享者位向量:每bit对应一个核心,置1表示该核心持有此块副本
- 拥有者:当状态为EXCLUSIVE时,记录当前持有写权限的核心ID
完整实现
#include <cstdint>
#include <unordered_map>
#include <cassert>
#include <cstdio>
constexpr int NUM_CORES = 4;
constexpr uint32_t INVALID_OWNER = 0xFFFFFFFF;
enum class DirState { UNCACHED, SHARED, EXCLUSIVE };
struct DirEntry {
DirState state = DirState::UNCACHED;
uint32_t sharers = 0; // 位向量,bit i 表示 core i 持有副本
uint32_t owner = INVALID_OWNER; // EXCLUSIVE 时的写权限持有者
};
class DirectoryProtocol {
public:
// 核心发起读请求,返回是否命中(无需总线传输)
bool handleRead(uint32_t core_id, uint32_t addr) {
auto &e = dir[addr];
if (e.state == DirState::UNCACHED) {
// 从内存取数,标记共享
e.state = DirState::SHARED;
e.sharers = (1u << core_id);
return false; // miss,需从内存加载
}
if (e.state == DirState::EXCLUSIVE) {
// 若请求者不是拥有者,需要先写回内存再降级为共享
if (e.owner != core_id) {
// 通知原拥有者写回(此处仅状态更新)
e.state = DirState::SHARED;
e.sharers = (1u << core_id) | (1u << e.owner);
e.owner = INVALID_OWNER;
return false; // 需要从拥有者处获取数据
}
return true; // 拥有者自身读命中
}
// SHARED 状态
e.sharers |= (1u << core_id);
return true; // 共享读命中
}
// 核心发起写请求,返回是否需要失效其他副本
bool handleWrite(uint32_t core_id, uint32_t addr) {
auto &e = dir[addr];
if (e.state == DirState::UNCACHED) {
e.state = DirState::EXCLUSIVE;
e.owner = core_id;
e.sharers = (1u << core_id);
return false; // 无其他副本需失效
}
if (e.state == DirState::SHARED) {
uint32_t others = e.sharers & ~(1u << core_id);
if (others != 0) {
// 需要向其他共享者发送失效消息
e.sharers = (1u << core_id);
e.state = DirState::EXCLUSIVE;
e.owner = core_id;
return true; // 有失效需要广播
}
// 无其他共享者,直接升级
e.state = DirState::EXCLUSIVE;
e.owner = core_id;
return false;
}
// EXCLUSIVE 状态
if (e.owner == core_id) {
return false; // 自身已持有写权限
}
// 其他核心独占,需先写回再转移所有权
e.owner = core_id;
e.sharers = (1u << core_id);
return true; // 需要通知原拥有者写回
}
// 处理核心的失效确认(收到失效消息后回复)
void handleAck(uint32_t core_id, uint32_t addr) {
auto &e = dir[addr];
e.sharers &= ~(1u << core_id);
if (e.sharers == 0 && e.state == DirState::SHARED) {
e.state = DirState::UNCACHED;
}
}
// 打印目录状态(调试用)
void dump(uint32_t addr) const {
auto it = dir.find(addr);
if (it == dir.end()) {
printf("addr 0x%x: UNCACHED\n", addr);
return;
}
const auto &e = it->second;
const char *st = e.state == DirState::UNCACHED ? "UNCACHED" :
e.state == DirState::SHARED ? "SHARED" : "EXCLUSIVE";
printf("addr 0x%x: state=%s sharers=0x%x owner=%u\n",
addr, st, e.sharers, e.owner);
}
private:
std::unordered_map<uint32_t, DirEntry> dir;
};
// 最小验证入口
int main() {
DirectoryProtocol proto;
const uint32_t ADDR = 0x1000;
// 场景1:core0 读 miss,core1 读共享
assert(proto.handleRead(0, ADDR) == false);
assert(proto.handleRead(1, ADDR) == true);
proto.dump(ADDR); // 预期: SHARED sharers=0x3
// 场景2:core2 写,需要失效 core0/core1
assert(proto.handleWrite(2, ADDR) == true);
proto.dump(ADDR); // 预期: EXCLUSIVE owner=2 sharers=0x4
// 场景3:core0 读,需要从 core2 获取数据并降级为共享
assert(proto.handleRead(0, ADDR) == false);
proto.dump(ADDR); // 预期: SHARED sharers=0x5
// 场景4:core2 写回确认(模拟收到失效响应)
proto.handleAck(2, ADDR);
proto.dump(ADDR); // 预期: SHARED sharers=0x1 (仅core0)
printf("All directory protocol tests passed.\n");
return 0;
}
运行与验证
- 将代码保存为
directory_protocol.cpp - 编译:
g++ -std=c++11 -o dir_proto directory_protocol.cpp - 运行:
./dir_proto
通过条件:程序输出4行目录状态(分别对应注释中的预期状态),最后打印 All directory protocol tests passed. 且无断言失败。四个场景覆盖了:读miss→共享、共享→独占(需失效)、独占→共享(需写回)、失效确认后的状态清理。
关键说明
- 位向量
sharers的bit i对应core i,0x3表示core0和core1持有副本 - 返回
true表示需要总线事务(失效广播或写回请求),false表示本地命中或仅需内存访问 - 实际系统中失效消息的发送与确认是异步的,本实现用
handleAck模拟确认到达后的状态更新 - 目录条目按需创建,
UNCACHED状态不显式存储,通过unordered_map缺省行为隐式表示