Linuxでのリンクレイヤー送受信の高速版
Linuxで、リンクレイヤーで送受信を行う場合、PF_PACKETというLinux独自の仕組みを使い、簡単にプログラミングができます。BSDではBPFという仕組みを使ったり、SolarisだとDLPIを使うなど、リンクレイヤーはUNIX系OSでも統一がされていない部分なのですが、個人的にはLinuxのPF_PACKETが一番シンプルで使いやすいと感じていました。
しかし、このところ10Gbpsでの通信などでPF_PACKETを使っていると、性能が追いつかないという問題にぶち当たり、いろいろと調べてみると、PF_PACKETはユーザランドへのデータコピーなどの為に、処理性能はそれほど高くない、という感じでした。
そうなると、カーネルモードでプログラミングをするしかない、ということになるのですが、私はカーネルモードのプログラミングをシステムで使うのはあまり好みではありません。問題が起きたときにOSごと止まってしまうという怖さもありますが、なによりも、システムの移植性が悪化するからです。カーネルのバージョンが変わるとそのままでは動かないということが増えてしまうのです。
何とかならないかと調べてみると、PF_RINGという、Linuxで使えるより高性能な仕組みがあるという情報がちらほら見つかり、ようやく今日、試してみる時間が取れました。
詳細は、こちらを読んでみてください。
http://www.ntop.org/products/pf_ring/
私が今回やりたいことは、単にリンクレイヤーで受信することと送信することだけなので、簡単なサンプルはどこかにないかと調べたのですが、PF_RINGはほとんど情報がなく、結局上記サイトから一式ソースをダウンロードし、その中にサンプルがいくつかあったので、それと付属のドキュメントを見ながら作ってみました。
はじめに、
pfring_open():デバイスオープン
pfring_set_application_name():名前を付与
pfring_set_direction():受信対象の指定(自分で送信したものも受信するか)
pfring_set_socket_mode():読み書きの指定
pfring_set_poll_watermark():受信レディをどのくらいたまったら反応させるか
このあたりで初期化して、
pfring_enable_ring():有効化
これで送受信開始可能になります。
pfring_poll():受信レディの調査
pfring_recv():受信
pfring_send():送信
使い終えたら、
pfring_close():デバイスクローズ
という感じです。分かってしまえば簡単なのですが、初期化のあたりは試行錯誤してようやく分かった感じです。
PF_PACKETで動かしていたプログラムをPF_RINGに置き換えてみたところ、スループットが1.5倍以上になりました。CPU使用率も下がります。
デバイスドライバを1つ追加で使うくらいで、セットアップも簡単です。カーネルバージョンが2.6.18以降で使えるようです。
さらにフィルター機能なども豊富なようですが、とりあえずPF_PACKETの性能で悩んでいる人は試してみると良いでしょう。
PF_PACKETに関しては、こんな本がお勧め(宣伝)!