システムコールを減らすシステムコール:知ってトクするシステムコール(5)(2/2 ページ)
前回まで、mmap(2)によるコピー処理の高速化について紹介してきた。mmap(2)にはまた、システムコールが呼ばれる回数を削減し、処理速度を高速化するという効果もある。今回は、共有メモリの観点から、その機能を紹介する(編集部)
プロセス間通信:mmap(2)の共有メモリを使う場合
では次に、mmap(2)の共有メモリを使ってデータのやりとりをするソースコードを紹介する。基本的な構造はパイプを使う場合と同じだ。fork()する前にmmap(2)経由で共有メモリを作成し、fork()後に共有メモリを経由してデータのやりとりを行っている。
#include #include <unistd.h> #include <sys/mman.h> #include <stdio.h> int main(void) { int i, rp=1000000; char *s; s = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); *s = '0'; if (0 == fork()) { for (i = 0; i < rp; i++) { while ('0' == *s) ; *s = '0'; } } else { for (i = 0; i < rp; i++) { while ('1' == *s) ; *(s+1) = 'a'; *s = '1'; } } }
これまでの例ではmmap(2)はファイルをマッピングするために利用していたが、フラグにMAP_ANONを指定することで、ファイルをマッピングしない目的で利用できる。MAP_SHAREDで共有マッピングの指定になるので、MAP_ANON | MAP_SHAREDの指定で共有メモリとして利用するという意味になる。この場合、ファイルディスクリプタには-1を指定し、オフセットの値には0を指定する。モードとしてPROT_READ | PROT_WRITEを指定すれば読み書きできる領域になる。
共有メモリのサイズは、ここでは2byteあれば十分なのだが、ページサイズの倍数単位で処理されることになるので、ページサイズである4096byteを指定してある。
まず、time(1)コマンドで実行時間と処理の状況を確認してみる。すると処理速度がパイプを使った場合に比べ、桁違いに速いことが分かる。自発的なコンテキストスイッチの回数は2回と、パイプの例と比べて桁違いに少ない。
% clang ipc_mmap.c % /usr/bin/time -lph ./a.out real 0.09 user 0.09 sys 0.00 740 maximum resident set size 4 average shared memory size 4 average unshared data size 138 average unshared stack size 76 page reclaims 0 page faults 0 swaps 0 block input operations 0 block output operations 0 messages sent 0 messages received 0 signals received 2 voluntary context switches 7 involuntary context switches %
truss(1)でシステムコールの呼び出し回数を比較すると、パイプの場合と比べてmmap(2)システムコールの呼び出し回数が1つ多いだけで、極めてシステムコールの呼び出し回数が少ないことが分かる。パイプにおけるシステムコールは100万と31回だが、mmap(2)を使ったこちらのソースコードではわずか31回だ。このシステムコールの回数の違いが処理性能に現れていることが分かる。
% truss -c ./a.out syscall seconds calls errors fork 0.000033437 1 0 lseek 0.000004469 1 0 mmap 0.000041217 7 0 open 0.000022099 3 1 close 0.000010263 2 0 fstat 0.000010511 1 0 access 0.000007366 1 0 sigprocmask 0.000095760 12 0 munmap 0.000005131 1 0 read 0.000036003 2 0 ------------- ------- ------- 0.000266256 31 1 %
mmap(2)の共有メモリの効果が出やすいように例を作成しているわけだが、これで特性の違いがよく分かったのではないかと思う。
mmap(2)で共有メモリを使う場合の注意点
write(2)/read(2)では、プログラムを組む方はデータのやりとりにおいて基本的には同期やロックなどの処理については考慮する必要がない。データのやりとりはカーネルが処理してくれるので、これを利用するプログラム側ではそのあたりの処理を気にかける必要がない。
一方、mmap(2)で共有メモリを作成して利用する場合には、自分でロックや同期、排他処理を考慮する必要がある。ここで排他制御を実施するために別のシステムコールを呼ぶようなことをすると本末転倒なので、そのあたりにも注意する必要がある。ここに掲載したサンプルでは、簡単にだが排他制御も実施している。複数のプロセスで共有するということになれば、もっとちゃんとした方法を実装する必要がある。
活用次第でさまざまな利益が得られるmmap(2)
いったんマッピングしてしまえば、以降はカーネルに処理を依頼する必要がなく(つまりシステムコールを呼ぶ必要がなく)データの共有ができるmmap(2)の仕組みは、極めて強力なものだ。これまでコピーやデータ共有といった簡単な例しか紹介していないが、それだけでもこの機能の便利さは伝わったのではないかと思う。
なお、mmap(2)はプログラムをメモリにローディングする段階で共有ライブラリを読み込む目的にも利用されている。mmap(2)は性能という面で利益を感じやすい、便利なシステムコールだ。
著者紹介
BSDコンサルティング株式会社取締役/オングス代表取締役
後藤 大地
@ITへの寄稿、MYCOMジャーナルにおけるニュース執筆のほか、アプリケーション開発やシステム構築、『改訂第二版 FreeBSDビギナーズバイブル』『D言語パーフェクトガイド』『UNIX本格マスター 基礎編〜Linux&FreeBSDを使いこなすための第一歩〜』など著書多数。
Copyright © ITmedia, Inc. All Rights Reserved.