連載
» 2003年01月18日 00時00分 公開

全貌を現したLinuxカーネル2.6(第1章):エンタープライズ向けに刷新されたカーネル・コア (2/3)

[山本高志,VA Linux Systemsジャパン]

マルチスレッド対応の強化

vcache

 vcacheは、仮想アドレスと物理ページの対応付けの変更をコールバック関数によって通知する仕組みである。

 後述するfutexの実装に使用されるため、カーネル2.6に追加された。

futex(Fast Userspace Mutexes)

 futexは、伝統的なUNIXカーネルで使われているsleep/wakeupとよく似た同期機構をユーザーランドに対して提供する。

 主にNPTL(Native POSIX Thread Library)などのライブラリの実装に使われるため、アプリケーションから直接利用することはあまりないと思われるが、POSIX ThreadはJavaスレッドの実装などにも用いられており、同期処理を多用するアプリケーションには利益があるだろう。

 futexシステムコールの主な機能はFUTEX_WAITとFUTEX_WAKEである。

  • FUTEX_WAIT
    ユーザー空間のアドレスを引数とし、そのアドレスに対応する待ちキューでスリープする。

  • FUTEX_WAKE
    ユーザー空間のアドレスを引数とし、対応する待ちキューでスリープしているスレッドをウェイクアップする。

 futexシステムコールは、共有メモリ中のアドレスをウェイトポイントとして使用する。しかし、共有メモリがマップされているアドレスはプロセスごとに違うため、仮想アドレスをそのまま使うことはできない。そこで、カーネル内で物理アドレスに変換してから使用するのだが、ここで注意しなければならないのは、対応する物理アドレスはページアウト/ページインなどで容易に変わってしまうことだ。カーネル2.6は、前述のvcacheを利用することでこれを解決している。

 参考までに、NPTLのpthread mutexの実装を簡単に書くと次のようになっている。

  • pthread_mutex_lock:

    1. アトミック命令を使用してロックを試みる
    2. 1でロックが成功したら終了
    3. FUTEX_WAITを使用し、pthread_mutex_tのアドレス上でスリープする
    4. ウェイクアップ後、1に戻る

  • pthread_mutex_unlock:

    1. アトミック命令を使用してアンロック処理を行う
    2. 1の結果、スリープしているスレッドがあるようならFUTEX_WAKEを使用してウェイクアップ処理を行う

 以上のように、競合がない場合はユーザー空間で処理が完結するため、システムコールのオーバーヘッドは実際にスレッド間の競合が発生した場合に限られている。

ネットワーク関連の改良

zerocopy NFS

 NFSサーバがREADリクエストを処理する際、カーネル2.4はページキャッシュからNFS送信用バッファへいったんメモリコピーを行う。それをプロトコル層でさらにソケットバッファにコピーし、そのうえでネットワークドライバが送信処理を行っていた。

 カーネル2.6では、ページキャッシュに使われているページを直接プロトコル層に渡し、可能であればそのままネットワークドライバまで渡すように改良された。これにより、無駄なコピー処理のオーバーヘッドが削減されるのはもちろん、プロセッサ内部のキャッシュメモリからほかの有用なデータが追い出されることが減ると期待できる。

epoll

 select/pollのインターフェイスは、大規模なWebサーバなど、多くのディスクリプタを監視する用途では効率が悪いため、Solarisの/dev/pollやFreeBSDのkqueueなど、非標準な代替インターフェイスが用意されてきた。

 epollはそれらと同様に、select/pollの代替となるべく追加されたシステムコールである。当初のパッチはSolarisと同じようにデバイスファイルを通してアクセスするインターフェイスであったが、Linus氏がこれを嫌ったため現在のシステムコールという形になった。

 epollはepoll_create、epoll_ctl、epoll_waitの3つのシステムコールから成っており、使用の手順は以下のようになる。

  1. epoll_createでepollで使用するディスクリプタを作成(このディスクリプタを以下「epoll fd」と呼ぶ)
  2. epoll_ctlを用いて、epoll fdに対してソケットなどのディスクリプタを登録
  3. epoll fdに対してepoll_waitを用いることで、2で登録したディスクリプタで発生したイベントを取得する。取得できるイベントがない場合、epollはブロックする

 なお、epoll fdは通常のディスクリプタであり、解放処理はcloseシステムコールで行う。また、epoll_ctlを用いてほかのepoll fdに再帰的に登録することもできる。

 select/pollインターフェイスを使用した場合、select/pollシステムコールを使用するたびに監視したいディスクリプタのリストをカーネルに渡す必要があり、これは大規模なサーバなどでは大きな負荷となる場合がある。また、select/pollシステムコールでは、どのイベントがどのディスクリプタで起こったのかを知るためにユーザー空間でディスクリプタのリストをスキャンする必要がある。だが、そもそもカーネルはこの情報を持っているはずであり、それを再度調べるのは無駄な処理である。

 これに対してepollは、上記の2で一度登録を行えばそのディスクリプタを繰り返しepoll_waitで使用できる。また、発生したイベント数のオーダーで処理できるため、select/pollインターフェイスに比べて効率が良いといえる。

 以上のように、select/pollインターフェイスの改良版といえるepollだが、/dev/pollやkqueueなど、ほかのOSで使われているインターフェイスと比べて特に優れている点は見当たらない。既存のインターフェイスのどれかと互換性のあるものにしておけば何かと便利だと思うのだが……。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。