シリアル-LANアダプタ(その2) 続き


今度こそTCP/IP!

 ここまででμITRONを制作したので、本題のTCP/IPに取りかかります。次の手順を踏むことにしましょう。

  1. 物理層、ARP、ICMPを実装する。
  2. IPそのものの送受信を実装する。
  3. PPPを実装する。
  4. TELNETを実装する。
  5. FTPを実装する。

 1.については、これまで作ってきたものをマルチタスクに対応させます。ここまでできると、前と同様pingに応答できるようになります。
 2.については、ここで確認はせず、作りこむだけに止めます。
 3.で、TELNETなどの前にPPPを作ります。作れればほとんどIP等のパケットをそのまま受け渡しするだけなので、ここでIP送受信のチェックもできます。これができるとMN128の持っている「LAS接続」というのが実現できます。このモードに入るために一部ATコマンドも実装する必要があります。
 4.と5.で、自身がIPの端末となるクライアントアプリを作ります。IPフラグメンテーションにも対応しないといけません。

 さて、どこまでできるでしょうかね。


物理層、ARP、ICMP…

 まずは、夏にpingの応答までできていたプログラムを、μITRON対応として作りなおします。各プロトコル層間のデータ受け渡しはメッセージバッファ機能を使用すれば良さそうです。特に、それぞれのプロトコル処理ルーチンへ引き渡すたびにバッファ転送ルーチンを記述しなければいけなかったのが、単純にOSの機能として使えるというのがいいです。
 では何にどれだけ割り当てればいいだろう…と考えていたところ、ふと、「大きさゼロでもいいかも…」と思いつきました。大きさがゼロのメッセージバッファでは、データを送るタスクと受けるタスクがそのデータ受け渡しの瞬間に待ち合わせます。一旦溜めて次のパケット処理に備えないといけないような気もするのですが、結局次のパケット処理ルーチンで詰まってたらそのうちバッファも満杯になってしまいますので同じことです。大きさがゼロでない場合は、メッセージバッファへの送信で1回、受信で1回の計2回転送処理がありますが、ゼロにしておくと送信と受信の転送が同時になりますので、結果的にパケットのコピー回数を減らすことができそうです。まぁうまく動かすには優先度がカギになるかもしれませんね。

 あと、NE2000アクセスルーチンではパケットの送受信両方とも同じアドレスのI/Oレジスタにアクセスします。いわゆるローカルDMAというヤツで、以前はアクセス中の割り込みを禁止するようにしていました。送信処理中に受信割り込みが入ったらデータ転送時にむちゃくちゃになりますからね。しかし今回は単純に割り込み禁止だとシリアルポートも受け付けられなくなりますので(以前のパフォーマンスの悪さはここにあったか…)、セマフォを使うことにします。が、この時点で私のμITRONにはセマフォがありません…ということでさくっと実装しました(ああ、セマフォなしでカッコ良くキメるつもりだったのに)。セマフォそのものは待ちに入る可能性があるのは獲得時だけ、数を管理するだけなので特別なバッファもいらないということもあって、実は簡単に実装できるものだったりします。しかしまぁ、改めて自作μITRONの中身を見てみると、好き勝手な構造体を作ってますなぁ…。

 それでは、NE2000アクセスルーチンの作り変えから。NE2000の初期化と検査はほぼそのままでいいのですが、無意味にグローバル変数を使っていたところがあったのを、ローカル変数に変更します。このグローバル→ローカルはここだけでなくて、タスク化する各サブルーチンも同様です。タスクは基本的には終了しませんから、そこだけで使う変数はローカル宣言で十分というわけなんです。ということで、NE2000の送信/受信ルーチンはタスク化するとともにパケットの受け渡しをメッセージバッファに切り替え、グローバル宣言していた以前のバッファは消してしまいます。以前のバッファはリングバッファだったので、その読み出し/書き込みポインタの計算などごちゃごちゃしていたのですけど、メッセージバッファ受信だとポインタの管理は必要ないですし、処理しようとするパケットは読み出した配列の先頭から必ず入っていますし、こういうのがOSを使ってて良かったなぁと思えるところですね。NE2000送信ルーチンはパラメータにパケットとあて先アドレスを指定するようになっていましたが、メッセージバッファ経由で完成したパケットをもらうことにしたので、ここでは単にレジスタを操作して送信データをNE2000に渡すだけになりました。NE2000受信ルーチンは以前から割り込み駆動になっていましたし、読み出したパケットを上位のタスクへのメッセージバッファへ送信するだけです。

 次に、その上位にあるイーサネット送信/受信ルーチン。受信ルーチンでプロトコルの種類によってIPとARPに振り分けるのは以前と同じです。変わったのはパケットの受け取りも振り分けもメッセージバッファを使うようになったぐらい。送信ルーチンは、以前はパケットをNE2000送信ルーチンに渡すだけといってもいいくらいの処理しかしてなかったのですが、今回は送信するイーサネットのデータにヘッダのうちプロトコルの種類(ARPかIPか)だけを入れてもらったものをメッセージバッファ経由で受信し、その種類によってMACアドレスを埋める方法を変えるようにしました。例えばARPだとデータグラム中に送り先のMACアドレスが入っているわけですし、IPだと送り先のIPアドレスはあるのでそれを元にARPテーブルを検索すればMACアドレスは求められるはずです。

 さらに、ARP処理ルーチン。ARPの受信時、要求か応答かで処理を振り分けるのは今までどおり。以前のは振り分け部をすっきり見せるために要求処理と応答処理をサブルーチンとして記述していましたが、メッセージバッファから受信したパケットを使用する都合上、そのサブルーチンを埋めこんでしまいました(パケットをポインタで渡してしまえば同じではないかという説もあるが…)。要求処理は以前と同じくソースのMACアドレスとIPアドレスをターゲットに転送し、ソースのアドレスは自分のものを入れて、オペレーションコードを「応答」に書換え、これをイーサネット処理タスクのメッセージバッファに送信します。応答処理の場合はもらったデータをARPテーブルに登録するだけ。
 ARP検索ルーチンは見つからなかった時にARP要求パケットを生成して送信する部分を追加しました。送信後そのままreturnするとMACアドレスがないままイーサネット送信タスクが進んでしまいますので、一旦slp_tskでスリープし、ARP応答パケットを処理するところでそれを起床するようにします。ARP探索ルーチンが再開すると、もう一度先頭へ戻って探しなおし、今度は見つかるはずなので、それを返り値とします。slp_tskで待つ間、実はIPアドレスが誰も持ってないものだったりするとARP応答が永遠にないわけで、ここでロックしてしまうことが考えられます。本当はタイムアウト付きのスリープ(tslp_tsk)にしておいてタイムアウトがあるとエラー表示と送信しようとしたパケットが捨てられるようにならないとまずいのでしょうが、それは後回しです。

 …と書いていてまずいことに気がつきました。ARP検索ルーチンはイーサネット送信タスクからサブルーチンとして呼ばれるので、そのタスクの一部分になってしまいます。つまり、ARP要求が必要になってその応答を待っている間、ARP検索ルーチンだけでなくイーサネット送信部が止まってしまうのです。もしその間に送信したいパケットができたら?pingの応答ができてしまえるくらいの時間は十分にあるような気がします。
 問題なのはIPアドレスに対応するMACアドレスがないことがわかって、それが解決するまで待たなければならないということです。解決を誰かに任せて、自分は別のパケット処理ができる態勢に戻れればいいのです。そこで、ARP要求出力タスクというのを作って、そこにARP要求→応答までの一連の処理をやらせることにします。このタスクには、MACアドレスがわからなくて送れなかったIPパケットを渡してやることにします。そのパケットから送信先のIPアドレスを読み取り、さっきARP検索ルーチンに追加したARP要求部をこちらに移してこちらで処理すればいいでしょう。ARP検索ルーチンは見つからなかったらNULLを返すことにして、これを使用するイーサネット送信タスクではARP検索の結果がNULLなら送ろうとしていたパケットをARP要求出力タスクに投げ、ARP要求出力タスクでは要求出力→応答確認の後もう一度そのパケットをイーサネット送信タスクに投げ返せば、その時にはMACアドレスは登録されているはずです。

 次は、ICMP。ICMPはIPに乗せるので、先にIPを片付けます。といっても、とりあえずTCPとUDPとICMPのプロトコルごとに処理を分けた後、それぞれのメッセージバッファに投げるだけ。以前は処理するつもりで組み込んでいたフラグメント処理は上位タスクで処理することにしました。まぁまだTCP/UDPについては何も処理するものがありませんので、受け取ってもパケットは捨てます。
 でICMP処理タスクへパケットを投げるのですが、イーサネット受信からIP/ARP受信へ投げるときはイーサネットヘッダを捨てるようにしたのに対し、IP受信からさらに上位へはIPヘッダは捨てずに渡すことにしました。フラグメントや受信ポートなど、上位で処理する内容が多いためです。ICMPはとりあえずエコー処理だけ組み込み、Port Unreachableは呼び出し方法未確定のため後回し。

 各タスクができたところで、イニシャライズルーチンを作ります。メッセージバッファ、データキュー、セマフォ、割り込みサービスルーチン、そしてタスクを生成するのです。
 メッセージバッファは先ほどのように大きさをゼロにします。受け付け可能な最大の大きさを2048バイトにしましたが、別に増やしてもメモリが減るわけじゃないですしね。メモリはタスクに割り付けるスタックの方が肝心です。今までグローバルに確保していたバッファをローカルに持つようになったので、それがスタックから溢れてしまうとタスク同士で食いつぶしてしまいますからね。
 さらに肝心なのはタスクの優先度。ここを間違えるとデッドロックしたり性能が出なかったりするかもしれません。割り込み駆動されるものは高く、そこから離れるにしたがって低くなるとすると、

