mlock, munlock, mlockall, munlockall - 锁定或解锁内存
概要
#include <sys/mman.h>
int mlock(const void *addr, size_t len);
int munlock(const void *addr, size_t len);
int mlockall(int flags);
int munlockall(void);
描述
mlock() 和 mlockall() 锁定调用进程的部分或全部虚拟地址空间在 RAM 内,阻止内存被页交互出去。munlock()和 munlockall()执行相反的操作,因此指定的虚拟地址可能在内核管理内存时多次被交互出去。内存锁定或解锁操作的单位都是整个分页。
mlock() 和 munlock()
mlock() 锁定开始于地址 addr 并延续长度为 len 个地址范围的内存。调用成功返回后所有包含该地址范围的分页都保证在 RAM内;这些分页保证一直在RAM 内直到后来被解锁。
munlock() 解锁开始 addr 并延续长度为 len 个字节的地址范围的分页。本调用之后,所有包含指定内存区间的分页都有可能因为额外的交互而被内核交互出去。
mlockall() 和 munlockall()
mlockall() 锁定调用里程所有映射到地址空间的分页。这包括代码、数据、栈片段分页,同时也包括共享库、用户空间内核数据、共享内存以及内存映射的文件。调用成功返回后所有映射的分页都保证在 RAM 中:直到后来的解锁,这些分页都保证一直在 RAM 内。
参数 flags 由下面数值常量一个或多个按位或运算构造:
MCL_CURRENT 锁定所有当前映射到进程地址空间的分页。 MCL_FUTURE 锁定所有接下来被映射到进程地址空间的分页。这些分页可能是堆栈增长请求的分页,内存文件映射或共享内存区域请求的分页。
如果 MCL_FUTURE 被指定,那么接下来的系统调用(如 mmap(2)、sbrk(2)、malloc(3)),都有可能因为锁定的分页超限(参考下面)而失败。同样道理,栈增长也可能失败:内核将阻止栈增长并递送信号 SIGSEGV 给相应进程。
munlockall() 解锁所有映射到调用进程地址空间的分页。
返回值
成功时这些系统调用返回 0。失败时,-1 被返回,errno 被设置为合适的值,并且进程的地址空间锁没有任何变化。
错误
ENOMEM (Linux 2.6.9 和以后)调用者有一个非零的 RLIMIT_MEMLOCK 软件资源限制,但是试图锁定超过这个限制的内存。如果进程拥有特权(CAP_IPC_LOCK),这个限制不是强制的。 ENOMEM (Linux 2.4 和以前)调用进程试图锁定的内存超过RAM 的一半。 EPERM(Linux 2.6.9 和之后)调用者没有特权 (CAP_IPC_LOCK) 并且 RLIMIT_MEMLOCK 软件资源限制设置为 0。 EPERM (Linux 2.6.8 及之前)调用进程没有足够的特权来调用
munlock()。在 Linux 系统下 CAP_IPC_LOCK 兼容的权限是必需的。
对于 mlock() 和 munlock():
EAGAIN 指定地址区间部分或全部无法锁定。 EINVAL len 是负值。 EINVAL (非 Linux) addr 不是页大小的整数倍。 ENOMEM 在当前进程的地址空间里,指定的地址区间有部分没有映射到分页。
对于 mlockall():
EINVAL 未知的 flags 被指定。
对于munlockall():
EPERM (Linux 2.6.8 及此前) 调用者没有特权 (CAP_IPC_LOCK)。
遵循于
POSIX.1-2001、SVr4。
有效性
在 mlock() 和 munlock() 有效的 POSIX 系统里 ,_POSIX_MEMLOCK_RANGE 定义在 <unistd.h> 中并且分页大小可能通过定义(如果定义了)在 <limits.h> 的常量 PAGESIZE 来取得或调用 sysconf(_SC_PAGESIZE) 来取得。
在 mlockall() 和 munlockall() 有效的 POSIX 系统里,_POSIX_MEMLOCK 是定义在 <unistd.h> 中的大于 0 的值。(参看 sysconf(3)。)
注意
内存锁定有两个主要的应用:实时算法和高安全数据处理。实时算法要求精确的计时,而调度和分页都会引起不期望的执行延迟。实现算法也应该使用 sched_setscheduler(2) 切换到实时调度中去。密码安全软件通常处理诸如密码或安全密钥作为严密的字节数据结构。分页的结果将会把这些秘密持久地存入交互空间的介质,而这个地方可能在安全软件清除 RAM 内密码并终止之后长久能被敌人访问。(但是请注意在现代笔记本和一些桌面机都会保存一份系统 RAM 到磁盘上去,而不管有没有内存锁。)
实时进程应该使用 mlockall() 来阻止延迟的页失败,这样应该会在严格的实时部分锁定足够多的栈空间分页,如此不会有函数调用导致的页失败。这可以通过调用一个分配大量自动变量(或数组)的函数并写入数据到这些数组的地方以触及这些栈分页。这种方式,足够多页会被映射并被锁定在 RAM。模仿的写可以保证在严格执行部分不会发生“写时复制”的分页失败。
内存锁不会由通过 fork(2)调用创建的子进程继承并在调用execve(2) 或进程终止时被删除(解锁)。
地址区间内的内存锁会因为地址区间被 munmap(2) 反映射而自动删除。
内存不会入栈,如果分页被mlock() 或 mlockall() 锁定多次,那么在相应区间内的一次 munlock() 或 一个 munlockall() 就会解锁。被映射到多处的分页或被多个进程锁定的 RAM 将锁定到最后一个区域解锁或最后一个进程解锁。
Linux 注意
在 Linux 系统里,mlock() 和 munlock() 会怎么向下扩展 addr 到最近的分页边缘。然而 POSIX.1-2001 允许实现要求 addr 是页对齐的,因此可移植的应用应当保证这一点。
VmLck 是 Linux 定义的 /proc/PID/status 文件显示ID进程为 PID 的进程使用 mlock()、mlockall()、shmctl(2) SHM_LOCK 和 mmap(2) MAP_LOCKED 锁定多少 KB 的内存。
限制和权限
Linux 2.6.8 及此前,一个进程必需有特权 (CAP_IPC_LOCK) 才可以锁定内存以及 RLIMIT_MEMLOCK 软件资源限制定义了当前进程有多少内存可以锁定。
从 Linux 2.6.9,没有特权的进程也可以锁定内存以及 RLIMIT_MEMLOCK 软件资源限制定义了没有特权的进程可以锁定内存的上限。
错误
在 2.4 内核中一直到 2.4.17,一个臭虫引起 mlockall() MCL_FUTURE 标志会被 fork(2) 子进程继承。在内核 2.4.28 修正了这个臭虫。
从 2.6.9 内核开始,如果一个特权进程调用 mlockall(MCL_FUTURE)并在之后放弃了特权(丢失了 CAP_IPC_LOCK 特权,比如调用有效 UID 为非零值),那么之后的内存分配(如 mmap(2)、brk(2))将会失败于 RLIMIT_MEMLOCK 达到资源限制。
参看
mmap(2)、setrlimit(2)、shmctl(2)、sysconf(3)、proc(5)、capabilities(7)。