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
错误原因
- 用户态无法直接访问物理地址:
write_to_shared_memory函数直接使用物理地址0x400000000作为指针操作,但RISC-V用户态进程只能访问虚拟地址空间,物理地址必须通过mmap映射到虚拟地址后才能访问。 - 代码逻辑冗余且错误:已经通过
mmap获取了合法的虚拟地址shm,但未使用该地址,反而直接访问未映射的物理地址,触发MMU页错误。 - 地址空间限制:
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




