Linux内存子系统——Locking Pages(内存锁定)
你可以让系统将特定的虚拟内存页与实际页帧相"关联",并保持这样的状态(称为
锁定
)。该部分内存不会被swap机制交换出来,也不会产生pagefault(因为已经分配了实际的物理内存)。
为什么需要锁定内存
一个背景知识pagefault
用户在分配出一部分虚拟内存时,其背后可能并没有真正的物理内存与之对应,只有在用户真正需要访问内存时,系统才会为这段虚拟内存分配实际的物理内存,这个过程叫做pagefault
(缺页异常)。这个过程对用户来说是不感知的,所以用户可以总是假定他要使用的虚拟内存背后有实际的物理内存
1. 速度
当用户只是执行简单的内存访问时,pagefault
流程对用户来说虽然是不感知的,耗时可以忽略不记,但是对于一些时间敏感型进程,尤其是实时进程,可能无法容忍执行速度的下降。
这种情况下,程序员可以先把所需要使用到的内存全部锁定
,为它们提前分配好实际的物理内存,这样在访问时,就可以省去pagefault流程,提升程序执行速度。
2. 安全
如果你把一些秘密存放在虚拟内存中(比如用户输入的密码),当虚拟内存被swap到磁盘后,就可能导致泄露。且可能在虚拟内存和物理内存被清除后很长一段时间依然存在。
副作用
当你每多lock一个页帧,那么可供其他虚拟内存使用的页帧就少了一个,意味者系统里可能发生更多的缺页异常,更多的swap,而导致系统执行速度变慢。
一个极端的情况是,当你锁定了所有的内存,系统将因为没有实际可用的内存而无法运行
一些细节
1. 堆叠
内存锁不会堆叠,你即使锁定一段内存两次,也只需要解锁一次
2. 生命周期
内存锁定会一直持续到拥有内存的进程显示的解锁它。但是进程终止
和exec
会导致虚拟内存不再存在,这可能意味着它不再被锁定
3. 继承
内存所不会被子进程继承,(但请注意,在现代Unix系统中,在fork之后,父级和子级的虚拟地址空间由相同的实页帧支持,因此子级享有父级的锁)
4. 权限
由于它能够影响其他进程,因此只有超级用户
可以锁定,但所有进程都可以解锁自己的内存
5. 写入时复制的行为
这里有一个非常有趣的行为,但我还没有研究透,允许我先挖个坑
libc接口
mlock
将从addr
开始长度len
的内存锁定
int mlock (const void *addr, size t len)
munlock
将从addr
开始长度len
的内存解锁
int munlock (const void *addr, size t len)
mlockall
全部锁定
int mlockall (int flags)
标志位说明:
MCL_CURRENT
代表只锁定当前已经分配的内存
MCL_FUTURE
将来分配的内存也会被立刻锁定,注意单独设置这个标志位不会锁定当前已经被分配的内存
注意 MCL_FUTURE
不会影响未来的进程地址空间,例如exec
后,该标志位将被擦除
munlockall
没啥好说的了,一次解锁所有内存(自己进程的)
int munlockall (void)
参考文献
The GNU C Library Reference Manual 3.5