为何使用POSIX可创建超出/dev/shm挂载大小的共享内存?
我来帮你理清这个问题——这其实是tmpfs(也就是/dev/shm背后的文件系统)的延迟内存分配特性导致的,咱们一步步拆解:
为什么创建阶段不报错?
/dev/shm是基于tmpfs的挂载点,tmpfs的核心特点之一就是懒加载内存:
- 当你调用
ftruncate(fd, size)设置一个超大的共享内存尺寸时,系统并没有立刻为这个大小分配对应的物理内存,只是在文件系统的inode里记录了这个目标尺寸而已。此时并没有实际的内存页被占用,所以不会触发任何内存不足的错误。 - 同样,
mmap()只是在你的进程虚拟地址空间里建立了一段映射关系,并没有把物理内存页和这些虚拟地址绑定,所以也不会报错。
只有当你真正开始写入数据到这段共享内存时,内核才会尝试为对应的虚拟地址分配物理页框。如果这时候tmpfs的已用空间加上要分配的内存超过了它的挂载大小(你看到的500M),内核无法满足分配请求,就会给你的进程发送SIGBUS信号,也就是你看到的Bus Error。
如何在创建阶段就控制共享内存大小?
如果你想在创建共享内存的时候就确保不会超出/dev/shm的可用空间,可以试试这几种方法:
1. 提前检查/dev/shm的可用空间
你可以在代码里调用statfs()函数来获取/dev/shm的总大小和已用空间,然后和你要创建的共享内存大小做对比:
#include <sys/statfs.h> bool is_shm_size_allowed(size_t desired_size) { struct statfs fs_info; if (statfs("/dev/shm", &fs_info) != 0) { perror("statfs failed"); return false; } // 计算可用空间:每个块的大小 * 可用块数 uint64_t available = (uint64_t)fs_info.f_bsize * fs_info.f_bavail; return desired_size <= available; }
在调用ftruncate之前先调用这个函数,如果返回false就直接报错退出。
2. 强制提前分配所有内存
如果你想立刻验证内存是否足够,可以在mmap之后立刻把整个共享内存区域写一遍(比如用memset),这样内核会立刻尝试分配所有需要的物理页。如果超出限制,这一步就会触发SIGBUS,你可以捕获这个信号或者直接让程序退出,避免后续的意外:
// 在mmap成功后 memset(addr, 0, sizeToUse); // 写入所有页面,强制分配内存
不过这种方法会立刻占用所有请求的内存,可能不太适合超大尺寸的场景,但能提前暴露问题。
3. 改用System V共享内存(可选)
如果你更倾向于创建时就立刻分配内存的行为,可以考虑用System V的共享内存API(shmget)。它在创建共享内存段的时候就会立刻分配物理内存,所以如果请求的大小超过了系统限制(比如SHMMAX参数),shmget会直接返回错误,不需要等到写入阶段。不过System V共享内存的API不如POSIX的灵活,你需要权衡一下。
小提示
另外,你提到每次运行前需要手动删除共享内存,其实可以在程序退出时调用shm_unlink(name)来自动清理,这样就不用手动删除了——当然如果你需要保留它用于hexdump查看,那手动删除是没问题的。
内容的提问来源于stack exchange,提问作者Fulgor3

