内存锁定系统调用:mlock, mlock2, munlock, mlockall, munlockall详解
Linux操作系统提供了一组内存锁定系统调用,允许进程将其部分或全部地址空间锁定在物理内存中,防止这些内存页被换出到交换空间。这对于实时应用程序、安全敏感的程序以及需要确保内存访问延迟可预测的场景非常重要。本文将详细介绍这些系统调用的功能、用法和工作原理。
内存锁定的基本概念
内存锁定机制允许程序锁住其部分或全部地址空间,确保这些内存页始终驻留在物理内存中。这意味着无论系统内存压力如何,操作系统不会将这些内存页换出到交换空间(swap space)[9]。这对于以下场景特别有价值:
- 实时应用程序:需要确保内存访问的时间延迟可预测
- 安全敏感应用:防止敏感数据被写入交换文件,避免程序结束后可能被恢复
- 高性能计算:确保关键数据结构始终在内存中,减少访问延迟
需要注意的是,内存锁定和解锁都是以整页为单位进行的,在x86 Linux系统上,页面大小通常为4KB[9]。
mlock() 和 munlock() 函数
mlock() 函数
mlock()
系统调用允许进程锁定指定范围的地址空间:
int mlock(const void *addr, size_t len);
参数说明:
addr
:指定要锁定的地址范围的起始地址len
:指定要锁定的地址空间长度(以字节为单位)
调用成功时返回0,失败时返回-1并设置errno
[1][2]。
mlock()
锁定从addr
开始、长度为len
字节的地址范围内的所有页面。包含指定地址范围的所有页面在调用成功返回时都保证驻留在RAM中,并且会一直保持在RAM中,直到被解锁[2]。
munlock() 函数
munlock()
系统调用用于解锁之前通过mlock()
锁定的内存:
int munlock(const void *addr, size_t len);
参数与mlock()
相同,用于指定要解锁的内存区域。调用成功后,指定范围内的所有页面可以被操作系统根据需要再次换出到交换空间[2]。
mlock2() 函数
mlock2()
是mlock()
的扩展版本,增加了更多的控制选项:
int mlock2(const void *addr, size_t len, unsigned int flags);
除了前两个与mlock()
相同的参数外,它增加了flags
参数:
- 如果
flags
为0,则mlock2()
的行为与mlock()
完全相同 - 如果
flags
为MLOCK_ONFAULT
,则只锁定当前已在内存中的页面,并标记整个范围,使得剩余的非驻留页面在发生页面错误时才被锁定[5][7]
MLOCK_ONFAULT
标志提供了更高效的内存锁定方式,特别适用于处理大型内存映射的应用程序,其中只有部分数据需要被访问[10][13]。这种方式可以显著减少内存使用,因为只有实际访问的页面才会被锁定。
mlockall() 和 munlockall() 函数
mlockall() 函数
mlockall()
系统调用锁定调用进程的整个地址空间:
int mlockall(int flags);
flags
参数可以是以下一个或多个值的组合[4][17]:
MCL_CURRENT
:锁定当前映射到进程地址空间的所有页面MCL_FUTURE
:锁定将来映射到进程地址空间的所有页面,在建立这些映射时MCL_ONFAULT
:类似于mlock2()
的MLOCK_ONFAULT
,只在页面错误发生时锁定页面
使用MCL_CURRENT|MCL_FUTURE
可以锁定当前以及将来分配的所有内存[9]。
munlockall() 函数
munlockall()
系统调用解锁进程的所有已锁定内存:
int munlockall(void);
该函数不需要参数,成功时返回0,失败时返回-1并设置errno
[4]。
munlockall()
对进程地址空间中所有当前映射的页面进行解锁。调用munlockall()
后映射到进程地址空间的新页面不会被锁定,除非后续又调用了mlockall()
[4][17]。
内存锁定的工作原理
内存锁定的核心工作原理包括:
-
页面级操作:内存锁定和解锁是以整页为单位进行的,而不是以字节或其他单位[1][2]。
-
物理内存保留:被锁定的页面保证在物理RAM中驻留,不会被换出到交换空间,即使系统内存压力较大[1]。
-
页面映射:成功调用内存锁定函数后,操作系统会为指定的地址空间分配物理页面,并确保这些页面保持在内存中[1]。
-
写时复制考量:仅分配内存并调用
mlock()
不足以确保为调用进程锁定这些内存,因为对应的页面可能是写时复制(copy-on-write)的。因此,建议在每个页面写入一个值,强制系统为进程分配独立的内存页[9]。 -
内存移动的限制:使用
mlock()
不保证程序不会遇到页面I/O。它确保数据保留在内存中,但无法确保它保留在同一页面中。其他功能如move_pages
和内存压缩器仍然可以移动数据[1][15]。
内存锁定的使用限制与注意事项
权限要求
通常,只有具有超级用户(root)权限的进程才能使用mlock()
或mlockall()
锁定内存。如果无权限的进程调用这些函数,将会失败并返回-1,同时errno
被设置为EPERM
[4][9]。
资源限制
系统通过RLIMIT_MEMLOCK
限制一个进程可以锁定在内存中的字节数。对于mlock()
、mlockall()
和mmap() MAP_LOCKED
操作,这是一个进程级别的限制[6][12]。
锁定大量内存,尤其是通过mlockall()
,可能对整个系统造成严重影响。过度的内存锁定可能导致系统其他进程争夺更少的可用资源,增加内存交换的频率,甚至导致系统开始终止进程[9]。
页面共享与双锁定问题
内存锁定是基于页面进行的,而不是基于分配单元。如果两个动态分配的内存片段共享同一页面,并且被mlock()
或mlockall()
锁定两次,那么一次munlock()
或munlockall()
调用就会解锁整个页面[1][15]。
为了减少双锁定或单锁定问题,有两种常用的解决方案:
- 跟踪已分配和锁定的内存区域,创建包装函数,在解锁页面前验证页面拥有的分配数量[1][15]。
- 执行内存分配时考虑页面大小和对齐,以防止在同一页面中出现双锁定[1][15]。
自动解锁条件
除了显式调用munlock()
或munlockall()
外,内存锁在以下情况下会被自动删除[12]:
- 进程终止时
- 被锁住的页面通过
munmap()
被解除映射时 - 被锁住的页面被使用
mmap() MAP_FIXED
标记的映射覆盖时
实际应用示例
以下是一个使用mlock()
锁定内存的简单示例:
#include
#include
#include
int main() {
// 分配32MB内存
const int alloc_size = 32 * 1024 * 1024;
char* memory = malloc(alloc_size);
// 锁定内存
if (mlock(memory, alloc_size) != 0) {
perror("mlock failed");
free(memory);
return 1;
}
// 确保每个页面都被实际分配(避免写时复制问题)
size_t page_size = getpagesize();
for (size_t i = 0; i
int main() {
// 锁定当前和未来的所有内存映射
if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
perror("mlockall failed");
return 1;
}
// 程序继续运行,所有内存都被锁定...
// 程序结束前解锁所有内存
munlockall();
return 0;
}
对于大型内存映射但只需要部分锁定的场景,可以使用mlock2()
的MLOCK_ONFAULT
标志:
#include
int main() {
void *addr = /* 大型内存映射 */;
size_t len = /* 映射大小 */;
// 只锁定当前访问的页面,其他页面在访问时才锁定
if (mlock2(addr, len, MLOCK_ONFAULT) != 0) {
perror("mlock2 failed");
return 1;
}
// 使用内存...
// 解锁
munlock(addr, len);
return 0;
}
总结
内存锁定系统调用是Linux系统提供的重要功能,允许应用程序控制内存页的驻留行为。这些功能对于实时系统、安全应用和高性能计算尤为重要。mlock()
和munlock()
提供了基本的内存区域锁定和解锁能力,mlock2()
增加了更灵活的锁定方式,而mlockall()
和munlockall()
则提供了对整个进程地址空间的控制。
在使用这些系统调用时,需要注意权限要求、资源限制以及对系统整体性能的影响。合理使用内存锁定机制可以提高特定场景下的应用程序性能和可靠性,但过度使用可能导致系统资源紧张,影响整体系统稳定性。
Citations:
[1] https://docs.redhat.com/zh-cn/documentation/red_hat_enterprise_linux_for_real_time/9/html/understanding_rhel_for_real_time/con_mlock-system-calls_assembly_memory-management-on-rhel-for-real-time-
[2] https://man.archlinux.org/man/mlock.2.en
[3] https://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316695.html
[4] https://www.ibm.com/docs/zh/aix/7.3?topic=m-mlockall-munlockall-subroutine
[5] https://man7.org/linux/man-pages/man2/mlock.2.html
[6] https://www.ichenfu.com/2019/07/18/linux-rlimits/
[7] https://aquasecurity.github.io/tracee/v0.13/docs/events/builtin/syscalls/mlock2/
[8] https://man7.org/linux/man-pages/man2/mlockall.2.html
[9] https://blog.csdn.net/u011955950/article/details/18860449
[10] http://man.he.net/man2/mlock2
[11] https://github.com/rfjakob/earlyoom/issues/112
[12] https://www.cnblogs.com/arnoldlu/p/12551008.html
[13] https://www.gnu.org/s/libc/manual/html_node/Page-Lock-Functions.html
[14] http://man.he.net/man2/mlockall
[15] https://docs.redhat.com/zh-cn/documentation/red_hat_enterprise_linux_for_real_time/8/html/understanding_rhel_for_real_time/con_mlock-system-calls_assembly_memory-management-on-rhel-for-real-time-
[16] https://github.com/torvalds/linux/blob/master/mm/mlock.c
[17] https://pubs.opengroup.org/onlinepubs/7908799/xsh/mlockall.html
[18] https://coral.googlesource.com/linux-imx/+/refs/tags/10-1/tools/testing/selftests/vm/mlock2-tests.c?autodive=0%2F%2F%2F
[19] https://android.googlesource.com/kernel/common/+/refs/heads/android-mainline/mm/mlock.c
[20] https://docs.redhat.com/zh-cn/documentation/red_hat_enterprise_linux_for_real_time/9/html/understanding_rhel_for_real_time/proc_using-mlock-system-calls-to-lock-pages_assembly_using-mlock-system-calls-on-rhel-for-real-time
[21] https://blog.csdn.net/m0_74282605/article/details/129465633
[22] https://man7.org/linux/man-pages/man2/mlock.2.html
[23] https://blog.csdn.net/goosse/article/details/128346376
[24] https://man7.org/linux/man-pages/man3/mlockall.3p.html
[25] https://cloud.tencent.com/developer/article/1087458
[26] https://blog.csdn.net/dbitc/article/details/140439985
[27] https://www.man7.org/linux/man-pages/dir_section_2.html
[28] https://blog.csdn.net/enlyhua/article/details/83474290
[29] https://wanghenshui.github.io/2020/06/03/linux-handbook.html
[30] https://blog.csdn.net/XD_hebuters/article/details/78550656
[31] https://www.zengl.com/a/201403/141.html
[32] https://blog.csdn.net/Linux_Everything/article/details/121943153
[33] https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md
[34] https://sbexr.rabexc.org/latest/sources/3d/1703890fe0d9c3.html
[35] https://www.cnblogs.com/fortunely/p/14855912.html
[36] https://docs.redhat.com/en/documentation/red_hat_enterprise_linux_for_real_time/8/html/optimizing_rhel_8_for_real_time_for_low_latency_operation/assembly_using-mlock-system-calls-on-rhel-for-real-time_optimizing-rhel8-for-real-time-for-low-latency-operation
[37] https://linaro.atlassian.net/browse/LKQ-57
[38] https://blog.csdn.net/u014636245/article/details/106874059
[39] https://filippo.io/linux-syscall-table/
[40] https://www.cnblogs.com/hellokitty2/p/18056006
[41] https://docs.redhat.com/en/documentation/red_hat_enterprise_linux_for_real_time/7/html/reference_guide/using_mlock_to_avoid_page_io
[42] https://www.ibm.com/docs/zh/safer-payments/6.6.0?topic=configuration-changing-operating-system-settings
[43] https://stackoverflow.com/questions/41014740/mlockall-to-another-process
[44] http://mails.dpdk.org/archives/dev/2020-February/158487.html
[45] https://pubs.opengroup.org/onlinepubs/009696799/functions/mlockall.html
[46] https://www.reddit.com/r/rust/comments/7vv999/rust_and_the_classical_c_cryptosecurity_idioms/
[47] https://man.freebsd.org/cgi/man.cgi?query=mlockall
[48] https://www.qnx.com/developers/docs/6.3.2/neutrino/lib_ref/m/mlockall.html
[49] https://www.qnx.com/developers/docs/6.5.0SP1.update/com.qnx.doc.neutrino_lib_ref/m/mlockall.html
[50] https://github.com/GrapheneOS/hardened_malloc/issues/128
[51] https://www.ibm.com/docs/en/aix/7.2?topic=m-mlockall-munlockall-subroutine
[52] https://news.ycombinator.com/item?id=18185706
[53] https://www.mkssoftware.com/docs/man3/mlockall.3.asp