前回の冒頭にちょろっと書いたけど, 先日のもくもく会 でethernetの送信割り込み対応をやっていたのだが,その続きを家でやっていて まとまったので公開しよう.
まず今回の修正だが,以下の3点だ.
この送信処理は,シリアルでは割り込みハンドラの延長で次の文字を送信していた. しかしこれだと送信パケットのキューイング処理と実際の送信処理で, 割り込み禁止にする区間が必要になってくる(シリアル送信の処理はそうなっている). これだとなんかすっきりしないので,送信処理はすべて netdrv が行って,割り込み ハンドラは netdrv に送信完了通知を送るだけの実装にしてみる. 送信処理を割り込みハンドラが行うのかタスクが行うのかでシリアル送信とは 構造が異なっているが,データをキューイングしておいて最初の1発を送信すると, あとは送信完了割り込みを契機にして後続のデータを送信していく,という手順は シリアル送信と似ている.
さらに受信処理だが,これも従来は割り込みハンドラで受信を行っていたのだけど, 送信処理が netdrv タスクによってすべて行われるので,受信処理もそーいうふうに 変更してみた.具体的には,受信割り込みが入るとハンドラが netdrv にメッセージで 通知して,実際の受信処理は netdrv タスクが行う(recv_packet()という関数).
この実装だと割り込み禁止区間が無くなって非常にすっきりするのだが, ちょっと問題点もある.
まずは割り込みのクリアだ.割り込みハンドラの中では割り込みのクリア (割り込みレジスタ(ISR)のビットを落とす処理)を行わないと,割り込み処理が 終わった後も同じ割り込みが再度発生して,割り込みの無限ループになってしまう. なので従来は受信処理の後に割り込みのクリアを行っていたのだが, 今回の修正で実際の受信処理は netdrv が行うように変更された. なので,ハンドラ内部で割り込みをクリアしてから実際の受信処理を行うという 順番になる.実際の受信処理までにタスクのディスパッチも発生するので 他のタスクが動作する可能性もあることになる.
しかし,割り込みのクリア処理を netdrv 側に持っていくことはできない. というのはクリア処理を netdrv に持っていってハンドラ内部から削ってしまうと, ハンドラ抜けて割り込み有効になったとたんにまた同じ割り込みが入ってしまい, 無限ループになってしまうからだ.
まあこれに関しては,実際に割り込みクリアしてから受信処理を行っても問題無い ように思えるので,これでいいとしよう.なにかまずい問題あったら誰か教えて. やっぱり割り込みハンドラの中で受信処理をするのが普通なのかなあ. ちなみに回避策はあって,netdrv の動作中は割り込み禁止,というようにしてしまえば 割り込みのクリア処理を netdrv タスク(具体的には,recv_packet()という関数)が 行うように持っていくことはできる.実は優先度をゼロにすると割り込み禁止で 動作するようにCCRが設定されてタスク起動する(kozos.cのthread_run()参照)ので, netdrv を優先度ゼロで起動すればこのようなことができる.でも優先度ゼロ (最高優先度)のタスクを気軽に作るのもやなので,まあこれでいいとしよう.
あと割り込みのクリアから実際の送受信処理をするまで間があるので,この間に また新しいパケットを受信したらどうなるか?ということも気になる.これに関しては 問題無いように思う.送信に関しては,送信割り込みが入ったら次のパケットを 送信しないと新たな送信割り込みは発生しないので,この問題は無い,と思う.
さらに,この構造は高負荷をかけたときに弱いように思える.パケット受信が 多量になって受信割り込みが上がりまくったときに,割り込みが多くて netdrv を タスクディスパッチできないのでetherコントローラの受信バッファから受信データを 引き上げることができず,コントローラがひたすら受信と古いパケットを破棄と 受信割り込み発行を行ってしまうからだ.この場合,まったく受信できないという ことになってしまう.このことを考えるとやはり受信割り込みハンドラで受信データの 引き上げを行ったほうが高負荷に強いだろう.でもまあこちらのほうが実装が すっきりするので,今回はこれでいこう.
で,修正したソースコードは以下.
ちなみにRTL8019はDMAを持っているみたいで,ISRのRDCというビットでDMAの 完了割り込みを見れるようだ.なので本来ならば,パケット受信したらDMAを開始して, DMAの完了割り込みが入ったら実際の受信処理(処理タスクにメッセージで渡す)を 行う,という処理にするのが正しいようにも思う.送信に関しても, 送信データをDMA転送して,DMAが終わってDMA完了割り込みが入ったら実際の送信を 行う,という処理にすべきなように思う.このような複雑な処理を割り込みドリブンで 行えればOSを使っているメリットが十分に出てくるというか,OS利用の真価を発揮 できるように思う.実際には現状ではRDCをポーリングで待ってしまっている (rtl8019.cの read_data(), write_data()).なのでちょっと中途半端な実装に なってしまっているのだが,これ以上いろいろやるとnetdrvが複雑になってしまうし, KOZOSの「シンプルなサンプル実装」という面から見ると,このくらいの実装が まあ手頃でちょうどいいのかなあ…とも思ってしまうところだがどうでしょうかねえ.
動かしてみたら,webサーバがけっこう安定して動いた. そうそう,今回はetherの送信割り込みに対応したので, シミュレータ側も送信割り込みに対応させることも考えないといかん. やることいっぱいだ.