Open Source Web Development Tutorials - Dev Shed
mmap()を利用する高度なファイルI/O
(2009/03/18公開)
mmap()を利用する高度なファイルI/O
これは全7回でLinux I/Oファイルのシステムコールを解説する連載の第4回です。今回は、mmap()の使い方を学習していきましょう。内容は、ロバート・ラブ著『Linux System Programming: Talking Directly to the Kernel and C Library(Linuxシステムプログラミング-カーネルおよびCライブラリへ直接話しかける:仮題)』第4章からの抜粋です。(原著はオライリーより2007年に出版、ISBN: 0596009585(Copyright 2007 O'Reilly Media, Inc.))禁無断転載。出版社の許可を得て使用しています。書店あるいはオライリーメディアから直接購入できます。
マッピング例
以下はmmap()を利用するシンプルなプログラムの一例です。標準出力を選択して、ファイルを印刷します。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
int main (int argc, char *argv[])
{
struct stat sb;
off_t len;
char *p;
int fd;
if (argc < 2) {
fprintf (stderr, "usage:
%s <file>\n", argv[0]);
return 1;
}
fd = open (argv[1], O_RDONLY);
if (fd == -1) {
perror ("open");
return 1;
}
if (fstat (fd, &sb) == -1) {
perror ("fstat");
return 1;
}
if (!S_ISREG (sb.st_mode)) {
fprintf (stderr, "%s is not a file\n", argv[1]);
return 1;
}
p = mmap (0, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
perror ("mmap");
return 1;
}
if (close (fd) == -1) {
perror ("close");
return 1;
}
for (len = 0; len < sb.st_size; len++)
putchar (p[len]);
if (munmap (p, sb.st_size) == -1) {
perror ("munmap");
return 1;
}
return 0;
}
上記のプログラム例中、唯一見慣れないシステムコールはfstat()だと思いますが、これは第7章で取り上げます。この時点で知っておいてほしいのは、fstat()がファイルに関する情報を戻すことです。S_ISREG()マクロによってこの情報の一部をチェックできるので、マッピングする前、ファイルが(デバイスファイルやディレクトリではなく)通常ファイルであることを確認できます。非通常ファイルをマッピングしたときの動作は、システム内のデバイスによって異なります。mmapできるデバイスファイルもある一方、mmapできず、errnoとしてEACCESSが示される非通常ファイルもあります。
上例の残りの部分は簡単に理解できますね。引数としてファイルの名称を渡します。ファイルを開き、通常ファイルであることを確認し、マッピングし、閉じ、バイト単位の標準出力で印刷し、最後にメモリからファイルのマッピングを削除します。
mmap()の長所
mmap()を利用するファイル操作には、標準のread()やwrite()システムコールを利用する場合と比べて、以下のような利点があります。
・read()やwrite()システムコールの場合、データはユーザースペースのバッファに対してコピーを実行しなくてはなりませんが、メモリにマッピングされたファイルの読み出しや書き込みには、そのような外部コピーが発生しません
・メモリにマッピングされたファイルの読み出しや書き込みは、なんらかのページフォールトを除き、システムコールや文脈切り替えのオーバーヘッドを生じません。メモリへのアクセスと同じシンプルなプロセスです
・同一のオブジェクトを複数のプロセスがメモリにマッピングする場合、すべてのプロセス間でデータが共有されます。読み出し専用マッピングと共有書き込み可能マッピングでは、全体が共有されます。プライベート書き込み可能マッピングでは、not-yet-COW(copy-on-write:コピーオンライト)ページが共有されます
・マッピング周辺の探索にかかわるポインタ操作はわずかです。lseek()システムコールの必要はありません
以上のような理由から、多くのアプリケーションで、mmap()は賢い選択です。
mmap()の短所
mmap()を利用する場合、以下の点に留意しましょう。
・メモリマッピングは、常に、ページサイズの整数です。したがって、バッキングファイルのサイズとページの整数の差は、スラックスペースとして「消費」されます。小さいファイルの場合には、マッピングによって消費される割合がかなり大きくなる可能性があります。例えば、ページが4KBの場合、7バイトのマッピングを実行すると、結果的に4,089バイトが消費されます
・メモリマッピングは、そのプロセスのアドレス空間に収まらなくてはなりません。アドレス空間が32ビットの場合、さまざまなサイズのマッピングの多くがアドレス空間を断片化する結果となる可能性があり、大きな空き隣接領域を見つけるのが難しくなります。もちろん、アドレス空間が64ビットの場合は、この問題はそれほど目立ちません
・メモリマッピングの生成と保持、それに関連するカーネル内のデータ構造から、オーバーヘッドが発生します。そのようなオーバーヘッドは、特に大型ファイルや頻繁にアクセスするファイルの場合、前節で述べた二重コピーを排除する方法で未然に防止できます
これらの理由から、mmap()のメリットが最大限に発揮されるのは、大型のファイルをマッピングする場合(マッピング全体と比較して、消費されるスラックスペースの割合が小さい)、マッピングするファイルの合計サイズがページサイズに等分できる場合(消費されるスラックスペースがない)です。
Copyright © 2008 Ziff Davis Enterprise, Inc.
Originally appearing in the U.S. Edition of Dev Shed. All Rights Reserved.








