You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

RISC-V QEMU与主机共享内存配置及ivshmem段错误排查

RISC-V QEMU ivshmem共享内存段错误问题排查与解决

问题背景

首次使用QEMU为RISC-V虚拟机配置主机共享内存(用于后续FPGA对接),最初直接使用memory-backend-file映射/dev/shm无法实现双向读写,但virtfs挂载目录可正常工作。改用ivshmem设备后,QEMU启动命令更新为:

qemu-system-riscv64 -M 'virt' \
-cpu 'rv64' -smp 4 -m 4096 \
-object memory-backend-file,size=1G,share=on,mem-path=/dev/shm,id=hostmem \
-device ivshmem-plain,memdev=hostmem \
-device virtio-blk-device,drive=hd \
-drive file=/home/user/qemu/db-riscv64/image.qcow2,if=none,id=hd \
-device virtio-net-device,netdev=net \
-netdev user,id=net,hostfwd=tcp::2222-:22 \
-bios  /usr/share/qemu/opensbi-riscv64-generic-fw_dynamic.bin \
-kernel /home/user/qemu/db-riscv64/kernel \
-initrd /home/user/qemu/db-riscv64/initrd \
-virtfs local,path=/home/user/qemu/db-riscv64/shared_folder,mount_tag=shared,security_model=none,id=hostshare \
-nographic  -append "root=LABEL=rootfs console=ttyS0"

通过lspci -vv可识别ivshmem设备及其BAR地址:

00:01.0 RAM memory: Red Hat, Inc. Inter-VM shared memory (rev 01)
        Subsystem: Red Hat, Inc. QEMU Virtual Machine
        Control: I/O- Mem+ BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-
        Status: Cap- 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
        Region 0: Memory at 40001000 (32-bit, non-prefetchable) [size=256]
        Region 2: Memory at 400000000 (64-bit, prefetchable) [size=1G]

但运行以下测试C代码时触发段错误:

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>

#define SHM_SIZE 4096 // size of the shared memory region to map
#define SHM_BAR 0x400000000 // BAR address of the ivshmem device (obtained from lspci)

void write_to_shared_memory(){
    volatile uint32_t *shm = (volatile uint32_t *)SHM_BAR;
    shm[0] = 1234;  
    printf("Written Data: %d\n", shm[0]);
}

int main() {
    int fd = open("/dev/mem", O_RDWR); // open the /dev/mem device file
    if (fd == -1) {
        perror("open");
        return -1;
    }
    void *shm = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, SHM_BAR); // map the BAR address to shm
    if (shm == MAP_FAILED) {
        perror("mmap");
        return -1;
    }
    // do something with shm, such as reading or writing data
    write_to_shared_memory();

    munmap(shm, SHM_SIZE); // unmap the shared memory region
    close(fd); // close the /dev/mem device file
    return 0;
}

错误信息:

[11086.441459] memory_sharing[475]: unhandled signal 11 code 0x1 at 0x0000000400000000 in memory_sharing[5555571d8000+1000]
[11086.443966] status: 8000000200006020 badaddr: 0000000400000000 cause: 000000000000000f
Segmentation fault

错误原因

  1. 用户态无法直接访问物理地址write_to_shared_memory函数直接使用物理地址0x400000000作为指针操作,但RISC-V用户态进程只能访问虚拟地址空间,物理地址必须通过mmap映射到虚拟地址后才能访问。
  2. 代码逻辑冗余且错误:已经通过mmap获取了合法的虚拟地址shm,但未使用该地址,反而直接访问未映射的物理地址,触发MMU页错误。
  3. 地址空间限制0x400000000属于物理地址空间,不在用户态虚拟地址的可访问范围内,直接访问会触发段错误。

解决方法

修改测试代码,使用映射后的虚拟地址

删除直接访问物理地址的逻辑,改用mmap返回的虚拟地址进行读写:

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>

#define SHM_SIZE 4096
#define SHM_BAR 0x400000000

int main() {
    int fd = open("/dev/mem", O_RDWR);
    if (fd == -1) {
        perror("open");
        return -1;
    }

    // 映射物理地址到进程虚拟地址空间
    void *shm = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, SHM_BAR);
    if (shm == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return -1;
    }

    // 使用虚拟地址操作共享内存
    volatile uint32_t *shm_ptr = (volatile uint32_t *)shm;
    shm_ptr[0] = 1234;
    printf("Written Data: %d\n", shm_ptr[0]);

    munmap(shm, SHM_SIZE);
    close(fd);
    return 0;
}

主机端访问共享内存的方法

主机端可直接打开/dev/shm下的共享内存文件(若QEMU未指定具体路径,可通过ls /dev/shm查找临时文件,或启动时指定mem-path=/dev/shm/qemu_shm),映射后读写:

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>

#define SHM_SIZE 4096
#define SHM_PATH "/dev/shm/qemu_shm"

int main() {
    int fd = open(SHM_PATH, O_RDWR);
    if (fd == -1) {
        perror("open");
        return -1;
    }

    void *shm = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return -1;
    }

    volatile uint32_t *shm_ptr = (volatile uint32_t *)shm;
    printf("Read Data from VM: %d\n", shm_ptr[0]);

    munmap(shm, SHM_SIZE);
    close(fd);
    return 0;
}

额外注意事项

  • 运行虚拟机内的程序需要root权限,或给程序添加CAP_SYS_RAWIO权限(setcap cap_sys_rawio+ep ./memory_sharing)。
  • 确保SHM_BAR地址是页对齐的(从lspci输出看0x400000000已满足),否则mmap会失败。

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

火山引擎 最新活动