優先度 タスクの内容
1 NE2000受信/送信,シリアルポート受信/送信
2 イーサネット受信/送信
3 IP受信/送信,ARP受信/送信
4 TCP受信,UDP受信,ICMP受信/送信

という感じでしょうか。一時は受信の優先度を高く、送信を低く、というようにしていたのですが、まずやってみるなら物理層を高く、プロトコルが上位であるほど低くして、不都合が優先度にあるならその都度検討すればいいんですね。


まぁ一発で動くとは思ってないが…。

 ひととおりできたところでコンパイル、実行。といっても、一発で動くことがあるはずもなく、FreeBSDからのpingには予想通りの無反応。プログラムができたからっていきなり実行してしまうのはBASICプログラマの悪い癖ですかねぇ。
 まぁ基本にしたがって、順番に解決していきましょう。まずはタスク生成。最後に生成するタスクはディスパッチ有りの自動起動なので、その後にメッセージを出力させても表示はされないはず…される(^_^;)。そういえば当初の見込みよりタスクの数が増えたような気がするなぁと思って初期設定を見たら、なんと数が足らない(-_-;)。追加してコンパイルすると、ちゃんと最後まで生成されてディスパッチされるようになりました。でもまだ無反応。
 ではどこまでタスクが起動されたか(ディスパッチされてタスク外の要求待ちになるか)を確かめるべく、あらゆるタスクの先頭にメッセージ出力を仕込んでコンパイル。あ、タスクからのメッセージ出力も「シリアルポートに文字列を出力するタスク」が担当します。μITRONそのもののデバッグでかなり痛い目を見ましたからね。…ところが、最初のタスクの起動メッセージ以外何も現れません。うーん、こりゃ無反応なはずだわ。
 タスク起動の順番を変えても先頭だけ起動するので、タスクの中から出られない(ディスパッチするはずが、していない)か暴走しているか。暴走の原因はスタックサイズの問題がすぐ頭に浮かびますので、とりあえずスタックを増量…おお、全部じゃないけど起動する(^_^;)。なるほど、やっぱり少なかったかという事で他のタスクのスタックもある程度増量しておきます。
 増量しても途中で止まる症状は変わらなかったので、今度はディスパッチしているかチェック。まずμITRONのディスパッチ部にメッセージを仕込んでみると、なんと止まるタスクの起動後のディスパッチがありません。試しにそのAPI(rcv_dtq)の直後にメッセージを仕込むと、メッセージの羅列が…。変だなぁと思ってまた初期設定を確かめてみたら、なんとデータキューの数を0にしていました(^_^;)。一時はいらないかと思ってたんですが、どうせテストプログラムから改造するんだもの、使うのは確実だし、と思って残したんですが…初期設定をいじったのはいらないと思っていた時期でしたからねぇ。まぁこれで、タスクの起動はかかるようになりました。でもやっぱり無反応。
 次は割り込みで起動するタスクが動いているかの確認。昔と違い割り込みのスタートアップルーチンでの扱いはほとんど許容という感じなのでトラブルはμITRONかユーザープログラムにあるはずです。しかも、メッセージ出力は動いてますからユーザー側にしか考えられないんですけどね。しかし、割り込み後起床される箇所でのメッセージ出力を仕込んでも何も表示されない。なんで?NE2000の初期設定?プロービング?MACアドレスを表示させても問題ないみたいだし…あれ?いつの間にか割り込みが受けつけられてる!?でも続きがない!?うーん、謎だ…。

 一夜明けて再挑戦。とりあえずもう一度実行してみて…あれ?割り込みが入る!?2回で止まるけど再実行すると間違いなくまた入る…そういえば昨日はアダプタの電源をまったく落さずにやってたから、リセットでは復帰できない状況に陥っていたのかもしれないですね。再現しないというのがいやらしいですが、まぁ良しとしましょう。
 次なる問題は、割り込みを2回受けつけて止まるというところ。どこで止まっているのか、NE2000受信タスクのディスパッチの起こりそうなところにメッセージを仕込んでみると、2回目のセマフォ獲得時に止まっているようです。まぁ簡単とはいえまだデバッグしてませんからねぇ…。気になるのは上位、つまりEthernet受信タスクへパケットを投げているはずなのにスパッと処理を終わらせていること。待ち合わせするのにもうちょっと時間かかりませんかね?ということでEthernetのパケット受信待ちの直後にメッセージを仕込みます。どうせだからどのタスクにもそういうメッセージを仕込んでおきましょうかね。
 で実行してみると、どうもEthernet受信タスクはパケットを受けとってない模様。ということはパケットを送ってないということ?ではパケット送信(snd_mbf)はエラーを出してないか戻り値をチェックするようにしてみると…おお、エラーではないですか。さらに戻り値も表示するようにしてみると、E_PAR(パラメータエラー)だということがわかりました。パラメータエラーをsnd_mbfが返すのは受け渡そうとするデータの量が生成時に規定した最大値を超えている場合。ではその大きさは、と表示させてみると…なんじゃこりゃ。マイナスいくらってFFFF…とかいう数値かな。う〜ん、なんかとんでもない位置のメモリから読み出したとかいうような感じですな。そういえば、前はグローバルにしていた変数をローカルにして、ただ定義すればいいだけなんだっけ?使いまわして(=前の値を使って)何かするということはないんかな?とすれば初期化は必要かも…ということでローカル変数のゼロクリアを入れてみると、むちゃくちゃな数値は出なくなったもののそれでも変。ではそれを読み出す箇所ではそもそもどうなっているのか…と表示させてみたところ、なんとゼロ…。
 受信して割り込みがかかったのですから、ゼロバイト受信てことはないでしょう。やはり初期化とかまずかったのかなぁ、変数をローカル化した時に何かしたかなぁと悩みましたが、悩んだ末ようやくわかりました。問題の変数は、以前はグローバル宣言されてて

