50 虚拟内存操作
本章介绍在进程的虚拟地址空间上执行操作的各个系统调用。
mprotect()系统调用修改一块虚拟内存区域上的保护信息。mlock()和mlockall()系统调用将一块虚拟内存区域锁进物理内存,从而防止它被交换出去。mincore()系统调用让一个进程能够确定一块虚拟内存区域中的分页是否驻留在物理内存中。madvise()系统调用让一个进程能够将其对虚拟内存区域的使用模式报告给内核。
mprotect()
同 mmap() 的 prot 参数。
mlock()
可能是为了速度,也可能是为了防止敏感信息写入磁盘(这样恶意程序没办法从磁盘中读取内容)。
一些电脑的休眠模式会在磁盘上存储当前系统运行状态的副本,不管页面有没有被锁定。
SYNOPSIS
#include <sys/mman.h>
int mlock(const void addr[.len], size_t len);
int mlock2(const void addr[.len], size_t len, unsigned int flags);
int munlock(const void addr[.len], size_t len);
int mlockall(int flags);
int munlockall(void);
如果传给 mlock() 的 addr 不是对齐到页的,那么系统会将其向下舍入到页面的整数倍。addr + len 则是向上舍入。例如,在一个分页大小为 4096 字节的系统上,mlock(2000, 4000) 调用会将 0 到 8191 之间的字节锁住(2000 + 4000 向上舍入到 8192)。
通过查看 Linux 特有的 /proc/PID/status 文件中的 VmLck 条目能够找出一个进程当前已经锁住的内存数量。
RLIMIT_MEMLOCK 资源限制
非特权进程能够锁定的页面数量是有限的。RLIMIT_MEMLOCK 资源限制描述了能够锁定的字节数,实际检查时会将其向下舍入到页面的整数倍。其默认值 32768 对应 8 个页面。
- 对于
mmap()、mlock()、mlockall(),RLIMIT_MEMLOCK限制了一个非特权进程可以锁定的字节数(向下舍入到页),它们共享这个限制。 - 对于
shmctl()的 SHM_LOCK 操作,RLIMIT_MEMLOCK限制了同一个真实用户 ID 能够锁定的字节数(向下舍入到页)。这是因为 System V 共享内存作为一种内核资源,在进程退出之后仍然存在,除非被显式删除。
内存锁的特性
- 内存锁不会跨越
fork()或exec(),只对当前进程生效。 - 内存锁是进程自己的属性,
mlock()和mlockall()在返回前就已经将指定的页面锁定到内存中来。 shmctl的SHM_LOCK操作只是修改了共享内存段的属性,需要有进程访问共享内存段才能将页面锁定到内存中来。而且即便所有附加该段的进程都退出,共享内存段的锁定也不会自动解除。
lockall()
用来锁定进程的所有页面。标志参数可以指定当前,或者未来所有页面。
mincore()
检查给定范围内的页面是否在内存中。这个函数返回的信息只表示当前状态,在之后用这些页面的时候,页面的状态有可能会发生变化。
madvise()
此函数用来告知内核应用程序接下来要使用给定区域内存的方式,这可能会影响内核对内存的管理方式。举例:
MADV_NORMAL:默认。MADV_RANDOM:随机访存,没有必要预取。MADV_SEQUENTIAL:顺序访存。MADV_DONTNEED:不同系统的实现不同。在 Linux 上,MAP_SHARED分页用此通知的效果因架构而异;MAP_PRIVATE则会显式丢弃这些分页,这些分页下一次被加载的时候就会被重新初始化——在私有匿名分页的情况下,会被初始化为 0。这是一种懒清零的方法,但是不可移植。
SUSv3 还规定了一个 posix_madvise() 来实现相似的功能,但是同时规定该函数不能破坏程序原来的语义,比如 Linux 上 MADV_DONTNEED 的效果和 POSIX_MADV_DONTNEED 不同,前者有破坏性。