ART-Linuxで実時間測定ネットワークモニタを作ろう  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ver 0.21 2001/01/08 ver 0.2 2001/01/03 ver 0.1 2000/01/21 ver 0.0 2000/01/11 江端智一 E-mail:See http://www.kobore.net/mailAddress.gif http://www.kobore.net/  言うまでもありませんが、本文章は 無保証です。この文章の影響によりどの ような事が起ころうとも当方は一切の責任を持ちません。 0.変更履歴  ̄ ̄ ̄ ̄ ̄ ver 0.21 2001/01/08 「リアルタイム〜〜プログラム」の非リアルタイムタスクの 部分のミスを修正 ver 0.2 2001/01/03 「リアルタイムパケットキャプチャプログラムを作る前のテ ストプログラム」を追加 ver 0.1 2000/01/21 make dep; make bzImage が抜けていた(致命的)ので追加 ver 0.0 2000/01/11   初版 1.目的  ̄ ̄ ̄  パソコンを、ネットワークアナライザとして使うソフトが増えてきました。 一昔前は、そんなことは到底無理でした。ネットワークカードの性能は言うま でもなく、パソコンにネットワークを流れる全てのパケットを処理する能力が なかったからです。  最近は、調べるのも面倒なくらい、パソコンのスペックは向上し、それに伴 ない実装されるメモリも相当な量となりました。ネットワークカードの性能も 格段に向上しています。私はネットワーク解析に、東洋テクニカ社の SnifferBasicを利用しているのですが、なかなか使いやすく助かっています。  一つだけ問題があるとすれば、時間が正確でないという事です。  これは、パソコンOSのタスクの切換えスイッチが10ms以上で行われる事から、 これ以上の精度で時間を測る事が出来ないのです。  私はこのQoS品質の測定と制御を行なう研究を行なっているのですが、その 一つとして、パケットの正確な到着時刻を測定することが要求されていました。  そしてその精度は、『1ms以下』であることが絶対条件とされており、この 条件を満たす手段を、ずっと探していました。 2.課題  ̄ ̄ ̄  現在のネットワークモニタで何が問題かというと、タスクがどのように動く かは、OSが管理する事であって、私たちが直接コントロールできないという事 です。  先ほど、タスクの切換えスイッチが10msといいましたが、10ms毎にパケット をキャプチャするプログラムが動いてくれれば、一応10msの精度でも「実時間 測定ネットワークモニタ」と言えないことはないのです(ちと苦しいが)。  問題は、割り込みが不定期に発生し、そのたび毎に訳の判らんタスクが動き 出して、スケジュールをぐちゃぐちゃにしてしまうことです。そして、その測 定用のタスクが動き出した時点の時間をもって、パケットのキャプチャ時間と してしまうので、キャプチャ時間が全くあてにならない訳です。  これに対処するには、 「ある時間間隔で、確実にタスクが動き、処理を完了する」  ことを保証するOSでなければなりません。  このようなOSを、リアルタイムOSと呼びます。  #「処理が完了する」ためには、プログラムの設計者がチューニングする必  #要がありますが。 3.What's "ART-Linux" ?  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄  詳しくは、 http://www.etl.go.jp/etl/robotics/Projects/ART-Linux/PressDoc990426.html  を読んで頂くのがよろしいと思いますが、私なりに簡単にまとめてみました。 ○電総研の石綿陽一さん作 ○リアルタイム対応Linuxカーネルである。これは、Linuxカーネルにパッチを  当てて、カーネルリコンパイルを行なう事で実現する。 ○既存のプログラムに、わずかなAPIを追加するだけで、たちまちリアルタイ  ムアプリケーションとして利用する事が可能。 ○リアルタイム対応ドライバなども一切不要。  私の理解できる範囲で、もう少し専門的なことを言えば、 ○割り込みシグナルのフック  多くの外部機器はCPUと非同期に動作し,割り込みという特殊な信号をCPUに 送ることによってCPUに処理を依頼するんですが、こいつのお陰で処理を急が なければならないタスクが実行を妨げられてしまいます。  ART-Linuxは、この割り込みをブロックしてCPUに知らせないようにします。 そして、ある時間が来たらまとめてCPUに手渡す訳です。  要するに、勉強中の子供の部屋にコミックブックが入らないように、お母さ んがドアに立ちふさがり、宿題が終わったあたりで部屋の中に投げ込むような ものです。宿題の効率も上がるでしょう。 ○優先度逆転現象の抑制  RT-Linux(ART-Linuxじゃないよ)では、高い優先度のタスクが,低い優先度 のタスクの結果を待つ場合,両者の中間の優先度のタスクが優先して実行され てしまうことがありました。  ART-Linuxでは、このような時、低い優先度のタスクが,高い優先度のタス クを一時的に受け継ぐことで素早く処理を終わらせることで、やっかいな逆転 現象を防ぎます。  要するに、夜遅くまでカラオケをやっている時、終電に乗らなければならな い奴にマイクを渡して歌わせ、終電に間に合わせてやる、と言う事です。 ○一般権限でタスク実行による信頼性の確保  普通UNIXでは、特権レベルで動作させると、どんな処理でも出来てしまいま す。でも、どんな処理でもできるから、メモリ保護なんぞ全然しません。です から、プログラムにバグがあった場合、簡単にシステムを暴走させてしまいま す。   ART-Linuxでは、一般権限でもタスクを実行させる事ができるので、システ ム全体にバグの影響が波及する事がなくなります。  要するに、暴れる危険のあるプログラムを、檻の中に閉じ込めて飼おう、と いうことです。  このようにART-Linuxは、特別なプログラムを作らなくても、これまで使っ てきたプログラムを、簡単にリアルタイムプログラムとして利用する事ができ るようにするものです。   4.ART-Linuxのインストール  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄  私の環境は以下の通りです。 −ディストリビューションとして RedHat5.2Jを使用 −日立FLORA370(ちなみに、私は日立の回し者です) −PCI対応の10/100MイーサとSCSIが装着  ART-Linuxのインストールの手順を以下に示します。  なおこの文章は、Interface 1999/11 CQ出版の内容と殆ど同じです。 (Step.0)自分の環境の調査(非常に重要) −ネットワークカード、SCSIカードのベンダ、製品名 −CPUの種類  を調べておきましょう。マルチブートでWindowsが使えるなら、「コントロー ルパネル」あたりから、調べる事ができます。 (Step.1)カーネルソースコード、ART-Linuxパッチコードの取得    例えば、以下のURLから2.0.36のカーネルソースを取ってきます(別にどこ からとってきてもいいけど)。 ftp://ftp.jp.kernel.org/pub/linux/kernel/v2.0/linux-2.0.36.tar.gz  次に、ART-Linuxパッチコードを取得します。 http://esd.etl.go.jp/softlib/en/steps.jhtml?siteid=0&lang=en&softid=11  使用者の承認が必要となるようで、2段階の処理が必要です。 (1)Download procedureのページが表示されるので、Get a passwordのボタン をクリック (2)READMEのページを読んで、Nextのボタンをクリック (3)LICENSEのページを読んで、同意するならAgreeをクリック (4)User registrationページが表示されるので、各項目を記入してRegistをク リック  しばらくすると、電子メールでUserIDとPasswordが送られて来ます。 (1)Download procedureのページから、Downloadボタンをクリック (2)Download password inputページが表示されるので、電子メールで送られて きたUserIDとPasswordを入力  こうすると、ART-0.999.diff.gzがダウンロードできるようになります。  これらのカーネルソースコード、ART-Linuxパッチコードは、/usr/srcに置 いておきましょう。 (Step.2)カーネルソースコード、ART-Linuxパッチコードの展開 root権限で、以下の処理を行います。 # cd /usr/src # mv linux linux-old ←古いカーネルソースは残しておく # tar xvfz linux-2.0.36.tar.gz # mv linux ART-Linux # ln -s ART-Linux linux # cd linux # gunzip -c ../ART-0.999.diff.gz | patch -p1 -s ←パッチを当てる (Step.3)カーネルコンパイル X環境が立ち上がっているものとします。 # cd /usr/src/linux # make xconfig  GUIを用いたカーネルコンフィギュレーションを行なう事ができます。  (※重要)カーネルコンフィギュレーションの内容に、ART-Linux用の項目 が追加されていますので、忘れずに設定して下さい。  ART schedule Support → 当然"Y"を選択してください。  ART error reporting → カーネルデバッグ用なので、通常は"N"  ART monitor support → カーネルデバッグ用なので、通常は"N"  ART scheduling period in micro second →  リアルタイムスケジューラが実行される周期をμs単位で指定する。 10000(10ms)以下の値を指定する事。 (私の場合、1msの周期精度があればいいので、"1000"と指定した) 小さな値にするほど、リアルタイムスケジューラの負担が大きくなり、 パフォーマンスが低下する。2つ以上のタスクを走らせる場合は、そ の2つのタスクの最大公約数を指定するのが適当、とのことです。  初心者にとって、一番鬱陶しいのが、このカーネルコンフィギュレーション です。何をYにして、なにをNにしてよいか解らないからです。  このコンフィギュレーション情報は、  /usr/src/linux/.config  に記載されますので、カーネルコンパイルの実績のある他のマシンの ".config"をもらって、/usr/src/linux/においてから、make xconfigをすると、 その".config"の内容が反映されますので、変更が必要なところだけ(イーサ ネットカードやSCSIカードの情報)変えるのが、一番手っ取り早い方法です。 # make dep # make bzImage  さらに、カーネルモジュールを利用している場合には、以下のコマンドを実 行して下さい。 # make modules # make modules_install  ただし、RedHat5.2Jは2.0.36のカーネルがデフォルトでインストールされて いますので、多分モジュールのコンパイルは必要ないと思います。私はやりま せんでしたが、問題無く動いています。 (Step.4)テスト用コマンドの作成  テストプログラム用のファイルが/usr/src/linux/testに出来ていますので、 これをコンパイルします。 # cd /usr/src/linux/test # make (Step.5)カーネルのインストール  先ほどの(Step.3)で作ったカーネルを、一般的にカーネルが格納されるディ レクトリに放り込みます。 # cd /usr/src/linux # cp arch/i386/boot/bzImage /boot/vmlinuz-art # cp System.map /boot/System.map-art    System.mapのリンクを変えておきます。 # cd /boot # rm System.map # ln -s System.map-art System.map (Step.6)ブートローダの内容変更  オリジナルの/etc/lilo.confの内容が以下のような内容であったとします。 boot=/dev/hda map=/boot/map install=/boot/boot.b prompt timeout=50 image=/boot/vmlinuz label=linux root=/dev/hda1 initrd=/boot/initrd read-only  以下の内容を、上記の"timeout=50"の以下に追加します。 image=/boot/vmlinuz-art label=ART-Linux root=/dev/hda1 initrd=/boot/initrd read-only  変更内容を、ブートローダに書込みます。 # lilo -v  以上で、ART-Linuxのインストールは終わりです。  この後は、リブートして、無事立ち上がる事を祈ります。 # reboot  しかし、往々にして立ち上がらない事があるもんです。  その場合は、慌てずに LILO boot:   と言うプロンプトの表示が出てきたとき、すばやく[TAB]を押します。  そうすると、昔のカーネルが表示されますので、その名前を入力してやる事 で、昔のカーネルで立ち上げる事ができます。立ち上がった後は、再度、 (Step.3)に戻って、カーネルコンフィギュレーションから行って下さい。  きっと、いつかは成功しますよ(無責任)。 5.リアルタイムタスク実行状態への移行  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄  カーネルが無事立ち上がったら、ログインして"uname -v"と入力して下さい。 「なんちゃら ART-0.999 かんちゃら」と出てきたら、成功です。  (※重要)  この状態では、リアルタイムタスクを利用する事ができません。リアルタイ ムタスクを利用する為には、 # /usr/src/linux/test/setup  と入力して下さい。 6.テストプログラムの実行  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ # /usr/src/linux/test/beep 1 # /usr/src/linux/test/beep 10 # /usr/src/linux/test/beep 100  などと打ち込んで、音の出方の違いを楽しんで見てください。 7.プログラムのコンパイル方法  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ /usr/src/linux/testにあるMakefileの実行パターンを読み出したに過ぎませ んが、コンパイルはこんな感じでできそうです。  ターゲットソースファイルを"source.c"とします。 gcc -Wall -O source.c -o source.o gcc -o source source.o /usr/src/linux/arch/i386/lib/art_syscalls.o ソースファイルの書き方は、/usr/src/linux/test以下のものを参照して下さ い。 8.「リアルタイムパケットキャプチャプログラムを作る前のテストプログラム」  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 1ms間隔で、5秒間パケット数をカウントするプログラムを作ってみました。 ART-Linuxメーリングリストで、「・・・てなプログラムを作ってみたいん ですけど、どうすれば良いでしょうか?」と質問したところ、ART-Linuxを開発 された石綿陽一さんと、Wang Peiさんから、一つしておかなければならないこ とを教えて貰いました。 On ART-Linux, all hardware interrupts are polled in 10ms period. As the result, mesurement precision of timing becomes 10ms. When more precise mesurement is needed, you need to make the polling period shorter. It needs the modification of kernel source. (ART-Linuxでは、全てのハードウェア割り込みは、10ms毎にポーリングされて います。その結果として、測定の間隔も10ms間隔になってしまいます。厳密な 測定(1ms間隔)が必要でしたら、ポーリング周期を短くしなればなりません。 これは、カーネルソースに手を入れる必要があります。) In the file "arch/i386/kernel/art_irq.c", there are functions to manage interrupt handlers. ("arch/i386/kernel/art_irq.c"に、割り込みハンドラの管理機能があります。) I think i can say it more clearly .In the below interrupt handlers, just change the value of INTR_USEC from (1000000 / HZ)=10000 to (1000000 / (10*HZ))=1000. In this case , the interrupt handlers will run with the period of 1ms. sure you also need to change "ART scheduling period in micro second" to less 1ms . (具体的に申しあげますと、INTR_USECを (1000000 / HZ)=10000 から、 (1000000 / (10*HZ))=1000. に変更します。これで、割り込みハンドラは、 1ms間隔で動くようになります。もちろん、"ART scheduling period in micro second" は 1ms 以下にする必要があります。) と言う訳で、この部分を変更して、再度カーネルを作りなおしました。 さて、テストプログラムを以下に示します。 内容は極めてシンプルでして、 (1)ネットワークカードをプロミスカスモードにして、recvfromを使ってパ ケットをキャプチャし、パケットのカウントを取り続ける、親プロセス として動くタスク (2)一定時間毎に、上記のカウントを記憶し、パケットのカウンタを0リセッ トする、小プロセスとして動くタスク から成り、(1)(2)のプロセスで共通して使うパケットカウンタは、共有メモ リを使って、非同期に書き込みと読み出しをしています。 (ここから、version2.0の内容) ただ、このプログラムの中で一つだけ心配なことがあります。 それは、(1)のプロセスで、ecvfromを使ってパケットをキャプチャした瞬間、 きちんとハードウェア割り込みがかかって、この(非リアルタイム)タスクに処 理が引き戻されているのか、ということです。 もちろん、リアルタイムタスクが動いているときは、この割り込みの起動は 阻止されて良いのですが、そうでないときは正確にこのタスクに処理が戻って くれないと困るのです。 なぜなら、パケットキャプチャの時間が正確に測定できないからです。 どなたか、これを検証する方法やこのことに関して詳しく御存じでしたら、 御教授して下さい。 (version2.0の内容終り) さて、上記の記述に関してですが、I/O割り込みを待っている非リアルタイ ムタスクは、割り込み検知後、処理が戻る保証がなく、起動待機状態になるよ うです。 当然、それではリアルタイムでパケットをキャプチャできないことになりま す。そこで、このパケットキャプチャを行なう部分も、1msのリアルタイムタ スクとして起動させることにしました。 recvfromをノンブロッキングモードで動かし、バッファが空になったのを確 認して、リアルタイムタスクから脱出させることにしました。 ただ、この処理では、1ms以内に全部のパケットが取れている保証はないの で、処理時間を測定する為の工夫が必要になると思われます。 /*************************************************************** リアルタイムパケットキャプチャ プログラムを作る前のテストプログラム Outline: ART-Linuxを使って、パケットキャプチャの時間を厳密に計るプログラム History: 1999/01/09 プロミスカスモードテスト成功 1999/01/14 ヘッダ解析開始 2000/11/02 Linux kernel 2.0.36に対応 2001/01/03 5秒間、1ms間隔でパケット数を測定する機能を実装成功 2001/01/08 パケットキャプチャ部をリアルタイムタスクに変更 Makefile: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CC=gcc CFLAGS=-Wall -O all: art_promisc art_promisc: art_promisc.o /usr/src/linux/arch/i386/lib/art_syscalls.o $(CC) -o $@ $^ art_promisc.o: /usr/src/linux/include/linux/art_task.h - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ***************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _ART_ /* 受信パケットの先頭サイズ */ #define BUFFER_SIZE 128 /* 同期用パイプ */ int pipefds[2]; int fd; /* リアルタイムパケットカウンタ */ int _realTimePacketCounter(void) { key_t shmkey; int shmid; int counter[5000]; int *pCounter; int i; char ch; FILE *fp; /* 格納用パケットカウンタ配列のクリア */ memset(counter, 0, sizeof(counter[5000])); /* 共有メモリのセット */ shmkey = ftok("art_promisc2",'a'); shmid = shmget(shmkey, sizeof(int), IPC_CREAT | 0666); pCounter = shmat(shmid, 0, 0); #ifdef _ART_ /* set interval time 1ms */ if (art_enter(ART_PRIO_MAX, ART_TASK_PERIODIC, 1000) == -1) { perror("art_enter"); exit(1); } #endif /* パイプによる同期(プロミスカスモードの起動まで待つ) */ printf("before read\n"); i = read(pipefds[0], &ch,1); printf("%d after read\n",i); /* 5秒間(刻み時間1ms) */ for (i = 0; i < 5000 ; i++){ #ifdef _ART_ if (art_wait() == -1) { perror("art_wait"); exit(1); } #else usleep(1); #endif /* パケットカウント数格納 */ counter[i] = *pCounter; /* パケットカウンタリセット */ *pCounter = 0; } #ifdef _ART_ if (art_exit() == -1) { perror("art_exit"); exit(1); } #endif /* カウント結果をファイルに格納 */ fp = fopen("art_promisc.log", "w"); for (i = 0; i < 5000 ; i++){ fprintf(fp, "%d\t%d\n", i, counter[i]); } fclose(fp); exit(1); } /* 終了処理 */ int _endTimePacketCounter(void) { close(pipefds[0]); close(pipefds[1]); close(fd); printf("ART-Promisc Finished\n"); exit(0); } /* メインルーチン */ int main(void) { char from_msg[BUFFER_SIZE]; struct sockaddr from_addr; int msglen; int dummyInt; struct sockaddr sa; char device[] = "eth0"; struct ifreq ifr; int pid; key_t shmkey; int shmid; int* pCounter; char ch; int _endTimePacketCounter(); int i; /* 同期用パイプの生成 */ if (pipe(pipefds) < 0){ perror("pipe"); exit(1); } /* 子プロセスの終了時に、親プロセスを終了する処理 */ signal(SIGCLD, _endTimePacketCounter); /* 子プロセスの生成 */ if ((pid = fork()) < 0){ perror("fork"); exit(1); } if (pid == 0){ _realTimePacketCounter(); } /* Generate Paraent Process */ /* ソケットの作成 */ fd = socket(AF_INET,SOCK_PACKET,htons(ETH_P_ALL)); memset(&sa, 0 , sizeof(sa)); sa.sa_family = AF_INET; (void)strncpy(sa.sa_data, device, sizeof(sa.sa_data)); /* バインドの実行 */ bind(fd, &sa, sizeof(sa)); /* デバイス名を設定 */ memset(&ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, device); /* flagのセット */ ioctl(fd, SIOCGIFFLAGS, &ifr); /* プロミスカスモードの設定 */ ifr.ifr_flags |= (IFF_PROMISC); ioctl(fd, SIOCGIFFLAGS, &ifr); /* recvfromのNon-Blocking Modeのセット */ fcntl(fd,F_SETFL,O_NONBLOCK); /* 共有メモリのセット */ shmkey = ftok("art_promisc2",'a'); shmid = shmget(shmkey, sizeof(int), IPC_CREAT | 0666); pCounter = shmat(shmid, 0, 0); /* パケットカウンタのリセット */ *pCounter = 0; /* プロミスカスモードを起こす為のダミー処理 */ dummyInt = recvfrom(fd, &from_msg, sizeof(from_msg) , 0, &from_addr, &msglen); ch = 'a'; /* パイプによる同期(リアルタイムタスクに起動を通知) */ printf("before write\n"); i = write(pipefds[1], &ch,1); printf("%d after write\n",i); #ifdef _ART_ /* set interval time 1ms */ if (art_enter(ART_PRIO_MAX-1, ART_TASK_PERIODIC, 1000) == -1) { perror("art_enter"); exit(1); } #endif /* 5秒間(刻み時間1ms) */ for (i = 0; i < 5000 ; i++){ #ifdef _ART_ if (art_wait() == -1) { perror("art_wait"); exit(1); } #endif while(1){ /* プロミスカスモードによるパケットキャプチャ(Non-Blocking) */ /* この処理では、1ms以内に全部のパケットを取れる保証はないので、 スキップ用のカウンタを準備する必要があるかもしれない。*/ if(recvfrom(fd, &from_msg, sizeof(from_msg),0, &from_addr, &msglen) == -1) break; else /* パケットカウンタの加算 */ *pCounter = *pCounter + 1; } } #ifdef _ART_ if (art_exit() == -1) { perror("art_exit"); exit(1); } #endif return 0; } 9.概況  ̄ ̄ ̄  私の所のマシンでは、Xを立ち上げてちょっと妙な操作をすると、Linuxのカー ネルが簡単に凍ってしまいます。 ですから、Xは使わないのですが、Shutdownの時に暴走してしまう(これって、 私のとこだけでしょうか)のに困っています。電源をブチッときらなければな らず、再起動の再、心臓に良くありません。 多分、次のバージョンは安定するに違いない、と思っていたところ、 ついに出ました! 待望のLinux kernel 2.2 シリーズのNewバージョン!! http://www.etl.go.jp/~you1/art-linux/download.html 20世紀最後の日、ART-Linuxメーリングリストで公知されました。 『ようやくRedHat5.2Jを捨てる日が来た』と万感の想いに浸りました。 10.最後に  ̄ ̄ ̄ ̄ ̄  将来的には、tcpdumpやNetraMetの実時間測定を実現してみたいと思ってい ます。色々適用ができるようになりましたら、またバージョンを上げて、ご報 告致します。  ではでは。