mmap相关API

概述

mmap()调用进程的虚拟进程空间中一段新的内存映射。

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);

mmap(2) — Linux manual page

用途

变更的可见性 映射类型  
  文件 匿名
私有 根据文件内容初始化内存 内存分配
共享 内存映射I/O,进程间共享内存(IPC) 进程间共享内存(IPC)

文件映射

创建步骤

//1. 打开使用的文件
fd = open(argv[1], O_RDONLY);
//2.获取文件信息,文件大小
fstat(fd, &sb);
//3. 生成内存映射
addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

相关议题

文件权限与内存保护位prot
fd = open(argv[1], O_RDONLY);
...
addr = mmap(NULL, sb.st_size, PROT_WRITE, MAP_SHARED, fd, 0);

mmap抛出 errno=13 Permission denied错误

边界情况

增加一个异常捕获的处理

void signal_handler(int no){

   switch(no){
   case 11:
      printf("get SIGSEGV\n");
      break;
   case 7:
      printf("get SIGBUS\n");
      break;
   default:
      printf("get %d\n", no);
      break;
   }
    exit(-1);
}
# ls -l dax.file
-rw-r--r--. 1 root root 7 Jul 18 17:51 dax.file
  • SIGSEGV

访问超过mmap映射范围

图片来自 The linux programming interface

signal(SIGSEGV, signal_handler);
signal(SIGBUS, signal_handler);
...
addr = mmap(NULL, 6000, PROT_READ, MAP_PRIVATE, fd, 0);
#define ACCESS_OFF  9000
char a = *(addr + ACCESS_OFF);
# ./mmaptest dax.file
addr 16ff5000 size 7
get SIGSEGV
  • SIGBUS

访问mmap映射的范围大于文件大小按页对齐范围部分。

图片来自 The linux programming interface

signal(SIGSEGV, signal_handler);
signal(SIGBUS, signal_handler);
addr = mmap(NULL, 8192, PROT_READ, MAP_PRIVATE, fd, 0);
#define ACCESS_OFF  9000
char a = *(addr + ACCESS_OFF);
# ./mmaptest dax.file
addr 35e2000 size 7
get SIGBUS
  • Hole Punching(文件打洞)

对于稀疏文件如果访问Hole时mmap是否会报错

This file was made by User:SvenTranslation from wiki Sparse file


void printf_size(int fd) { struct stat st; fstat(fd, &st); printf("size %d B(%d B allocate).\n\n", st.st_size, st.st_blocks*512); } ... fd = open(argv[1],O_RDWR); fallocate(fd, 0, 0, 4096); printf_size( fd); addr = mmap(NULL, 4096*10, PROT_WRITE, MAP_SHARED, fd, 0); #define ACCESS_OFF 4096*2 fallocate(fd, 0, 4096*5, 4096); printf_size( fd); *(addr + ACCESS_OFF) = 'j'; printf_size( fd);
# >dax.file
# ./mmaptest dax.file
// 初始化
size 4096 B(4096 B allocate).

# stat dax.file
  File: 'dax.file'
  Size: 0               Blocks: 0          IO Block: 4096   regular empty file
Device: fd00h/64768d    Inode: 69436841    Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)

// 文件打洞  实际分配 8096字节
size 24576 B(8192 B allocate).

# stat dax.file
  File: 'dax.file'
  Size: 24576           Blocks: 16         IO Block: 4096   regular file
Device: fd00h/64768d    Inode: 69436841    Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)

// 写入一个字节, 增加一个IO Block 4096byte
size 24576 B(12288 B allocate).

# stat dax.file
  File: 'dax.file'
  Size: 24576           Blocks: 24         IO Block: 4096   regular file
Device: fd00h/64768d    Inode: 69436841    Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)

这里可以看到创建带hole文件大小 8KB, 对应文件16个 Blocks可以看到stat结构中block= 512B , 16 512B = 8KB, 写入一个字符, 增加8个block 24512B = 12KB

# man fstat
struct stat {
   ...
    blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
   ...
};

匿名映射

可以通过/dev/zero可以创建匿名映射, 不用关联具体文件

open("/dev/zero",O_RDWR);
mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
...

