ファイルマップ

May 04, 2018

ファイルマップとは

通常プロセスがファイルにアクセスする際は、ファイルを開いた後に read() write() lseek() 等のシステムコールを用いる。
これらに加え、Linux にはファイル領域を仮想アドレス空間上にマップする、ファイルマップという機能が存在する。

マップしたファイルには通常のメモリアクセスと同様の方法でアクセスすることが可能であり、アクセスした領域の変更については、所定のタイミングでストレージデバイス上に反映される。

実際に使ってみる

実際にファイルマップ機能を用いるプログラムを作成し、本当にメモリ上にマップできているのか、中身にアクセスできるのかを確認する。
確認する項目は以下。

  • ファイルが仮想アドレス空間にマップされていること
  • マップされた領域の読み出しにより、ファイルを読み出せること
  • マップされた領域の書き込みにより、ファイルに書き込めること

まずは、testfile を作成する。

$ echo hello >testfile  
$ cat testfile   
hello  

ここで、以下のようなプログラムを作成する。

  1. /proc/[pid]/maps の出力を表示
  2. testfile を開く
  3. ファイルを mmap() によりメモリ空間にマップする
  4. /proc/[pid]/maps の出力を再度表示
  5. マップされた領域のデータを読み出して出力する
  6. マップされた領域のデータを書き換える

実際のコードは以下。

filemap.c
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <unistd.h>  
#include <sys/mman.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <err.h>  
  
#define BUFFER_SIZE 1000  
#define ALLOC_SIZE (100*1024*1024)  
  
static char command[BUFFER_SIZE];  
static char file_contents[BUFFER_SIZE];  
static char overwrite_data[] = "HELLO";  
  
int main(void) {  
  pid_t pid;  
  
  pid = getpid();  
  snprintf(command, BUFFER_SIZE, "cat /proc/%d/maps", pid);  
  
  puts("====== memory map before file mapping ======");  
  fflush(stdout);  
  system(command);  
  
  int fd;  
  fd = open("testfile", O_RDWR);  
  if (fd == -1)  
    err(EXIT_FAILURE, "open() failed.");  
    
  char * file_contents;  
  file_contents = mmap(NULL, ALLOC_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);  
  
  if (file_contents == (void *) -1) {  
    warn("mmap() failed.");  
    goto close_file;  
  }  
  
  puts("");  
  printf("====== succeeded to map file: address = %p; size = %x ======\n", file_contents, ALLOC_SIZE);  
  
  puts("");  
  puts("====== memory map after file mapping ======");  
  fflush(stdout);  
  system(command);  
  
  puts("");  
  printf("====== file contents before overwriting mapped region: %s", file_contents);  
    
  memcpy(file_contents, overwrite_data, strlen(overwrite_data));  
  
  puts("");  
  printf("====== file contents after overwriting mapped region: %s", file_contents);  
  
  if(munmap(file_contents, ALLOC_SIZE) == -1)  
    warn("munmap() failed.");  
  
 close_file:  
  if(close(fd) == -1)  
    warn("close() failed.");  
  
  exit(EXIT_SUCCESS);  
}  

実行してみる。

$ ./filemap   
====== memory map before file mapping ======  
00400000-00401000 r-xp 00000000 ca:01 524418                             /home/ec2-user/LinuxBootCamp/Chapter5/filemap  
00601000-00602000 rw-p 00001000 ca:01 524418                             /home/ec2-user/LinuxBootCamp/Chapter5/filemap  
7f4966e9b000-7f4967055000 r-xp 00000000 ca:01 2608                       /lib64/libc-2.17.so  
7f4967055000-7f4967254000 ---p 001ba000 ca:01 2608                       /lib64/libc-2.17.so  
7f4967254000-7f4967258000 r--p 001b9000 ca:01 2608                       /lib64/libc-2.17.so  
7f4967258000-7f496725a000 rw-p 001bd000 ca:01 2608                       /lib64/libc-2.17.so  
7f496725a000-7f496725f000 rw-p 00000000 00:00 0   
7f496725f000-7f4967280000 r-xp 00000000 ca:01 2601                       /lib64/ld-2.17.so  
7f4967476000-7f4967479000 rw-p 00000000 00:00 0   
7f496747e000-7f4967480000 rw-p 00000000 00:00 0   
7f4967480000-7f4967481000 r--p 00021000 ca:01 2601                       /lib64/ld-2.17.so  
7f4967481000-7f4967482000 rw-p 00022000 ca:01 2601                       /lib64/ld-2.17.so  
7f4967482000-7f4967483000 rw-p 00000000 00:00 0   
7ffd72b23000-7ffd72b44000 rw-p 00000000 00:00 0                          [stack]  
7ffd72b71000-7ffd72b74000 r--p 00000000 00:00 0                          [vvar]  
7ffd72b74000-7ffd72b76000 r-xp 00000000 00:00 0                          [vdso]  
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]  
  
====== succeeded to map file: address = 0x7f4960a9b000; size = 6400000 ======  
  
====== memory map after file mapping ======  
00400000-00401000 r-xp 00000000 ca:01 524418                             /home/ec2-user/LinuxBootCamp/Chapter5/filemap  
00601000-00602000 rw-p 00001000 ca:01 524418                             /home/ec2-user/LinuxBootCamp/Chapter5/filemap  
7f4960a9b000-7f4966e9b000 rw-s 00000000 ca:01 524417                     /home/ec2-user/LinuxBootCamp/Chapter5/testfile  
7f4966e9b000-7f4967055000 r-xp 00000000 ca:01 2608                       /lib64/libc-2.17.so  
7f4967055000-7f4967254000 ---p 001ba000 ca:01 2608                       /lib64/libc-2.17.so  
7f4967254000-7f4967258000 r--p 001b9000 ca:01 2608                       /lib64/libc-2.17.so  
7f4967258000-7f496725a000 rw-p 001bd000 ca:01 2608                       /lib64/libc-2.17.so  
7f496725a000-7f496725f000 rw-p 00000000 00:00 0   
7f496725f000-7f4967280000 r-xp 00000000 ca:01 2601                       /lib64/ld-2.17.so  
7f4967476000-7f4967479000 rw-p 00000000 00:00 0   
7f496747e000-7f4967480000 rw-p 00000000 00:00 0   
7f4967480000-7f4967481000 r--p 00021000 ca:01 2601                       /lib64/ld-2.17.so  
7f4967481000-7f4967482000 rw-p 00022000 ca:01 2601                       /lib64/ld-2.17.so  
7f4967482000-7f4967483000 rw-p 00000000 00:00 0   
7ffd72b23000-7ffd72b44000 rw-p 00000000 00:00 0                          [stack]  
7ffd72b71000-7ffd72b74000 r--p 00000000 00:00 0                          [vvar]  
7ffd72b74000-7ffd72b76000 r-xp 00000000 00:00 0                          [vdso]  
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]  
  
====== file contents before overwriting mapped region: hello  
  
====== file contents after overwriting mapped region: HELLO  

まず、“7f4960a9b000-7f4966e9b000 rw-s 00000000 ca:01 524417 /home/ec2-user/LinuxBootCamp/Chapter5/testfile” の行から、testfile のデータが 0x7f4960a9b000 にマップされたことがわかる。
また、memcpy() により、メモリの内容が HELLO に変更されている。
実際にファイルの中身を確認すると、変更が反映されていることが確認できる。

$ cat testfile   
HELLO  

 © 2023, Dealing with Ambiguity