unsigned char ne_ringbuf_status,ne_ringbuf_bound;
unsigned int ne_ringbuf_len;

のようになっていました。この3つの変数のうち"ne_ringbuf_status"のアドレスを指定して、4バイトの数値をNE2000から読み出すようにプログラムされていました。4バイトは元々次のバッファ・ステータス・受信バイト数の順に並んでいて、これを読み出しの際に2バイト単位で上位下位を入れ替えるので上記の宣言の順に変数に格納される仕組みになっています。ではこの変数をローカル宣言したらどうなるかというと、ローカル変数はスタックに積み上げられていくので、スタックには

ne_ringbuf_len
ne_ringbuf_bound
ne_ringbuf_status

の順に定義されることになります。ここで"ne_ringbuf_status"のアドレスを指定して受信すると、他の2つの変数には何も値が格納されないということになるわけです。ですから、ローカル化したときには

unsigned int ne_ringbuf_len;
unsigned char ne_ringbuf_bound,ne_ringbuf_status;

というようにしないといけなかったわけです。ううむ、奥が深い(本当か?)。

 さて、ようやくバイト長などがNE2000から得られるようになったわけですが、相変わらずEthernet受信タスクからは反応がありません。というか、snd_mbfでエラーになっているのはそのままです。六十数バイト程度のパケットで大きいと言われるなんて…。不思議に思って実際に定義されている最大パケットの大きさはいくらかを判定箇所で表示させてみると、なんとゼロ。そりゃあ大きすぎると言われるわな。
 では、メッセージバッファの生成で実はエラーがあったとかいうことはないのかと返り値を調べてみると、これも問題なし。まさか、定義に使用する構造体が生成のどこかで変更されてる?一旦最大パケットを2048に設定した後、変更はないのでそのまま他のメッセージバッファの生成にも使いまわしていたんですが、それがまずかったのか?試しにエラーになっているメッセージバッファの直前でその値を調べてみると、ゼロ…。ううむ、まさかコンパイラのバグかなぁ…。でもコンパイル結果を見てもおかしそうなところは見当たらないんですが…もしかしたらランタイムライブラリの中かなぁ…。

 さらに一夜明け、もしやスタックがつぶれてるんではと思ってスタックサイズや変数の定義順をチェックしてみてそちらには問題ないと確信、ますますバグの疑いが深まります。ソースとコンパイル結果を送るための準備をしつつ、ふとヘッダファイルを見てみると…なんと、最大パケットサイズの型定義「UINT」をunsigned charで定義(typedef)していたのを発見。UINTというのは元々unsigned intの略なんですが、intという定義を「そのプロセッサで最も扱いやすいサイズ」などと説明していたのでcharと同等にしていたんです。でも、unsigned char型に2048を入れても、256×8ですから結果はゼロですよねぇ。ということでUINTをunsigned intとすることで問題解決。snd_mbfもエラーなくデータを転送するようになりました。いやぁ、濡れ衣着せるところでしたねぇ。

 Ethernetパケット受信まで動くようになりましたがその先は動いてないので、とりあえず今度は2回目のセマフォ獲得で止まってしまう問題。セマフォ獲得で止まるということは資源がないということですが、動いている様子を見ても他の箇所のwai_semは実行されてないみたい。実際メッセージを仕込んでもそれは表示されません。
 wai_sem処理ルーチン内で資源がなくてディスパッチする寸前にメッセージを仕込むと表示されますしついでにセマフォの数もチェックしてみると確かにゼロですので、sig_semで資源が正しく返されていないのが原因ということになります。が、sig_sem内で資源を返さないのはすでに資源待ちタスクが存在する場合のみ、タスクがあるかどうかのチェックは他のAPIでも使っている方法そのままですのでここに間違いがあるとも考えにくいです。ですが、そこにメッセージ出力を仕込むと表示が…。ということは、あるはずのないタスクが存在していることになるわけですね。ではそもそも初期化(セマフォ生成時)の時にはどうだったか…と見てみると、なんか変。あれ?セマフォ待ちキューの初期化がないような気がする…。ということで、ここに初期化を入れればちゃんとセマフォのやりとりができるようになりました。

 次はEthernet受信タスク。繰り返しパケットは受信されているようですが、その次、つまりARPやらIPやらへの転送が行われていないようです。プロトコルタイプを判定する箇所の次にメッセージを仕込んでも表示なし。まさかと思いつつプロトコルタイプを表示してみると、なんとゼロ。なんでかな…と悩みましたが、ふと、シフト演算子の優先度が低いことに気がつきました。つまり、