可以通过MAP_ANONYMOUS创建匿名映射, fd位置填写-1`

mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|
MAP_ANONYMOUS, -1, 0);

调整现有映射

mremap(),传入要进行扩展内存映射的地址, 并增加_GNU_SOURCE的宏定义, Linux特有

#define _GNU_SOURCE
...
char *addr = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE,
 MAP_SHARED|MAP_ANONYMOUS, -1, 0);

char *new_addr = mremap(addr, sizeof(int), sizeof(int)*2,
 MREMAP_MAYMOVE);

创建非线性映射

remap_file_pages() 可以调整文件分页在内存中出现的顺序, 其目的是为了能够有效地映射大型文件放入有限的32位虚拟地址空间(例如数据库)中.

#define _GNU_SOURCE
...
fd = open("/tmp/tfile", O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);

pageSize = sysconf(_SC_PAGESIZE);

addr = mmap(0, 3 * pageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
/* The three pages of the file -- 0 1 2 -- are currently mapped
       linearly. Now we rearrange the mapping to 2 1 0. */
remap_file_pages(addr, pageSize, 0, 2, 0);
remap_file_pages(addr + 2 * pageSize, pageSize, 0, 0, 0);
...

在早先版本中,不创建VMA结构,后续版本是后续处理和mmap类似可以通过/proc/$PID/maps 看到新创建的VMA, 可以看到调用了两次remap_file_pages创建出两个的VMA.

# cat /proc/8923/maps
...
7f7f987e5000-7f7f987e6000 rw-s 00002000 fd:00 69436841  /root/src/mmap/dax.file
7f7f987e6000-7f7f987e7000 rw-s 00001000 fd:00 69436841  /root/src/mmap/dax.file
7f7f987e7000-7f7f987e8000 rw-s 00000000 fd:00 69436841  /root/src/mmap/dax.file

源码可以看一下:

/*
 * Emulation of deprecated remap_file_pages() syscall.
 */
SYSCALL_DEFINE5(remap_file_pages, unsigned long, start, unsigned long, size,
unsigned long, prot, unsigned long, pgoff, unsigned long, flags)
{
...
   file = get_file(vma->vm_file);
   ret = do_mmap_pgoff(vma->vm_file, start, size,
         prot, flags, pgoff, &populate, NULL); 
         ...
    }

do_mmap_pgoff -> do_mmapmmap的 调用链vm_mmap_pgoff->do_mmap_pgoff->do_mmap是一致的

过度使用内存overcommit

进程拥有内存是否可以大于实际可用的标识

0: 需要计算
1: 总是允许
2: 禁止使用

看下具体设置

# sysctl -a|grep overcommit
vm.overcommit_memory = 0
vm.overcommit_ratio = 50

0, 需要运算

公式: CommitLimit = (Physical RAM * vm.overcommit_ratio / 100) + Swap


[root@centosgpt mmap]# cat /proc/meminfo MemTotal: 481900 kB ... SwapTotal: 5195316 kB

CommitLimit = (481900 * 50 / 100 ) + 5195316 = 5436266

验证一下,差 2K,Commited_AS是已经申请内存大小

# grep -i commit /proc/meminfo
CommitLimit:     5436264 kB
Committed_AS:     853844 kB

oom

如果设置了overcommit, 当系统内存不能满足现有进程需要时, oom出来按照规则清理掉部分进程。

涉及的两个文件,oom killer查看进程的 oom_score, 分数越大的先杀掉, 用户可以通过调整oom_adj(范围 [-17, +15]),降低比较中要进程的值,保证正常运行.

/proc/$PID/oom_score
/proc/$PID/oom_adj

DAX

针对NVM新兴非易失性存储,容量大,断电内容不消失, 目前Intel推出的DCPMM已经在腾讯云的Redis上使用.

可以看下Red hat的CONFIGURING PERSISTENT MEMORY FOR FILE SYSTEM DIRECT ACCESS上, 目前是文件系统方式使用

# mkfs -t xfs /dev/pmem0
# mount -o dax /dev/pmem0 /mnt/pmem/

参考及引用

用mmap接口访问文件时边界问题会导致的两个错误
[2/2] mm: replace remap_file_pages() syscall with emulation
remap_file_pages(2) — Linux manual page
Linux 的 OOM Killer 机制分析
理解和配置 Linux 下的 OOM Killer
Linux Kernel中AEP的现状和发展
Direct Access for files
视频基于英特尔傲腾数据中心级持久内存的 Redis 介绍

Be First to Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注