3.4.1 应用编程接口
内存管理子系统提供了以下常用的系统调用。
(1)mmap()用来创建内存映射。
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
(2)mremap()用来扩大或缩小已经存在的内存映射,可能同时移动。
void *mremap(void *old_address, size_t old_size, size_t new_size, int flags, ... /* void *new_address */);
(3)munmap()用来删除内存映射。
int munmap(void *addr, size_t length);
(4)brk()用来设置堆的上界。
int brk(void *addr);
(5)remap_file_pages()用来创建非线性的文件映射,即文件区间和虚拟地址空间之间的映射不是线性关系,现在被废弃了。
(6)mprotect()用来设置虚拟内存区域的访问权限。
int mprotect(void *addr, size_t len, int prot);
(7)madvise()用来向内核提出内存使用的建议,应用程序告诉内核期望怎样使用指定的虚拟内存区域,以便内核可以选择合适的预读和缓存技术。
int madvise(void *addr, size_t length, int advice);
在内核空间中可以使用以下两个函数。
(1)remap_pfn_range把内存的物理页映射到进程的虚拟地址空间,这个函数的用处是实现进程和内核共享内存。
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot);
(2)io_remap_pfn_range把外设寄存器的物理地址映射到进程的虚拟地址空间,进程可以直接访问外设寄存器。
int io_remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot);
应用程序通常使用C标准库提供的函数malloc()申请内存。glibc库的内存分配器ptmalloc使用brk或mmap向内核以页为单位申请虚拟内存,然后把页划分成小内存块分配给应用程序。默认的阈值是128KB,如果应用程序申请的内存长度小于阈值,ptmalloc分配器使用brk向内核申请虚拟内存,否则ptmalloc分配器使用mmap向内核申请虚拟内存。
应用程序可以直接使用mmap向内核申请虚拟内存。
1.系统调用mmap()
系统调用mmap()有以下用处。
(1)进程创建匿名的内存映射,把内存的物理页映射到进程的虚拟地址空间。
(2)进程把文件映射到进程的虚拟地址空间,可以像访问内存一样访问文件,不需要调用系统调用read()和write()访问文件,从而避免用户模式和内核模式之间的切换,提高读写文件的速度。
(3)两个进程针对同一个文件创建共享的内存映射,实现共享内存。
函数原型:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数如下。
(1)addr:起始虚拟地址。如果addr是0,内核选择虚拟地址。如果addr不是0,内核把这个参数作为提示,在附近选择虚拟地址。
(2)length:映射的长度,单位是字节。
(3)prot:保护位。
PROT_EXEC:页可执行。
PROT_READ:页可读。
PROT_WRITE:页可写。
PROT_NONE:页不可访问。
(4)flags:标志。常用的标志如下。
MAP_SHARED:共享映射。
MAP_PRIVATE:私有映射。
MAP_ANONYMOUS:匿名映射。
MAP_FIXED:固定映射,不要把参数addr解释为一个提示,映射的起始地址必须是参数addr,必须是页长度的整数倍。
MAP_HUGETLB:使用巨型页。
MAP_LOCKED:把页锁在内存中。
MAP_NORESERVE:不预留物理内存。
MAP_NONBLOCK:不阻塞,和MAP_POPULATE联合使用才有意义,从Linux 2.6.23开始,该标志导致MAP_POPULATE什么都不做。
MAP_POPULATE:填充页表,即分配并且映射到物理页。如果是文件映射,该标志导致预读文件。
(5)fd:文件描述符。仅当创建文件映射的时候,这个参数才有意义。如果是匿名映射,有些实现要求参数fd是−1,可移植的应用程序应该保证参数fd是−1。
(6)offset:偏移,单位是字节,必须是页长度的整数倍。仅当创建文件映射的时候,这个参数才有意义。
返回值:
如果成功,返回起始虚拟地址,否则返回负的错误号。
2.系统调用mprotect()
mprotect()用来设置虚拟内存区域的访问权限。
函数原型:
int mprotect(void *addr, size_t len, int prot);
参数如下。
(1)addr:起始虚拟地址,必须是页长度的整数倍。
(2)len:虚拟内存区域的长度,单位是字节。
(3)prot:保护位。
PROT_EXEC:页可执行。
PROT_READ:页可读。
PROT_WRITE:页可写。
PROT_NONE:页不可访问。
返回值:
如果成功,返回0,否则返回负的错误号。
3.系统调用madvise()
madvise()用来向内核提出内存使用的建议,应用程序告诉内核期望怎样使用指定的虚拟内存区域,以便内核可以选择合适的预读和缓存技术。
函数原型:
int madvise(void *addr, size_t length, int advice);
参数如下。
(1)addr:起始虚拟地址,必须是页长度的整数倍。
(2)length:虚拟内存区域的长度,单位是字节。
(3)advice:建议。
POSIX标准定义的建议值如下。
MADV_NORMAL:不需要特殊处理,这是默认值。
MADV_RANDOM:预期随机访问指定范围的页,预读的用处比较小。
MADV_SEQUENTIAL:预期按照顺序访问指定范围的页,所以可以激进地预读指定范围的页,并且进程在访问页以后很快释放。
MADV_WILLNEED:预期很快就会访问指定范围的页,所以可以预读指定范围的页。
MADV_DONTNEED:预期近期不会访问指定范围的页,即进程已经处理完指定范围的页,内核可以释放相关的资源。
Linux私有的建议值如下。
MADV_REMOVE:进程想要释放指定范围的页和相关的后备存储。
MADV_DONTFORK:在执行fork()的时候从子进程的地址空间删掉指定范围的页。
MADV_DOFORK:取消MADV_DONTFORK,在执行fork()的时候不从子进程的地址空间删掉指定范围的页。
MADV_HWPOISON:毒化指定范围的页,像内存损坏一样处理对指定范围的页的访问。
MADV_MERGEABLE:允许KSM(Kernel Samepage Merging,内核相同页合并)合并数据相同的页。
MADV_UNMERGEABLE:取消MADV_MERGEABLE,不允许合并数据相同的页。
MADV_SOFT_OFFLINE:使指定范围的页软下线,即内存页被保留,但是下一次访问的时候,把数据复制到新的物理页,旧的物理页下线,对进程不可见。这个特性用来测试处理内存错误的代码。
MADV_HUGEPAGE:允许指定范围使用透明巨型页。
MADV_NOHUGEPAGE:不允许指定范围使用透明巨型页。
MADV_DONTDUMP:生成核心转储文件的时候不要包含指定范围的页。
MADV_DODUMP:取消MADV_DONTDUMP,生成核心转储文件的时候包含指定范围的页。
MADV_FREE:从4.5版本开始支持,进程不再需要指定范围的页,内核可以释放这些页,释放可以延迟到内存不足的时候。
返回值:
如果成功,返回0,否则返回负的错误号。