内存保护, mprotect()

mprotect() 改变一定范围内的虚拟内存页的保护方式.

锁定内存 mlock(), mlockall()

因为内核可能会将进程的内存页写到交换分区上, 如果该内存页上有敏感数据的话, 可能 导致它被泄露, 可以给关键的内存区域加锁, 告诉内核不要把这个区域的内存页执行交换 页的操作.

当然了, 如果启用了系统挂起(suspend), 内存中的数据会被拷贝到硬盘上, 无视内存锁.

内存锁在子进程(fork()), 以及执行新的可执行文件(exec()) 时, 都不会被继承.

mlockall() 会锁定进程的所有内存.

检测内存位置, mincore()

mincore() 检查一个区域的内存页是不是位于物理内存中, 如果位于交换分区中, 这个 系统调用不会触发page fault.

以下示例结合了 mlock() 还有mincore()

// Copyright (c) 2018 Xu Shaohua <xushaohua2016@outlook.com>.
// All rights reserved.
// Use of this source is governed by General Public License that can be found
// in the LICENSE file.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

static void displayMinCore(char* addr, size_t length) {
  const long page_size = sysconf(_SC_PAGESIZE);
  const long num_pages = (length + page_size - 1) / page_size;
  unsigned char* vec = malloc(num_pages);
  if (vec == NULL) {
    perror("malloc()");
    exit(EXIT_FAILURE);
  }

  if (mincore(addr, length, vec) == -1) {
    perror("mincore()");
    exit(EXIT_FAILURE);
  }

  for (int i = 0; i < num_pages; i++) {
    if (i % 64 == 0) {
      printf("%s%10p: ", (i == 0) ? "" : "\n", addr + (i * page_size));
    }
    printf("%c", (vec[i] & 1) ? '*' : '.');
  }
  printf("\n");

  free(vec);
}

int main(int argc, char* argv[]) {
  if (argc != 4 || strcmp(argv[1], "--help") == 0) {
    printf("Usage: %s num-pages lock-page-step lock-page-len\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  const long page_size = sysconf(_SC_PAGESIZE);
  if (page_size == -1) {
    perror("sysconf()");
    exit(EXIT_FAILURE);
  }

  const size_t len = atoi(argv[1]) * page_size;
  const long step_size = atoi(argv[2]) * page_size;
  const size_t lock_len = atoi(argv[3]) * page_size;

  char* addr = mmap(NULL, len, PROT_READ, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  if (addr == MAP_FAILED) {
    perror("mmap()");
    exit(EXIT_FAILURE);
  }

  printf("Allocated %ld (%#lx) bytes starting at %p\n",
         (long) len, (unsigned long) len, addr);

  printf("Before mlock:\n");
  displayMinCore(addr, len);

  // Lock pages specified by command line arguments into memory.
  for (int i = 0; i + lock_len <= len; i += step_size) {
    if (mlock(addr +i, lock_len) == -1) {
      perror("mlock()");
      exit(EXIT_FAILURE);
    }
  }

  printf("After mlock:\n");
  displayMinCore(addr, len);

  exit(EXIT_SUCCESS);
}

给内核建议内存的使用策略: madvice()

为了进一步提升内存的性能表现, 可以给内核建议接下来内存页的使用策略, 内核可以使用 这个策略来提升文件映射(file mapping) 的 I/O 性能.

可用的策略有:

  • MADV_NORMAL, 默认的读写策略
  • MADV_RANDOM, 随机读写, 内核只会在每次读取尽可能少的数据,
  • MADV_SEQUENTIAL, 顺序读写, 内核会预读取大量的数据.
  • MADV_WILLNEED, 预先读取这个区域, 以便之后使用. readahead() 这个系统调用也有 类似的效果.
  • MADV_DONTNEED, 告诉内核这个区域的内存已经不再被需要.