如何将结构显式加载到 L1d 缓存中? CR0.CD = 1 在隔离核心 with/without 超线程上使用 INVD 的奇怪结果
How to explicitly load a structure into L1d cache? Weird results with INVD with CR0.CD = 1 on isolated core with/without hyperthreading
我的目标是将静态结构加载到 L1D 缓存中。之后使用这些结构成员执行一些操作并完成操作 运行 invd
以丢弃所有修改的缓存行。所以基本上我想在缓存内创建一个安全的环境,这样,在缓存内执行操作时,数据不会泄漏到 RAM 中。
为此,我有一个内核模块。我在结构的成员上放置了一些固定值。然后我禁用抢占,禁用所有其他 CPU 的缓存(当前 CPU 除外),禁用中断,然后使用 __builtin_prefetch()
将我的静态结构加载到缓存中。之后,我用新值覆盖之前放置的固定值。之后,我执行 invd
(清除修改后的缓存行),然后为所有其他 CPU 启用缓存,启用中断并启用抢占。我的理由是,当我在原子模式下执行此操作时,INVD
将删除所有更改。从原子模式返回后,我应该会看到我之前放置的原始固定值。然而,这并没有发生。退出原子模式后,我可以看到用于覆盖先前放置的固定值的值。这是我的模块代码,
奇怪的是,重启电脑后,我的输出发生了变化,我只是不明白为什么。现在,我根本看不到任何变化。我发布了完整的代码,包括@Peter Cordes 建议的一些修复,
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("test INVD");
static struct CACHE_ENV{
unsigned char in[128];
unsigned char out[128];
}cacheEnv __attribute__((aligned(64)));
#define cacheEnvSize (sizeof(cacheEnv)/64)
//#define change "Hello"
unsigned char change[]="hello";
void disCache(void *p){
__asm__ __volatile__ (
"wbinvd\n"
"mov %%cr0, %%rax\n\t"
"or $(1<<30), %%eax\n\t"
"mov %%rax, %%cr0\n\t"
"wbinvd\n"
::
:"%rax"
);
printk(KERN_INFO "cpuid %d --> cache disable\n", smp_processor_id());
}
void enaCache(void *p){
__asm__ __volatile__ (
"mov %%cr0, %%rax\n\t"
"and $~(1<<30), %%eax\n\t"
"mov %%rax, %%cr0\n\t"
::
:"%rax"
);
printk(KERN_INFO "cpuid %d --> cache enable\n", smp_processor_id());
}
int changeFixedValue (struct CACHE_ENV *env){
int ret=1;
//memcpy(env->in, change, sizeof (change));
//memcpy(env->out, change,sizeof (change));
strcpy(env->in,change);
strcpy(env->out,change);
return ret;
}
void fillCache(unsigned char *p, int num){
int i;
//unsigned char *buf = p;
volatile unsigned char *buf=p;
for(i=0;i<num;++i){
/*
asm volatile(
"movq [=12=],(%0)\n"
:
:"r"(buf)
:
);
*/
//__builtin_prefetch(buf,1,1);
//__builtin_prefetch(buf,0,3);
*buf += 0;
buf += 64;
}
printk(KERN_INFO "Inside fillCache, num is %d\n", num);
}
static int __init device_init(void){
unsigned long flags;
int result;
struct CACHE_ENV env;
//setup Fixed values
char word[] ="0xabcd";
memcpy(env.in, word, sizeof(word) );
memcpy(env.out, word, sizeof (word));
printk(KERN_INFO "env.in fixed is %s\n", env.in);
printk(KERN_INFO "env.out fixed is %s\n", env.out);
printk(KERN_INFO "Current CPU %s\n", smp_processor_id());
// start atomic
preempt_disable();
smp_call_function(disCache,NULL,1);
local_irq_save(flags);
asm("lfence; mfence" ::: "memory");
fillCache(&env, cacheEnvSize);
result=changeFixedValue(&env);
//asm volatile("invd\n":::);
asm volatile("invd\n":::"memory");
// exit atomic
smp_call_function(enaCache,NULL,1);
local_irq_restore(flags);
preempt_enable();
printk(KERN_INFO "After: env.in is %s\n", env.in);
printk(KERN_INFO "After: env.out is %s\n", env.out);
return 0;
}
static void __exit device_cleanup(void){
printk(KERN_ALERT "Removing invd_driver.\n");
}
module_init(device_init);
module_exit(device_cleanup);
我得到以下输出:
[ 3306.345292] env.in fixed is 0xabcd
[ 3306.345321] env.out fixed is 0xabcd
[ 3306.345322] Current CPU (null)
[ 3306.346390] cpuid 1 --> cache disable
[ 3306.346611] cpuid 3 --> cache disable
[ 3306.346844] cpuid 2 --> cache disable
[ 3306.347065] cpuid 0 --> cache disable
[ 3306.347313] cpuid 4 --> cache disable
[ 3306.347522] cpuid 5 --> cache disable
[ 3306.347755] cpuid 6 --> cache disable
[ 3306.351235] Inside fillCache, num is 4
[ 3306.352250] cpuid 3 --> cache enable
[ 3306.352997] cpuid 5 --> cache enable
[ 3306.353197] cpuid 4 --> cache enable
[ 3306.353220] cpuid 6 --> cache enable
[ 3306.353221] cpuid 2 --> cache enable
[ 3306.353221] cpuid 1 --> cache enable
[ 3306.353541] cpuid 0 --> cache enable
[ 3306.353608] After: env.in is hello
[ 3306.353609] After: env.out is hello
我的Makefile
是
obj-m += invdMod.o
CFLAGS_invdMod.o := -o0
invdMod-objs := disable_cache.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
rm -f *.o
有没有想过我做错了什么?正如我之前所说,我希望我的输出保持不变。
我能想到的一个原因是 __builtin_prefetch()
没有将结构放入缓存。另一种将内容放入缓存的方法是在 MTRR
和 PAT
的帮助下设置一个 write-back
区域。但是,我对如何实现这一目标一无所知。我发现 12.6. Creating MTRRs from a C programme using ioctl()’s 展示了如何创建 MTRR
区域,但我不知道如何将我的结构地址与该区域绑定。
我的CPU是:Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
内核版本:Linux xxx 4.4.0-200-generic #232-Ubuntu SMP Wed Jan 13 10:18:39 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
GCC 版本:gcc (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609
我用 -O0
参数编译了这个模块
更新 2:关闭超线程
我用 echo off > /sys/devices/system/cpu/smt/control
关闭了超线程。在那之后,运行宁我的模块似乎,changeFixedValue()
& fillCache()
没有被调用。
输出:
[ 3971.480133] env.in fixed is 0xabcd
[ 3971.480134] env.out fixed is 0xabcd
[ 3971.480135] Current CPU 3
[ 3971.480739] cpuid 2 --> cache disable
[ 3971.480956] cpuid 1 --> cache disable
[ 3971.481175] cpuid 0 --> cache disable
[ 3971.482771] cpuid 2 --> cache enable
[ 3971.482774] cpuid 0 --> cache enable
[ 3971.483043] cpuid 1 --> cache enable
[ 3971.483065] After: env.in is 0xabcd
[ 3971.483066] After: env.out is 0xabcd
在fillCache底部调用printk
看起来很不安全。您将要 运行 一些存储,然后是 invd
,因此 printk
对内核数据结构(如日志缓冲区)所做的任何修改都可能被写回 DRAM 或者可能会如果它们在缓存中仍然脏,则无效。如果一些但不是所有存储都进入 DRAM(因为缓存容量有限),您可能会使内核数据结构处于不一致状态。
我猜你当前禁用 HT 的测试显示一切都比你希望的更好,包括丢弃 printk
完成的存储,以及丢弃changeFixedValue
完成的商店。这可以解释缺少留给 user-space 在您的代码完成后阅读的日志消息。
要对此进行测试,您最好 clflush
printk 所做的一切,但没有简单的方法可以做到这一点。也许 wbinvd
然后 changeFixedValue
然后 invd
。 (你没有在这个核心上进入无填充模式,所以 fillCache
不是你的商店/invd 想法工作所必需的,见下文。)
启用超线程:
CR0.CD 是每个物理核心,因此让您的 HT 兄弟核心禁用缓存也意味着隔离核心的 CD=1。 因此,在启用 HT 的情况下,您 即使在隔离核心上也处于无填充模式。
关闭超线程后,隔离核心依然正常。
编译时和运行时重新排序
asm volatile("invd\n":::);
没有 "memory"
破坏告诉编译器它允许重新排序 wrt。内存操作。显然这不是你的问题,但这是一个你应该修复的错误。
将 asm("mfence; lfence" ::: "memory");
放在 fillCache
之前可能也是一个好主意,以确保任何缓存未命中的加载和存储不会仍在运行中,并且可能会在您的代码分配新的缓存行运行宁。或者甚至可能是像 asm("xor %eax,%eax; cpuid" ::: "eax", "ebx", "ecx", "edx", "memory");
这样的完全序列化指令,但我不知道 CPUID 会阻止哪些 mfence; lfence 不会。
题目问题:touching memory to bring it into cache
PREFETCHT0(进入 L1d 缓存)是 __builtin_prefetch(p,0,3);
。 This answer 显示 args 如何映射到指令;您正在使用 prefetchw
(写入意图)或者我认为 prefetcht1
(L2 缓存)取决于编译器选项。
但是真的因为你需要这个来确保正确性,所以你不应该使用可选的提示来提示 HW 在繁忙时会掉线。 mfence; lfence
会使它不太可能让 HW 实际上很忙,但仍然是个不错的主意。
使用 volatile
类似 READ_ONCE
的读法让 GCC 发出加载指令。或者使用 volatile char *buf
和 *buf |= 0;
或其他东西来真正 RMW 而不是预取,以确保该行是独占的,而不必让 GCC 发出 prefetchw
.
也许值得 运行宁 fillCache 几次,只是为了更加确保每一行都正确地处于您想要的状态。但是由于您的 env 小于 4k,每一行都将位于 L1d 缓存中的不同集合中,因此在分配另一行时不存在一行被丢弃的风险(除非是 L3 缓存的哈希函数中的别名?但即便如此, 伪 LRU 驱逐应该可靠地保持最近的行。)
按 128 对齐您的数据,一对对齐的缓存行
static struct CACHE_ENV { ... } cacheEnv;
不保证缓存行大小对齐;你缺少 C11 _Alignas(64)
或 GNU C __attribute__((aligned(64)))
。所以它可能跨越 sizeof(T)/64
行以上。或者为了更好的衡量,为 L2 相邻线预取器对齐 128。 (在这里你可以而且应该简单地对齐你的缓冲区,但是 展示了如何遍历任意大小的可能未对齐结构的每个缓存行。)
这并不能解释您的问题,因为唯一可能遗漏的部分是 env.out
的最后最多 48 个字节。 (我认为全局结构在默认 ABI 规则下将按 16 位对齐。)而且您只打印每个数组的前几个字节。
更简单的方法:memset(0) 以避免将数据泄漏回 DRAM
顺便说一句,在完成后通过内存集用 0
覆盖您的缓冲区也应该防止您的数据像 INVD 一样可靠地写回 DRAM,但速度更快。 (也许通过 asm 手动 rep stosb
以确保它不能作为死存储进行优化)。
无填充模式在这里也可能有用,可以阻止缓存未命中逐出现有行。 AFAIK,这基本上锁定了缓存,因此不会发生新的分配,因此不会发生驱逐。 (但是您可能无法读取或写入 other 正常内存,尽管您可以在寄存器中留下结果。)
无填充模式(对于当前核心)在重新启用分配之前使用 memset 清除缓冲区绝对安全;在导致驱逐的过程中没有缓存未命中的风险。尽管如果你的 fillCache 实际上工作正常并且在你开始你的工作之前让你的所有行进入 MESI 修改状态,你的加载和存储将命中 L1d 缓存而不会有驱逐任何缓冲行的风险。
如果您担心 DRAM 内容(而不是总线信号),那么 memset 之后的每一行 clflushopt 将减少window 漏洞。 (如果 0
对你不起作用,或者从原始副本的干净副本中获取 memcpy,但希望你可以在私人副本中工作,并且不修改原件。你当前的流浪回写总是可能的方法,所以我不想依赖它来始终保持大缓冲区不变。)
不要将 NT 存储用于手动 memset 或 memcpy:这可能会在 NT 存储之前刷新“秘密”脏数据。一种选择是使用普通存储或 rep stosb
进行 memset(0),然后使用 NT 存储再次循环。或者也许每行做 8x movq 正常存储,然后 8x movnti,所以你在继续之前对同一行背对背地做这两件事。
为什么要填充缓存?
如果您不使用无填充模式,那么在您写入行之前是否缓存这些行甚至都不重要。当 invd
运行 时,您只需要在缓存中写入脏数据,即使它们是从缓存中丢失的存储中获取的,这也应该是正确的。
您在 fillCache
和 changeFixedValue
之间已经没有像 mfence 这样的障碍,这很好,但这意味着当您弄脏缓存时,启动缓存的任何缓存未命中仍在运行中。
INVD 本身 is serializing,因此它应该在丢弃缓存内容之前等待存储离开存储缓冲区。 (所以在你的工作之后,在 INVD 之前放置 mfence;lfence
应该没有任何区别。)换句话说,INVD 应该丢弃仍在存储缓冲区中的可缓存存储,以及脏缓存行,除非提交一些那些商店碰巧驱逐了任何东西。
我的目标是将静态结构加载到 L1D 缓存中。之后使用这些结构成员执行一些操作并完成操作 运行 invd
以丢弃所有修改的缓存行。所以基本上我想在缓存内创建一个安全的环境,这样,在缓存内执行操作时,数据不会泄漏到 RAM 中。
为此,我有一个内核模块。我在结构的成员上放置了一些固定值。然后我禁用抢占,禁用所有其他 CPU 的缓存(当前 CPU 除外),禁用中断,然后使用 __builtin_prefetch()
将我的静态结构加载到缓存中。之后,我用新值覆盖之前放置的固定值。之后,我执行 invd
(清除修改后的缓存行),然后为所有其他 CPU 启用缓存,启用中断并启用抢占。我的理由是,当我在原子模式下执行此操作时,INVD
将删除所有更改。从原子模式返回后,我应该会看到我之前放置的原始固定值。然而,这并没有发生。退出原子模式后,我可以看到用于覆盖先前放置的固定值的值。这是我的模块代码,
奇怪的是,重启电脑后,我的输出发生了变化,我只是不明白为什么。现在,我根本看不到任何变化。我发布了完整的代码,包括@Peter Cordes 建议的一些修复,
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author");
MODULE_DESCRIPTION("test INVD");
static struct CACHE_ENV{
unsigned char in[128];
unsigned char out[128];
}cacheEnv __attribute__((aligned(64)));
#define cacheEnvSize (sizeof(cacheEnv)/64)
//#define change "Hello"
unsigned char change[]="hello";
void disCache(void *p){
__asm__ __volatile__ (
"wbinvd\n"
"mov %%cr0, %%rax\n\t"
"or $(1<<30), %%eax\n\t"
"mov %%rax, %%cr0\n\t"
"wbinvd\n"
::
:"%rax"
);
printk(KERN_INFO "cpuid %d --> cache disable\n", smp_processor_id());
}
void enaCache(void *p){
__asm__ __volatile__ (
"mov %%cr0, %%rax\n\t"
"and $~(1<<30), %%eax\n\t"
"mov %%rax, %%cr0\n\t"
::
:"%rax"
);
printk(KERN_INFO "cpuid %d --> cache enable\n", smp_processor_id());
}
int changeFixedValue (struct CACHE_ENV *env){
int ret=1;
//memcpy(env->in, change, sizeof (change));
//memcpy(env->out, change,sizeof (change));
strcpy(env->in,change);
strcpy(env->out,change);
return ret;
}
void fillCache(unsigned char *p, int num){
int i;
//unsigned char *buf = p;
volatile unsigned char *buf=p;
for(i=0;i<num;++i){
/*
asm volatile(
"movq [=12=],(%0)\n"
:
:"r"(buf)
:
);
*/
//__builtin_prefetch(buf,1,1);
//__builtin_prefetch(buf,0,3);
*buf += 0;
buf += 64;
}
printk(KERN_INFO "Inside fillCache, num is %d\n", num);
}
static int __init device_init(void){
unsigned long flags;
int result;
struct CACHE_ENV env;
//setup Fixed values
char word[] ="0xabcd";
memcpy(env.in, word, sizeof(word) );
memcpy(env.out, word, sizeof (word));
printk(KERN_INFO "env.in fixed is %s\n", env.in);
printk(KERN_INFO "env.out fixed is %s\n", env.out);
printk(KERN_INFO "Current CPU %s\n", smp_processor_id());
// start atomic
preempt_disable();
smp_call_function(disCache,NULL,1);
local_irq_save(flags);
asm("lfence; mfence" ::: "memory");
fillCache(&env, cacheEnvSize);
result=changeFixedValue(&env);
//asm volatile("invd\n":::);
asm volatile("invd\n":::"memory");
// exit atomic
smp_call_function(enaCache,NULL,1);
local_irq_restore(flags);
preempt_enable();
printk(KERN_INFO "After: env.in is %s\n", env.in);
printk(KERN_INFO "After: env.out is %s\n", env.out);
return 0;
}
static void __exit device_cleanup(void){
printk(KERN_ALERT "Removing invd_driver.\n");
}
module_init(device_init);
module_exit(device_cleanup);
我得到以下输出:
[ 3306.345292] env.in fixed is 0xabcd
[ 3306.345321] env.out fixed is 0xabcd
[ 3306.345322] Current CPU (null)
[ 3306.346390] cpuid 1 --> cache disable
[ 3306.346611] cpuid 3 --> cache disable
[ 3306.346844] cpuid 2 --> cache disable
[ 3306.347065] cpuid 0 --> cache disable
[ 3306.347313] cpuid 4 --> cache disable
[ 3306.347522] cpuid 5 --> cache disable
[ 3306.347755] cpuid 6 --> cache disable
[ 3306.351235] Inside fillCache, num is 4
[ 3306.352250] cpuid 3 --> cache enable
[ 3306.352997] cpuid 5 --> cache enable
[ 3306.353197] cpuid 4 --> cache enable
[ 3306.353220] cpuid 6 --> cache enable
[ 3306.353221] cpuid 2 --> cache enable
[ 3306.353221] cpuid 1 --> cache enable
[ 3306.353541] cpuid 0 --> cache enable
[ 3306.353608] After: env.in is hello
[ 3306.353609] After: env.out is hello
我的Makefile
是
obj-m += invdMod.o
CFLAGS_invdMod.o := -o0
invdMod-objs := disable_cache.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
rm -f *.o
有没有想过我做错了什么?正如我之前所说,我希望我的输出保持不变。
我能想到的一个原因是 __builtin_prefetch()
没有将结构放入缓存。另一种将内容放入缓存的方法是在 MTRR
和 PAT
的帮助下设置一个 write-back
区域。但是,我对如何实现这一目标一无所知。我发现 12.6. Creating MTRRs from a C programme using ioctl()’s 展示了如何创建 MTRR
区域,但我不知道如何将我的结构地址与该区域绑定。
我的CPU是:Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
内核版本:Linux xxx 4.4.0-200-generic #232-Ubuntu SMP Wed Jan 13 10:18:39 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
GCC 版本:gcc (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609
我用 -O0
参数编译了这个模块
更新 2:关闭超线程
我用 echo off > /sys/devices/system/cpu/smt/control
关闭了超线程。在那之后,运行宁我的模块似乎,changeFixedValue()
& fillCache()
没有被调用。
输出:
[ 3971.480133] env.in fixed is 0xabcd
[ 3971.480134] env.out fixed is 0xabcd
[ 3971.480135] Current CPU 3
[ 3971.480739] cpuid 2 --> cache disable
[ 3971.480956] cpuid 1 --> cache disable
[ 3971.481175] cpuid 0 --> cache disable
[ 3971.482771] cpuid 2 --> cache enable
[ 3971.482774] cpuid 0 --> cache enable
[ 3971.483043] cpuid 1 --> cache enable
[ 3971.483065] After: env.in is 0xabcd
[ 3971.483066] After: env.out is 0xabcd
在fillCache底部调用printk
看起来很不安全。您将要 运行 一些存储,然后是 invd
,因此 printk
对内核数据结构(如日志缓冲区)所做的任何修改都可能被写回 DRAM 或者可能会如果它们在缓存中仍然脏,则无效。如果一些但不是所有存储都进入 DRAM(因为缓存容量有限),您可能会使内核数据结构处于不一致状态。
我猜你当前禁用 HT 的测试显示一切都比你希望的更好,包括丢弃 printk
完成的存储,以及丢弃changeFixedValue
完成的商店。这可以解释缺少留给 user-space 在您的代码完成后阅读的日志消息。
要对此进行测试,您最好 clflush
printk 所做的一切,但没有简单的方法可以做到这一点。也许 wbinvd
然后 changeFixedValue
然后 invd
。 (你没有在这个核心上进入无填充模式,所以 fillCache
不是你的商店/invd 想法工作所必需的,见下文。)
启用超线程:
CR0.CD 是每个物理核心,因此让您的 HT 兄弟核心禁用缓存也意味着隔离核心的 CD=1。 因此,在启用 HT 的情况下,您 即使在隔离核心上也处于无填充模式。
关闭超线程后,隔离核心依然正常。
编译时和运行时重新排序
asm volatile("invd\n":::);
没有 "memory"
破坏告诉编译器它允许重新排序 wrt。内存操作。显然这不是你的问题,但这是一个你应该修复的错误。
将 asm("mfence; lfence" ::: "memory");
放在 fillCache
之前可能也是一个好主意,以确保任何缓存未命中的加载和存储不会仍在运行中,并且可能会在您的代码分配新的缓存行运行宁。或者甚至可能是像 asm("xor %eax,%eax; cpuid" ::: "eax", "ebx", "ecx", "edx", "memory");
这样的完全序列化指令,但我不知道 CPUID 会阻止哪些 mfence; lfence 不会。
题目问题:touching memory to bring it into cache
PREFETCHT0(进入 L1d 缓存)是 __builtin_prefetch(p,0,3);
。 This answer 显示 args 如何映射到指令;您正在使用 prefetchw
(写入意图)或者我认为 prefetcht1
(L2 缓存)取决于编译器选项。
但是真的因为你需要这个来确保正确性,所以你不应该使用可选的提示来提示 HW 在繁忙时会掉线。 mfence; lfence
会使它不太可能让 HW 实际上很忙,但仍然是个不错的主意。
使用 volatile
类似 READ_ONCE
的读法让 GCC 发出加载指令。或者使用 volatile char *buf
和 *buf |= 0;
或其他东西来真正 RMW 而不是预取,以确保该行是独占的,而不必让 GCC 发出 prefetchw
.
也许值得 运行宁 fillCache 几次,只是为了更加确保每一行都正确地处于您想要的状态。但是由于您的 env 小于 4k,每一行都将位于 L1d 缓存中的不同集合中,因此在分配另一行时不存在一行被丢弃的风险(除非是 L3 缓存的哈希函数中的别名?但即便如此, 伪 LRU 驱逐应该可靠地保持最近的行。)
按 128 对齐您的数据,一对对齐的缓存行
static struct CACHE_ENV { ... } cacheEnv;
不保证缓存行大小对齐;你缺少 C11 _Alignas(64)
或 GNU C __attribute__((aligned(64)))
。所以它可能跨越 sizeof(T)/64
行以上。或者为了更好的衡量,为 L2 相邻线预取器对齐 128。 (在这里你可以而且应该简单地对齐你的缓冲区,但是
这并不能解释您的问题,因为唯一可能遗漏的部分是 env.out
的最后最多 48 个字节。 (我认为全局结构在默认 ABI 规则下将按 16 位对齐。)而且您只打印每个数组的前几个字节。
更简单的方法:memset(0) 以避免将数据泄漏回 DRAM
顺便说一句,在完成后通过内存集用 0
覆盖您的缓冲区也应该防止您的数据像 INVD 一样可靠地写回 DRAM,但速度更快。 (也许通过 asm 手动 rep stosb
以确保它不能作为死存储进行优化)。
无填充模式在这里也可能有用,可以阻止缓存未命中逐出现有行。 AFAIK,这基本上锁定了缓存,因此不会发生新的分配,因此不会发生驱逐。 (但是您可能无法读取或写入 other 正常内存,尽管您可以在寄存器中留下结果。)
无填充模式(对于当前核心)在重新启用分配之前使用 memset 清除缓冲区绝对安全;在导致驱逐的过程中没有缓存未命中的风险。尽管如果你的 fillCache 实际上工作正常并且在你开始你的工作之前让你的所有行进入 MESI 修改状态,你的加载和存储将命中 L1d 缓存而不会有驱逐任何缓冲行的风险。
如果您担心 DRAM 内容(而不是总线信号),那么 memset 之后的每一行 clflushopt 将减少window 漏洞。 (如果 0
对你不起作用,或者从原始副本的干净副本中获取 memcpy,但希望你可以在私人副本中工作,并且不修改原件。你当前的流浪回写总是可能的方法,所以我不想依赖它来始终保持大缓冲区不变。)
不要将 NT 存储用于手动 memset 或 memcpy:这可能会在 NT 存储之前刷新“秘密”脏数据。一种选择是使用普通存储或 rep stosb
进行 memset(0),然后使用 NT 存储再次循环。或者也许每行做 8x movq 正常存储,然后 8x movnti,所以你在继续之前对同一行背对背地做这两件事。
为什么要填充缓存?
如果您不使用无填充模式,那么在您写入行之前是否缓存这些行甚至都不重要。当 invd
运行 时,您只需要在缓存中写入脏数据,即使它们是从缓存中丢失的存储中获取的,这也应该是正确的。
您在 fillCache
和 changeFixedValue
之间已经没有像 mfence 这样的障碍,这很好,但这意味着当您弄脏缓存时,启动缓存的任何缓存未命中仍在运行中。
INVD 本身 is serializing,因此它应该在丢弃缓存内容之前等待存储离开存储缓冲区。 (所以在你的工作之后,在 INVD 之前放置 mfence;lfence
应该没有任何区别。)换句话说,INVD 应该丢弃仍在存储缓冲区中的可缓存存储,以及脏缓存行,除非提交一些那些商店碰巧驱逐了任何东西。