You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在gem5中正确模拟两个进程的共享内存场景?

在gem5中模拟进程共享内存缓存命中的问题排查与解决

我需要在gem5中模拟两个进程共享内存的场景,真实机器上通过mmap映射同一文件可以实现,但在gem5中测试时,第二个进程访问同一内存地址的延迟没有下降,说明缓存没有共享。具体表现为:第一个进程访问内存后第二次延迟从291降到22,但第二个进程访问同样地址的延迟还是246,未命中缓存。


相关代码

program1(sender)

#define _GNU_SOURCE
#include<stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sched.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include "../util.h"
#define arr_len (64*8*8*8)
volatile  int test;
int main(){
    uint64_t *addr = (uint64_t *) mmap_file(0,"shared");
    int tt = time_mread(addr);
    mfence();
    int tt2 = time_mread(addr);
    printf("%p\n", addr);
    printf("time %d %d\n", tt,tt2);

    return 0;
}

program2(receiver)

#define _GNU_SOURCE
#include<stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sched.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include "../util.h"

int main(){
    uint64_t *addr = (uint64_t *) mmap_file(0,"shared");
    uint64_t *synchronization = (uint64_t *)  mmap_file(64,"shared");
    delayloop(5000);
    int tt = time_mread(addr);
    printf("%p\n", addr);
    printf("time %d\n", tt);
    return 0;
}

util.h

#ifndef UTIL_H_
#define UTIL_H_

#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <math.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <x86intrin.h>

#define PAGE_SHIFT 12
#define PAGEMAP_LENGTH 8
#define PAGE 4096
#define FILE_SIZE   4096


uint64_t rdtscp64() {
  uint32_t low, high;
  asm volatile ("rdtscp": "=a" (low), "=d" (high) :: "ecx");
  return (((uint64_t)high) << 32) | low;
}


uint64_t mmap_file(int offset, char *fileName)
{
    uint64_t addr;
    // Map file to virtual memory and extract the address at the file offset
    if (fileName != NULL) {
        int inFile = open(fileName, O_RDONLY);
        if(inFile == -1) {
            printf("Failed to Open File\n");
            exit(1);
        }

        void *mapaddr = mmap(NULL,FILE_SIZE,PROT_READ,MAP_SHARED,inFile,0);
        if (mapaddr == (void*) -1 ) {
            printf("Failed to Map Address\n");
            exit(1);
        }
        addr = (uint64_t) mapaddr + offset;
    }
    return addr;
}

int 
// Attribution: https://cs.adelaide.edu.au/~yval/Mastik/
time_mread(void *adrs) 
{
  volatile unsigned long time;

  asm volatile (
    // "lfence\n"
    "mfence\n"
    "rdtscp\n"
    "lfence\n"
    "mov %%eax, %%esi\n"
    "mov (%1), %%eax\n"
    "rdtscp\n"
    "sub %%esi, %%eax\n"
    : "=&a" (time)          // output
    : "r" (adrs)            // input
    : "ecx", "edx", "esi"); // clobber registers

  return (int) time;
}

 void mfence() {
  asm volatile("mfence");
}

 void delayloop(uint32_t cycles) {
  uint64_t start = rdtscp64();
  while ((rdtscp64()-start) < cycles)
    ;
}

#endif

运行输出

0x7ffff7e03000
time 291 22
0x7ffff7e03000
time 246

gem5运行命令

/usr/local/src/gem5/build/X86/gem5.opt --debug-flags=CacheAll,ExecAll,CacheBlock,LSQ --debug-file=/usr/local/src/gem5/runScripts/attack_spectre/trace.out --debug-start=168800000 /usr/local/src/gem5/configs/example/se.py --num-cpus=2 --l1d_replacement=LRU --mem-size=4GB --l1d_size=32kB --l1d_assoc=8 --l1i_size=32kB --l1i_assoc=4 --l2_assoc=16 --l2_size=2MB --caches --l2cache --cpu-type=DerivO3CPU -c '/usr/local/src/gem5/programs/spectre/sender;/usr/local/src/gem5/programs/spectre/receiver'

问题原因与解决方法

核心原因

gem5的SE(Syscall Emulation)模式下,默认每个进程有独立的地址空间,且文件映射的物理页不会在进程间共享——即使两个进程mmap同一个文件,gem5会为每个进程分配独立的物理页副本,导致缓存无法共享。另外,SE模式下进程的执行是串行的(即使指定了多CPU),第一个进程退出后其缓存状态会被清空,第二个进程启动时缓存是冷的。

解决步骤

  1. 切换到FS(Full System)模式:FS模式模拟完整的操作系统,进程间共享物理内存和缓存的行为和真实机器一致。需要准备对应的磁盘镜像和内核。
  2. 调整进程调度与同步:如果坚持用SE模式,需要修改配置让两个进程同时运行在不同CPU上,并且确保第一个进程的缓存状态不会被清空。具体修改:
    • 修改se.py配置,让两个进程绑定到不同CPU,且同时启动而非串行执行。
    • 使用gem5的fork系统调用模拟父子进程,而非独立的两个进程——父子进程在SE模式下会共享地址空间和缓存。
  3. 验证物理页映射:在FS模式下,确保两个进程mmap的虚拟地址映射到同一物理页。可以通过读取/proc/self/pagemap验证物理页是否一致。
  4. 修正同步逻辑:当前的delayloop(5000)在gem5中可能不足以等待第一个进程完成内存访问,改用基于共享内存的同步(比如第一个进程写入共享内存标记,第二个进程轮询等待标记),确保第一个进程已经将数据加载到缓存后,第二个进程再发起访问。

具体修改示例(SE模式下父子进程方案)

修改program1,通过fork创建子进程(集成原program2逻辑),这样父子进程共享地址空间和缓存:

// 修改后的program1
#define _GNU_SOURCE
#include<stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sched.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include "../util.h"

volatile int test;

int main(){
    uint64_t *addr = (uint64_t *) mmap_file(0,"shared");
    uint64_t *sync_flag = (uint64_t *) mmap_file(64,"shared");
    
    // 创建子进程
    pid_t pid = fork();
    if(pid == 0){
        // 子进程等待父进程完成第一次访问
        while(*sync_flag == 0);
        int tt = time_mread(addr);
        printf("Child: %p\n", addr);
        printf("Child time %d\n", tt);
        return 0;
    } else {
        // 父进程先访问内存
        int tt = time_mread(addr);
        mfence();
        int tt2 = time_mread(addr);
        printf("Parent: %p\n", addr);
        printf("Parent time %d %d\n", tt,tt2);
        
        // 设置同步标记,通知子进程可以访问
        *sync_flag = 1;
        wait(NULL);
        return 0;
    }
}

运行时只需要指定这一个程序作为gem5的-c参数,SE模式下父子进程会共享物理页和缓存,子进程访问时会命中父进程加载的缓存。

调试建议

  • 查看gem5的debug trace(CacheAll标记),确认两个进程访问的物理地址是否一致。如果物理地址不同,说明页没有共享。
  • 在FS模式下,使用自定义的rdtscp工具测量缓存命中情况,和真实机器结果对比。

内容的提问来源于stack exchange,提问作者Gerrie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 16:45:32