• 作成:

mmapというファイルやデバイスをメモリーにマップするシステムコールの解説

選択3: mmap

mmapについて説明しなさい。その際に必ずanonymous メモリーについて言及すること。

概要

mmapとは, ファイルやデバイスをメモリーにマップするシステムコールです.

一応mmapはPOSIXに含まれているシステムコールです. しかし, POSIXに定義されている動作はごく一部で, 特にflags引数に指定できるのはMAP_FIXED, MAP_PRIVATE, MAP_SHAREDの3つしかなく, 無名ページもサポートしていません. それぞれのUNIXが独自に拡張しており, ゆるい共通APIを持っていますが厳格な規格にはなっていないようです.

mmapの引数

mmapの引数を軽く説明します.

  • void* addr: カーネルがメモリのどの位置にアドレスを配置するかヒントを設定できる. 通常NULLで動かす
  • size_t length: 確保するページのサイズ
  • int prot: ページが実行可能, 書き込み可能, 読み込み可能かを論理和でモード指定する
  • int flags: 色々な設定を論理和で行う
  • int fd: ファイルディスクリプタ
  • off_t offset: ファイルをどの部分から読み込むかのオフセット

flagsの意味のある非推奨ではないフラグ

flagsには互換性のために現在は使われなくなったり, 非推奨になったフラグがとても多いため, Manに書かれているもので非推奨ではない, 現在使う意義のあるものだけを抽出してみます.

  • MAP_SHARED: マッピングを共有して, 更新がファイルをマッピングしている他のプロセスに見えるようになる. マップ元のファイルが変更されるとマップ領域も更新される
  • MAP_PRIVATE: マッピングをプライベートにして, 同じファイルをマッピングしている他のプロセスに更新が見えなくなる. マップ元のファイルが変更されてもマップ領域が変更されるかは規定されていない
  • MAP_ANONYMOUS: マッピングがファイルと関連付けされなくなる
  • MAP_GROWSDOWN: マッピングをメモリ上で逆順に行う
  • MAP_HUGETLB: 大きいページサイズを使用する
  • MAP_LOCKED: メモリがスワップ領域にページングされるのを防ぐ
  • MAP_NORESERVE: スワップ空間の予約を行わない. これを選択した場合, 書き込み時に物理メモリに空きがないとSIGSEGVを受け取ることがある
  • MAP_POPULATE: ファイルマッピングの場合ファイルが先読みされるので, アクセス時にページフォールトしなくなる
  • MAP_UNINITIALIZED: 無名ページのクリアを行わない. カーネルの設定で有効になっていないと効果を発揮しない. セキュリティを犠牲にパフォーマンスが向上する

ファイルマッピングと無名マッピング

引数flagsMAP_ANONYMOUSを指定せずに, 引数fdにファイルオープン関数openの返り値を渡した場合, mmapはファイルマッピングモードとなり, SHAREDモードの場合マッピングされた領域に書き込みされた場合ファイルを更新します. 引数offsetはファイルを何処から読み込むというオフセットに使われます.

引数flagsMAP_ANONYMOUSを論理和で指定した場合, mmapはファイルを使わずに無名マッピングモードになり, Linuxの場合fdoffsetは無視されます. しかし, 他の実装はMAP_ANONYMOUSを使う場合fd-1にすることを要求する可能性があるため, 移植性のためfdには-1を指定するべきでしょう. offsetはどうするべきか書いてなかったのですが多分0で良いでしょう. この無名マッピングモードでマップされたメモリ領域をanonymous メモリーと呼ぶことがあります.

SHAREDモードとPRIVATEモード

MAP_SHAREDを指定するとmmapはSHAREDモードとなり, そのマッピングに対する更新は同じファイルをマッピングしている他のプロセスにも見えるようになります. これは簡易的なプロセス間通信に使うことができます.

MAP_PRIVATEを使うと, マッピングはそれぞれのプロセス間で独立します. forkしたりしてプロセスを分けると, 更新は共有されないということです. かと言って全てがコピーされて複製されるわけではなく, コピーオンライトを使って, 変更が行われたときのみコピーされます.

mmapを使ってabcと書かれているファイルのbをdに変えるサンプルコード

manにかかれていたサンプルコードはエラー処理などが長かったので簡単なサンプルコードを書きました.

C99です.

#include <fcntl.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>

int main() {
    int fd = open("abc.txt", O_RDWR);
    struct stat stat_buf;
    fstat(fd, &stat_buf);
    char* addr = mmap(NULL, stat_buf.st_size, PROT_WRITE, MAP_SHARED, fd, 0);
    addr[1] = 'd';
    return 0;
}

mallocはどのようにmmapを使っているか

mallocの著名な実装は, メモリ領域を確保するためにmmapの無名マッピングモードを使っています.

glibc-2.26の/malloc/malloc.cを見てみました.

以下のマクロが定義されて使われていました.

#define MMAP(addr, size, prot, flags) \
 __mmap((addr), (size), (prot), (flags)|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0)

forkした後, 他のプロセスがページ内容を変更した時に, それぞれのメモリ領域を独立させる必要があるので, 匿名目マッピングモードかつPRIVATEモードになっています.

msync

msyncはファイルをマップしたメモリーと同期させるシステムコールです.

これも一応POSIXにあります.

mmapでマップされたファイルの更新は非同期に行われることがあるので, これを使えば同期的に行うことが可能です.

munmap

mmapでのマップを解除します.

これも一応POSIXにあります.

プロセスが終了した時に自動的にアンマップされるので, メモリ管理を提供したりするプログラムを書く場合以外は気にしなくても良さそうですね.

解除された領域にアクセスした場合SIGSEGVが発生します.

参考文献