为何访问mmap映射范围外内存未触发预期的SIGSEGV?
内存映射超出长度未触发SIGSEGV的原因
我尝试创建长度大于底层文件的内存映射(mmap)。当访问映射中无对应文件内容的页面时,会触发SIGBUS信号;但访问超出内存映射长度的内存区域时,预期应触发SIGSEGV信号却未出现,请问这是为什么?
#include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> int main(int argc, char* argv[]) { char* inp_file_name = argv[1]; int src_fd = open(inp_file_name, O_RDWR); printf("system page size is: %ld\n", sysconf(_SC_PAGE_SIZE)); struct stat buf; fstat(src_fd, &buf) == -1); printf("file size is: %ld\n", buf.st_size); // 创建更大的内存映射 // 文件大小为9360字节, // 通过以下命令生成:dd if=/dev/random of=rand_inp count=120 char* addr = mmap(NULL, 2 * buf.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0); // 会触发SIGBUS // printf("value at point in unmapped file region: %c", addr[13000]); // 预期触发SIGSEGV // 未触发,可能是因为访问的是进程虚拟内存中未标记为不可访问的区域 printf("value at point in unmapped file region: %c", addr[40000]); }
原因解析
1. 虚拟内存的页粒度对齐
操作系统以页为最小单位管理虚拟内存,mmap的映射长度会被自动向上对齐到系统页大小的整数倍。假设系统页大小为4KB(4096字节),你的文件大小9360字节,2*buf.st_size为18720字节,对齐后实际映射的是20480字节(5个完整页)。你访问的addr[40000]对应第10个页,但这个地址未必处于未映射区域——如果它恰好落在进程已有的其他虚拟内存映射(如堆、共享库、栈)范围内,或者操作系统为进程预分配的虚拟地址空间中,访问就不会触发SIGSEGV。
2. SIGSEGV与SIGBUS的触发边界
- SIGBUS:触发于访问
mmap映射范围内,但对应文件内容不存在的地址(比如文件大小不足以覆盖该页的磁盘数据),内核无法从文件加载页内容,因此抛出该信号。 - SIGSEGV:仅当访问的地址完全不在进程的任何有效虚拟内存映射中(即页表中无对应可访问的项)时才会触发。如果
addr[40000]所在的页被进程其他映射占用,或属于进程虚拟地址空间中已预留但未提交的区域,访问时就不会触发信号。
3. 代码中的语法错误
原代码中fstat(src_fd, &buf) == -1);是无效语法,缺少if关键字和错误处理逻辑,可能导致buf.st_size未初始化,进而让mmap的长度计算出错,干扰测试结果。
验证方案
要确保访问的地址确实在所有有效映射之外,可以:
- 运行程序时,通过
/proc/[进程ID]/maps查看虚拟内存映射详情,确认addr+40000是否落在任何有效映射区间外。 - 大幅增大访问偏移,比如访问
addr[100*1024*1024](100MB偏移),此时该地址大概率处于未映射的虚拟内存区域,会触发SIGSEGV。
内容的提问来源于stack exchange,提问作者sudeepdino008
相关产品推荐
相关产品推荐

