Linux内核深度解析
上QQ阅读APP看书,第一时间看更新

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,否则返回负的错误号。