《Linux/UNIX系统编程手册》第50章 虚拟内存操作

关键词:mprotect()、mlock()、mlockall()、mincore()、madvise()等等。

mprotect()修改一块虚拟内存区域上的保护信息。

mlock()和mlockall()将一块虚拟内存区域锁进物理内存,从而防止它被交换出去。

mincore()让一个进程能够确定一块虚拟内存区域中的分页是否驻留在物理内存中。

madvise()让一个进程能够将其对虚拟内存区域中的使用模式报告给内核。

1. 改变内存保护:mprotect()

#include <sys/mman.h>
int mprotect(void *addr, size_t length, int prot);
    Returns 0 on success, or –1 on error

addr的取值必须是系统分页大小的整数倍。

length会被向上舍入到系统分页大小的下一个整数倍。

prot是一个位掩码,指定了这块内存区域上的心保护属性,取值可以是PROT_NONE、PROT_READ、PROT_WRITE、PROT_EXEC中一个或多个取OR。

如果一个进程在访问一块内存区域时违背了内存保护,那么内核就会向该进程发送一个SIGSEGV信号。

mprotect()一个用途是修改原先通过mmap()创建的内存映射区域上的保护

下面示例创建一块内存,然后对不同区域进行prot修改:

#define _BSD_SOURCE         /* Get MAP_ANONYMOUS definition from <sys/mman.h> */
#include <sys/mman.h>
#include "tlpi_hdr.h"

#define LEN (1024 * 1024)

#define SHELL_FMT "cat /proc/%ld/maps | grep zero"
#define CMD_SIZE (sizeof(SHELL_FMT) + 20)
                            /* Allow extra space for integer string */

