调用madvise(..., MADV_DONTNEED)会触发段错误吗?如何识别合法内存段?
关于
madvise(*, *, MADV_DONTNEED)的问题及解答 问题描述
madvise(*, *, MADV_DONTNEED)是否属于纯建议性操作?- 尝试扫描
/proc/self/maps并对所有条目调用madvise(addr, size, MADV_DONTNEED)时出现段错误,有没有简便方法判断哪些内存段可以安全调用该操作?
复现场景
运行以下代码:
#include <stdio.h> #include <sys/mman.h> int main(void) { FILE *fp; char line[1024]; fp = fopen("/proc/self/maps", "r"); if (fp == NULL) { perror("Failed to open maps file"); return 1; } while (fgets(line, sizeof(line), fp)) { char addr[32], perms[8], offset[32], dev[32], inode[32], path[256]; unsigned long start, end; path[0] = '\0'; sscanf(line, "%lx-%lx %s %s %s %s %[^\n]", &start, &end, perms, offset, dev, inode, path); int ret = madvise((void*)start, end - start, MADV_DONTNEED); if(ret == 0) printf(" OK\n"); else printf(" Error\n"); fflush(stdout); } fclose(fp); }
输出结果:
OK OK OK OK Segmentation fault
解答
1. MADV_DONTNEED是不是纯建议性操作?
不完全是。虽然madvise的多数标记是建议性的,但Linux下的MADV_DONTNEED会直接触发内核释放对应内存页的物理映射:
- 匿名页会被直接清零并释放物理内存;
- 文件映射的缓存页会被丢弃,后续访问会重新从文件读取。
不过它不会修改虚拟地址空间的权限,进程后续访问该地址时会自动重新分配物理页。但如果操作的是内核专属内存区域,会触发权限错误甚至段错误。
2. 为什么会出现段错误?
/proc/self/maps包含了进程的所有虚拟内存段,其中包括内核管理的特殊段(比如[vdso]、[vsyscall]、[vvar]),这些属于内核空间,用户进程没有权限对其调用madvise,强行操作就会触发段错误。
3. 如何判断可操作的内存段?
可以通过/proc/self/maps或/proc/self/smaps的字段筛选:
- 权限检查:
/proc/self/maps的第二列是权限字段,确保段拥有r(读)权限——madvise操作至少需要读权限才能执行; - 排除内核特殊段:跳过名称为
[vdso]、[vsyscall]、[vvar]的段; - 进阶筛选(可选):查看
/proc/self/smaps的VmFlags字段,排除带有locked、kernel标记的段。
修改后的安全代码示例
在调用madvise前加入判断逻辑:
#include <stdio.h> #include <sys/mman.h> #include <string.h> int main(void) { FILE *fp; char line[1024]; fp = fopen("/proc/self/maps", "r"); if (fp == NULL) { perror("Failed to open maps file"); return 1; } while (fgets(line, sizeof(line), fp)) { char addr[32], perms[8], offset[32], dev[32], inode[32], path[256]; unsigned long start, end; path[0] = '\0'; sscanf(line, "%lx-%lx %s %s %s %s %[^\n]", &start, &end, perms, offset, dev, inode, path); // 跳过内核特殊段 if (strstr(path, "[vdso]") != NULL || strstr(path, "[vsyscall]") != NULL || strstr(path, "[vvar]") != NULL) { printf(" Skip kernel segment\n"); continue; } // 检查是否有读权限 if (strchr(perms, 'r') == NULL) { printf(" Skip no-read segment\n"); continue; } int ret = madvise((void*)start, end - start, MADV_DONTNEED); if(ret == 0) printf(" OK\n"); else printf(" Error\n"); fflush(stdout); } fclose(fp); return 0; }
内容的提问来源于stack exchange,提问作者MWB
相关产品推荐
相关产品推荐

