【发布时间】:2022-07-23 04:17:13
【问题描述】:
我试图在 C 中实现一个基于缓存的隐蔽通道,但发现一些奇怪的东西。发送方和接收方之间的物理地址通过使用映射到具有 MAP_SHARED 选项的同一文件的 mmap() 调用来共享。下面是发送方进程的代码,它从缓存中刷新地址以传输 1,并将地址加载到缓存中以传输 0。它还测量了两种情况下的加载延迟:
// computes latency of a load operation
static inline CYCLES load_latency(volatile void* p) {
CYCLES t1 = rdtscp();
load = *((int *)p);
CYCLES t2 = rdtscp();
return (t2-t1);
}
void send_bit(int one, void *addr) {
if(one) {
clflush((void *)addr);
load__latency = load_latency((void *)addr);
printf("load latency = %d.\n", load__latency);
clflush((void *)addr);
}
else {
x = *((int *)addr);
load__latency = load_latency((void *)addr);
printf("load latency = %d.\n", load__latency);
}
}
int main(int argc, char **argv) {
if(argc == 2)
{
bit = atoi(argv[1]);
}
// transmit bit
init_address(DEFAULT_FILE_NAME);
send_bit(bit, address);
return 0;
}
load 操作在由同一进程发出时大约需要 0 - 1000 个周期(在缓存命中和缓存未命中期间)。
接收程序加载相同的共享物理地址并测量缓存命中或缓存未命中期间的延迟,其代码如下所示:
int main(int argc, char **argv) {
init_address(DEFAULT_FILE_NAME);
rdtscp();
load__latency = load_latency((void *)address);
printf("load latency = %d\n", load__latency);
return 0;
}
(我在发送者进程终止后手动运行接收者)
但是,与第一种情况相比,在这种情况下观察到的延迟有很大不同。 load 操作大约需要 5000-1000 个周期。
通过使用taskset 命令,这两个进程都已固定到同一个core-id。因此,如果我没记错的话,在缓存命中期间,两个进程都会在缓存命中时经历 L1 缓存的加载延迟,而在缓存未命中时会遇到 DRAM 的加载延迟。然而,这两个过程经历了非常不同的延迟。这个观察结果可能是什么原因,我怎样才能让两个进程经历相同的延迟?
【问题讨论】:
-
你在什么硬件上测试过这个?鉴于
rdtscp,显然有些x86的味道。还有,你为什么要在load = *((int *)p);中抛弃volatile?为什么将加载结果分配给定时区域内的全局(?)变量?那可能是软页面错误。事实上,假设您没有使用 MAP_POPULATE 或mlock,那么对共享数据的初始访问也是如此。这看起来不像minimal reproducible example,但是如果您只对每个 mmap 或每次运行整个程序进行一次测量,那么可能会发生页面错误。 -
另请注意,除非您立即运行第二个进程(例如,从相同的 shell 命令),否则操作系统将有机会将该内核置于深度睡眠状态。至少在 Intel CPU 上,这会清空 L1d 和 L2,因此它可以在更深的 C 状态下关闭它们。可能也是 TLB。
-
非常感谢!该程序确实在测量页面错误的延迟。我使用了 MAP_POPULATE,它现在似乎可以工作了!
标签: caching process operating-system cpu-architecture micro-architecture