int
main(int argc, char *argv[])
{
    char cmd[CMD_SIZE];
    char *addr;

    /* Create an anonymous mapping with all access denied */

    addr = mmap(NULL, LEN, PROT_NONE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED)
        errExit("mmap");

    /* Display line from /proc/self/maps corresponding to mapping */

    printf("Before mprotect()
");
    snprintf(cmd, CMD_SIZE, SHELL_FMT, (long) getpid());
    system(cmd);

    /* Change protection on memory to allow read and write access */

    if (mprotect(addr, LEN/8, PROT_READ) == -1)
        errExit("mprotect");
    if (mprotect(addr+LEN*1/8, LEN/8, PROT_WRITE) == -1)
        errExit("mprotect");
    if (mprotect(addr+LEN*2/8, LEN/8, PROT_EXEC) == -1)
        errExit("mprotect");
    if (mprotect(addr+LEN*3/8, LEN/8, PROT_READ | PROT_WRITE) == -1)
        errExit("mprotect");
    if (mprotect(addr+LEN*4/8, LEN/8, PROT_READ | PROT_EXEC) == -1)
        errExit("mprotect");
    if (mprotect(addr+LEN*5/8, LEN/8, PROT_EXEC | PROT_WRITE) == -1)
        errExit("mprotect");
    if (mprotect(addr+LEN*6/8, LEN/8, PROT_READ | PROT_WRITE | PROT_EXEC) == -1)
        errExit("mprotect");
    if (mprotect(addr+LEN*6/8, LEN/8, PROT_NONE) == -1)
        errExit("mprotect");

    printf("After mprotect()
");
    system(cmd);                /* Review protection via /proc/self/maps */

    exit(EXIT_SUCCESS);
}

执行结果:

Before mprotect()
7f9c7225d000-7f9c7235d000 ---s 00000000 00:05 11352269                   /dev/zero (deleted)
After mprotect()
7f9c7225d000-7f9c7227d000 r--s 00000000 00:05 11352269                   /dev/zero (deleted)
7f9c7227d000-7f9c7229d000 -w-s 00020000 00:05 11352269                   /dev/zero (deleted)
7f9c7229d000-7f9c722bd000 --xs 00040000 00:05 11352269                   /dev/zero (deleted)
7f9c722bd000-7f9c722dd000 rw-s 00060000 00:05 11352269                   /dev/zero (deleted)
7f9c722dd000-7f9c722fd000 r-xs 00080000 00:05 11352269                   /dev/zero (deleted)
7f9c722fd000-7f9c7231d000 -wxs 000a0000 00:05 11352269                   /dev/zero (deleted)
7f9c7231d000-7f9c7235d000 ---s 000c0000 00:05 11352269                   /dev/zero (deleted)

2. 内存锁:mlock()和mlockall()

将进程中虚拟内存部分或者全部锁进内存以确保他们总是位于物理内存中,有两个用途:一是提高性能,对被锁住的虚拟内存提前分配了对应物理内存,不会使用时发生缺页故障而发生延迟。而是安全,敏感数据的内存分区永远不会被交换出去。

给内存加锁有如下限制:

  • 特权进程能够锁住的内存数量是没有限制的,即RLIMIT_MEMLOCK会被忽略。
  • 非特权进程能够锁住的内存数量上限由软限制RLIMIT_MEMLOCK定义。

RLIMIT_MEMLOCK限制影响:

  • mlock()和mlockall()
  • mmap() MAP_LOCKED标记,该标记用来映射被创建时将内存映射锁进内存。
  • shmctl() SHM_LOCK操作,该操作用来给System V共享内存加锁。

对mlock()、mlockall()、mmap() MAP_LOCKED操作来讲,RLIMIT_MEMLOCK定义了一个进程级别的限制,它限制了一个进程的虚拟地址空间中能够被锁进内存的字节数。

对于shmctl() SHM_LOCK操作来讲,RLIMIT_MEMLOCK定义了一个用户级别的限制,它限制了这个进程的真实用户ID在共享内存段中能够锁住的字节数。

#include <sys/mman.h>
int mlock(void *addr, size_t length);
int munlock(void *addr, size_t length);
    Both return 0 on success, or –1 on error

mlock()系统调用会锁住调用进程的虚拟地址空间中add开始长度为length字节区域中所有分页。addr无需分页对齐:内核会从addr下面的下一个分页边界开始锁住分页。

mlock()调用成功之后就能确保指定区域中的分页会被锁住并驻留在物理内存中。

munlock()系统调用执行与mlock()相反。

除了显式地使用munlock()之外,内存锁在下列情况下会被自动删除:

  • 在进程终止时。
  • 当被锁住的分页通过munmap()被解除映射时。
  • 当被锁住的分页被使用mmap() MAP_FIXED标记的映射覆盖时。
#include <sys/mman.h>
int mlockall(int flags);
int munlockall(void);
    Both return 0 on success, or –1 on error

mlockall()和munlockall()给它占据的所有内存加锁和解锁。

MCL_CURRENT:将调用进程的虚拟地址空间中当前所有映射的分页锁进内存,包括当前为程序文本段、数据段、内存映射以及栈分配的所有分页。

MCL_FUTURE:将后续映射进调用进程的虚拟地址空间的所有分页锁进内存。

3. 确定内存驻留性:mincore()

 mincore()是内存加锁系统调用的补充,它报告在一个虚拟地址范围中哪些分页当前驻留在RAM中,因此在访问这些分页时也不会导致分页故障。

#define _BSD_SOURCE /* Or: #define _SVID_SOURCE */
#include <sys/mman.h>
int mincore(void *addr, size_t length, unsigned char *vec);
    Returns 0 on success, or –1 on error

4. 建议后续的内存使用模式:madvise()

madvise()通过通知内核调用进程对起始地址为addr长度为length字节的范围之内分页的可能使用情况来提升应用程序的性能。

内核可能会使用这种信息来提升在分页之下的文件映射上执行的IO效率。

#define _BSD_SOURCE
#include <sys/mman.h>
int madvise(void *addr, size_t length, int advice);
    Returns 0 on success, or –1 on error

MADV_NORMAL:默认行为。

MADV_RANDOM:这个区域中的分页会被随机访问,这样预先读将不会带来任何好处,因此内核在每次读取时所取出的数据量应该尽可能少。

MADV_SEQUWNTIAL:这个范围内分页只会被方位词,并且是顺序访问,因此内核可以激进地预先读,并且分页在被访问之后就可以将其释放了。

MADV_WILLNEDD:预先读取这个区域中的分页以备将来的访问之需。

MADV_DONTNEED:调用进程不再要求这个区域中的分页主流在内存中。

5. 小结

mprotect()修改一块虚拟内存区域上的保护。

mlock()和mlockall()将一个进程的虚拟地址空间中的部分或全部分别锁进物理内存。

mincore()报告一块虚拟内存区域中哪些分页当前驻留在物理内存中。

madvise()和posix_madvise()允许一个进程将其预期的内存使用模式报告给内核。

原文地址:https://www.cnblogs.com/arnoldlu/p/12551008.html