type=pkt[etherhdr_type] << 8 + pkt[etherhdr_type + 1];

としていたのですが、これだと先に「8 + pkt[etherhdr_type + 1]」が演算されてしまうんですよね。その結果分左シフトするので、消えてしまうという…。つまらない単純ミスでした。
 こんな単純ミスを繰り返しつつ、例えばARP応答パケットをEthernet送信タスクを飛び越してNE2000送信タスクに送っていたとか、Ethernet送信タスクでMACアドレス決定方法の分岐となるEthernetのプロトコルタイプを設定してなくて送信されずに捨てられていたとかありましたが、ようやくARPには応答できるようになりました。

 ARPに応答できると、次はICMPが送られてきます。こちらもping送信元IPアドレスの取り出し方を間違えてたりなどいろいろ細かいミスがありましたが、一応応答はするようになりました。がそれもtcpdumpのモニターのメッセージだけの話で、pingそのものは応答を受け取っていないようです。うーむ、またチェックサムですかねぇ。
 チェックサムを計算させるサブルーチンへのパラメータ、

sum=calc_chksum( &pkt[SIZEOF_IPHDR], size - SIZEOF_IPHDR );

で指定してるけど、これでちゃんとサイズまで渡ってるんでしたっけ。なんだかこれ、前にコンパイラがバグ出したのと似てますねぇ。心配なのでコンパイル結果を見てみると…おお、足してる!!なんでだろ、もしかすると「SIZEOF_IPHDR」の定義に他の文字列を使って、それがさらに他の文字列を使って…などとしてるので、その展開をここで正直にしてるのかなぁ。うーむ、これは本当にバグ報告だわ。以前に出たバグも、きっとこれですね。とりあえず「size - SIZEOF_IPHDR」を外に出して、SIZEOF_IPHDRを普通の数値に置き換えましたが、まだ変ですね…。pingの要求と応答のバイト数が違うというのが特に。

 またも一夜明けました。いや本当はもうちょっと時間経ってますが。イエローソフトへバグ報告したところ、私の方の認識違いということがわかりました。報告として挙げた例が

