使用write(/dev/null)检查虚拟地址有效性触发段错误,求原因
虚拟地址有效性检查:write触发段错误的原因分析
尝试通过向/dev/null写入目标地址的方式检查虚拟地址有效性,却触发段错误;改用mincore函数则可正常运行,运行环境为Ubuntu 22.04.5 LTS x86_64,内核版本5.15.0-100-generic。
测试代码(触发段错误)
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> int isMemoryMapped(void* addr) { int dev_null = open("/dev/null", O_WRONLY); if (dev_null == -1) { perror("Failed to open /dev/null"); exit(EXIT_FAILURE); } errno = 0; // 尝试向/dev/null写入 ssize_t result = write(dev_null, addr, sizeof(void*)); // <--- 地址无效时触发段错误 close(dev_null); return (result != -1 && errno != EFAULT); // 可访问返回1,不可访问返回0 } int main() { int a = 10, b = 20, c = 30; void* ptrArr[] = {&a, &b, &c}; printf("Starting address of ptrArr: %p\n", ptrArr); for (int i = 0; i < 1000000; i++) { if (isMemoryMapped(&ptrArr[i])) { printf("%d : Address %p is accessible, content = %p\n", i, &ptrArr[i], ptrArr[i]); } else { printf("%d : Address %p is NOT accessible\n", i, &ptrArr[i]); break; } } return 0; }
运行报错信息
... 1197 : Address 0x7ffeea336fe8 is accessible, content = 0x6f6d656d5f656661 1198 : Address 0x7ffeea336ff0 is accessible, content = 0x6e6163735f7972 1199 : Address 0x7ffeea336ff8 is accessible, content = (nil) Segmentation fault (core dumped)
问题原因
- write调用的本质是读取用户缓冲区内容:当传入完全未映射的无效虚拟地址时,CPU会立即触发页错误。内核处理该错误时,发现地址不属于进程的合法地址空间,会直接向进程发送
SIGSEGV信号,导致段错误,根本不会走到设置errno=EFAULT并返回-1的逻辑。 - 原方案仅适用于已映射但不可读的地址(比如写保护页):这种场景下,内核会拦截页错误,返回
EFAULT而不杀死进程,但完全未映射的地址不在此范围内。 - mincore的安全机制:
mincore是专门用于查询虚拟地址映射状态的系统调用,它直接查询进程页表,不会尝试访问地址对应的物理内存。即使地址未映射,它只会返回错误码,不会触发页错误或发送信号,因此能安全完成检查。
可正常工作的mincore实现
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <stdint.h> int isMemoryMapped(void* addr) { // 将地址对齐到页边界 long pageSize = sysconf(_SC_PAGESIZE); void* pageAlignedAddr = (void*)((uintptr_t)addr & ~(pageSize - 1)); unsigned char vec; if (mincore(pageAlignedAddr, pageSize, &vec) == 0) { return 1; // 内存已映射 } return 0; // 内存未映射 }
内容的提问来源于stack exchange,提问作者Abhishek Ghosh
相关产品推荐
相关产品推荐

