能否保护malloc/mmap分配的内存避免被swap-out操作换出?
如何防止malloc/mmap分配的内存被swap换出
可以实现,核心是使用操作系统提供的强制页锁定机制,注意内存保护接口mprotect仅能修改内存页的读/写/执行权限,完全无法阻止换出操作,不要用错接口。
核心可用接口
POSIX标准提供了两个层级的页锁定接口,所有主流类Unix系统(Linux、BSD、macOS等)都支持:
mlock(const void *addr, size_t len):锁定指定地址区间对应的物理内存页,被锁定的页不会被swap机制选中换出,也不会在内存压力下被内核回收。接口会自动将传入的地址对齐到系统页边界,不需要手动做对齐处理,对应的解锁接口是munlock。mlockall(int flags):直接锁定整个进程地址空间的内存,不需要逐段处理,支持两个组合标志:MCL_CURRENT:锁定当前已经映射到进程地址空间的所有内存页MCL_FUTURE:后续进程新分配、新映射的内存(包含malloc、mmap、栈扩展等所有来源的内存)自动加锁
对应的解锁接口是munlockall。
不同分配方式的具体用法
针对mmap分配的内存
拿到mmap返回的有效地址指针后,直接对目标区间调用mlock即可,示例代码:
// 分配4KB匿名私有内存 void *mmap_buf = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mmap_buf == MAP_FAILED) { // 处理mmap分配失败逻辑 } // 锁定对应内存区间 if (mlock(mmap_buf, 4096) != 0) { // 处理mlock失败逻辑,最常见原因是进程锁内存额度不足 }
如果是文件映射的mmap区域,锁定后同样会常驻内存,不会触发回写或换出。
针对malloc分配的内存
分两种场景处理:
- 分配块大于堆管理器的mmap阈值(glibc默认是128KB)时,malloc底层会直接调用mmap分配匿名内存,处理逻辑和上面mmap的场景完全一致,拿到指针直接调
mlock即可。 - 小块堆分配的场景,由于malloc返回的地址是堆管理器从已有的堆段切分出来的,可能和其他分配块共享内存页,如果只需要锁少量关键小块内存,直接对malloc返回的指针和申请长度调用
mlock即可(内核会自动对齐覆盖整个涉及到的页,注意这会把同页的其他内存块也一起锁住);如果需要锁的关键内存较多,直接调用mlockall(MCL_CURRENT | MCL_FUTURE)全局锁定更省心,避免逐块加锁遗漏。
关键注意事项
- 锁内存有额度限制:普通用户进程默认能锁定的内存总量有严格上限,Linux下可以通过
ulimit -l查看当前限制,默认通常是64KB~8MB不等。如果需要锁定更大内存,要么调整系统memlock限制,要么给进程授予CAP_IPC_LOCK权限,不需要root身份也能突破默认额度限制。 - 不要过量锁定内存:被锁定的内存内核完全无法回收,锁定总量不要超过系统实际可用物理内存,否则会导致系统剩余内存不足触发OOM,甚至导致系统卡死。
- 区分建议性操作和强制操作:
madvise(MADV_WILLNEED)只是给内核的预读提示,内存压力大时对应页依然会被换出,没有强制约束力,不能用来实现可靠的内存常驻需求。 - 锁会自动释放:不需要手动解锁的场景:调用
munmap释放mmap内存、调用free释放堆内存、进程退出时,内核都会自动解除对应区间的内存锁,不会造成资源泄漏。
内容的提问来源于stack exchange,提问作者Heoney
相关产品推荐
相关产品推荐