#define ONE 1
#define TWO ONE+1
#define THREE TWO+1
#define FOUR THREE+1
#define NINE FOUR+5

としていたのですが、これらのマクロが式で使用される際、すでに計算できる値、例えばTHREEなら3とかいうようにきっちり置き換わっているものと思っていました。しかしながら、THREEは最初に式の中で評価されるときには3ではなく「TWO+1」として置き換わります。演算としての代入でなく置き換えなので、TWOの後にある「+1」が足し算として評価されてしまうんですね。そういや、#defineは単純な置き換えしかしないと何かで読んだような気がします。ついつい、数値になるものは最後まで数値に置き換わるのだと思っていました。引き算でも正しく使用するためには、

#define ONE 1
#define TWO (ONE+1)
#define THREE (TWO+1)
#define FOUR (THREE+1)
#define NINE (FOUR+5)

としてカッコでくくらないといけません。で、今まで各プロトコル単位に分かれていたソース専用のインクルードファイルにプロトコルヘッダのラベルを#defineで定義していたのですが、カッコの修正もありますし、新たにTCP/IP関係の定義をまとめたインクルードファイルを作って必要ならそれをインクルードするようにしました。カッコの修正の後、もしかして他にも影響を受けていたかもしれないとコンパイルして実行してみると、なんとpingに応答します(;_;)。まぁある程度できてくればここまでは早いと思いましたが、やっと昨夏の時点に戻ったわけですね。

 メッセージなどを消して再コンパイルしpingの応答時間を見てみると、10msecちょっとくらい。昨夏のping応答プログラムの倍の速度で動いていることになりますね。メッセージが入っていても60msecちょっとくらいだったので、以前の3倍くらい(約200msec)速くなってます。リアルタイムOSならではの効果ということでしょうか。
 今のところの問題点は、連続ping中に無関係のブロードキャストパケットなどが入ると応答がその分遅れること(これはしかたないのか?)、またその後連続pingを止めると最後の応答パケットを吐き出さないままパケット待ちになることですね。スケジューリングか何かの問題のような気がしますが、とりあえず追求は後回し。ゆっくり考えましょう。


小休止中…m(__)m

 このあとPPPに挑戦していたのですがちょっと頓挫(^_^;)。経緯はそっちを読んでいただくとして、気持ちを切り替えていざTCP!と意気込んだのですが(いろいろ考えたり本を読むうちに実装方法が見え始めてきたのです)、先だってタイムアウトを実装しようとして困っているうちに大変な話が舞い込んで来て…。といっても悪い話ではないんで、そのうち何らかの形で案内できるかと。

 その「話」に絡んだやりとりをするうち、シリアルポートの不具合について情報を得ました。KL5C16030(というか、川鉄のシリーズは全部、か)のシリアルコントローラは、割り込みを禁止できないんです。割り込みコントローラで入らないようにすることはできても、シリアルコントローラは割り込みを発生させているんですね。で、割り込みコントローラで割り込みを許可すると遮られていた割り込みが入るわけです。
 YK30-1は、RAMでの動作をさせるとき初期設定や転送のためにFLASHメモリに書きこまれたモニタプログラムが起動するようになっています。このモニタは当然ながらプログラムの転送を促すメッセージと終了を知らせるメッセージをシリアルポート経由で出力します。モニタは割り込み駆動ではないので割り込み禁止状態で動いているのですが、ユーザープログラムは割り込みを使用しています。ユーザープログラムで最初の文字を送信しようと割り込み禁止状態で送信バッファに文字を書き込みただちに割り込みを許可すると、今送信しようとしている文字を送り終える前に、それまでに保留されていた割り込みが入ります。そしてその後本来あるべき割り込みが入ります。プログラムは1文字分しか割り込みを想定していないので、文字が欠けたりハングアップしたりするのです。
 そこで、割り込みが一回分あらかじめ余計にあることを考慮してダミーの割り込みを受け入れ、その後の送信文字数と割り込みの対応が一致するようにしました。これでかなりマシになったのですが、まだ調子悪いですね…。心なしか割り込みを多重に受けると調子悪いような気もするんですが、ちょっとまだ見極めきれてません。

 さて、実は少し前からこの一連のページをTRONな方々に引っ掛けていただいております。個人の趣味のレベルでμITRONを作ろうというのがそんなに数多くないうえに、さらにそこにTCP/IPを載せるべく四苦八苦しているのがポイントのようです。見ていただけるのは嬉しいことなのですが、実際のところ自分のサイト中に作ってるブツを置いてないということもありまして、ちょっと心苦しく感じていました。
 そこで、ブツに対して解説がまだなのですが、現時点での成果物を公開しようと思います。某所で公開されるであろうモノとほぼ同じですが、こちらは投げ出しているPPPなんかも含めてアーカイブとします。

lanadap1.lzh(130KB)

参考文献

元々パケットドライバの仕様が知りたくて買った本ですが、とりあえずTCP/IPを知るという点でもお手軽です。ただ、どの説明も詳しく踏み込むと端折ってあるというのが困りもので、結局下記のような本が必要になってしまいます。
TCP/IP理解のためのバイブル的存在といわれましたが、絶版のようです。作者のW.Richard Stevens氏は昨年7月頃に亡くなってしまいました。この本のIPv6版を見てみたかったですねぇ。
上記の本と同じくらいの厚さですが値段は半額。シリーズになっていて、これが最もプロトコルの説明が詳しく載っています。PPPの説明はこれくらいしか載っているのを見つけられませんでした。
ソケットインターフェースについて勉強したくて買いました。ですがそれだけでなくて、UNIXでのプログラミングについて比較的易しく解説してあって入門編としてお薦めです。

戻る