(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.

KOZOSの改良なのだけど,まずは割り込みまわりを大幅に改造したい.

現状でKOZOSは,割り込み処理用に子プロセスを起動して,ソケットの select() を子プロセスにまかせている.ただしソケットは fork() 時に引き継がれるので,新しくソケットを生成した場合には,そのソケットは子プロセスから見れない.この対策として,ソケットを開くたびに子プロセスを終了,再起動している.

また,ソケットの生成や操作は基本的に各サービススレッド(telnetdとか)が勝手に行っており,子プロセスは select() 待ちと受信時の通知のみ行っている.accept()に関しても,子プロセスは通知をするだけで,実際の accept() はデーモンが各自行っている.まあ疑似OSという考えで,ソケットまわりはあまり深く考えずに実装してしまったのでこんなことになっているのだが,ちょっとあんまりな作りだ.

そもそもextintrや子プロセスは,ハードウエアの割り込み処理を疑似的に実現するのが本来の目的なので,ソケット周りの処理はすべて子プロセスにまかせて,各サービススレッドは文字列の受信割り込みと実際の送受信くらいが行えればよい.つまり,子プロセスが単なるシリアルコンソールのように見えてくれると,それっぽくていい感じだ.

ということでソケットのオープンやaccept(),select()類はすべて子プロセスにまかせるように改造する.各サービススレッドは,子プロセスから受信文字を受け取ったり,子プロセスに送信処理を依頼することで,文字データの送受信を行う.(実際には子プロセスとのやりとりのために,extintrスレッドが中継動作をしている)

ソケット関連の処理はすべて子プロセスに閉じて行われるので,各サービススレッドは,外部とのやりとりがソケットで行われていることは意識しない.つまり,ソケットでなくシリアルを利用するように子プロセスを書き換えれば,サービススレッド側は何の変更もなく,telnet接続からシリアル接続に変更することができる.

子プロセスは文字の送受信処理と割り込み発行を行うハードウエアを模している.通常の組み込みOSで,一番手軽な入出力デバイスはシリアルコンソールだ.ソケット処理をすべて子プロセスにすべて任せ,ソケットを意識させないことで,シリアルデバイスを模しているわけだ.

で,子プロセスとどうやって通信するかなのだが,これは専用のソケットを開いてしまうのがいいだろう.(外部からの接続のためのソケットと,内部通信用のソケットの違いに注意.ここで言っているのは内部通信用のソケットのこと)

通常の組み込みOSだと,これは(おそらくメモリ上にマッピングされた)ハードウエア操作用のレジスタを読み書きすることになるのだが,KOZOSでは親プロセスと子プロセスの間でソケット通信することで,子プロセス(疑似シリアルデバイス)を操作する.

子プロセスは外部に対してソケットをオープンし,待ち合わせる.外部からTCP接続要求を受けた際(つまり,kozosに対してtelnetした際)には子プロセスは accept() する.また文字データを受信した際には,受信データの通知用ソケットを通じて親プロセスにデータ送信し,親プロセスに対して SIGHUP を通知する.親プロセスはSIGHUPを受けたら,なんらかのデータを外部から受信したとして,受信処理を行う.

つまりKOZOSでは,以下の表のように組み込みOSの動作を模していることになる.
組み込みOSKOZOS
シリアルケーブルによる接続TCP/IPによる子プロセスへの接続(telnet接続)
シリアルからの受信telnet接続からの受信
シリアルへの送信telnet接続への送信
受信割り込み子プロセスから親プロセスへのSIGHUP
レジスタリードによる受信データの読み込み内部通信用ソケットからの受信データ読み込み
レジスタライトによるデータ送信内部通信用ソケットへの送信データ書き込み


で,改造したソースは以下のような感じ.まず親プロセス側(KOZOS本体側)ではソケットは一切扱わないので,telnetd スレッドは command スレッドに変更.httpdは廃止.command スレッドでは,ソケット処理は行わずに extintr から通知された受信データを扱うように修正している.

さらに extintr.c は,上述した作りにごっそり書き換えている.扱うソケット数は決めうちで4個,ポート番号はこれも決めうちで12345~12348としている.外部からはこのポートに(telnetなどで)TCP/IP接続できる.

あとGDB利用を考えるとまたややこしいので,stubdはとりあえず廃止.

今回の修正はほとんど extintr に対するものとなっている.処理を簡単に説明すると,まず子プロセスの fork() 時には親プロセスとの間に連絡用のソケットを開く.ソケットはコマンドによる子プロセスの操作,受信データの通知,送信データの通知用に3種類開いている.これを子プロセスが接続管理する4ポートぶん開いているので,それだけでも12本のソケットをオープンしている.(さらに外部との接続待ちや接続,全体処理用などのためにソケットを開いている)

command スレッドは,使用したい外部ソケットの番号を指定してuコマンドをextintrスレッドに発行する(このコマンド発行の実体は,KOZOSのメッセージである).extintrスレッドはuコマンドを受信すると,コマンドの発行もとのスレッドをおぼえておいて,子プロセスからデータ受信した際に,受信ソケットに対応するスレッドに対して(KOZOSのメッセージで)データ送信を行う.

図にするとこんな感じ.

(初期化時)

サービススレッド extintr 子プロセス 外部からの接続
(commandスレッドなど) (外部からのtelnet接続など)
| | | |
| | socket() |
| | | |
| uコマンド | listen() |
kz_send()=========>| | |
| kz_recv() select()待ち |
| | |<-----------connect()
kz_recv()待ち kz_recv()待ち accept() |
| | |<------------>|
| | | TCP/IP接続確立 |
| | | |
| | select()待ち |
| | | |

---> は,ソケットによる接続や通信
===> は,KOZOSのメッセージ送信

(文字データ受信時)

サービススレッド extintr 子プロセス 外部からの接続
(commandスレッドなど) (外部からのtelnet接続など)
| | | |
kz_recv()待ち kz_recv()待ち select()待ち |
| | | |
| | |<------------send()
| | read() データ送信 |
| | | |
| |<------------write() |
| |<======------SIGHUP |
| kz_recv()(※1) | |
| read() 割り込み無効化(※2) |
| | | |
|<===========kz_send() select()待ち |
kz_recv() | | |
| kz_recv()待ち | |
| | | |
| eコマンド | | |
kz_send()=========>| | |
| kz_recv() eコマンド | |
| write()----------->| |
| | read() |
| | 割り込み有効化 |
| | | |
| | select()待ち |
| | | |

※1 kz_setsig() システムコールにより,シグナル受信は
メッセージによりextintrに通知される.
※2 データ受信の際に当該の外部ソケットの割り込み無効化し
(具体的には,select() 用の fd_set のビットを落とす),
eコマンドによる通知で再度割り込みを有効にする.

(文字データ送信時)

サービススレッド extintr 子プロセス 外部からの接続
(commandスレッドなど) (外部からのtelnet接続など)
| | | |
| kz_recv()待ち select()待ち |
| wコマンド | | |
kz_send()=========>| | |
| kz_recv() | |
| write()---------->| |
| | read() |
| | write()---------->|
| | | read()
| | | |

データの受信時には,受信した外部ソケットを select() 時の fd_set から落とすことで,一時的に受信検出できないようにする.これは実際のシリアルデバイスで,データの受信時に割り込みを一時的に無効化することを模しているつもり.処理スレッド(commandスレッド)側では受信データをリードしたらeコマンドを発行することで,受信割り込みの蓋開けをする.

で,extintr.c では以下の関数がそれぞれの処理を行っている.
  • intr_controller() ... 子プロセスでの処理(select()待ち,accept()処理,外部からのデータ受信処理とSIGHUP発行処理,外部へのデータ送信処理,e/dコマンドによる割り込み有効化/無効化処理)
  • extintr_handler() ... extintr の子プロセスからの割り込み受信処理(SIGHUP受けて子プロセスからのデータ受信してスレッドに転送する)
  • extintr_mainloop() ... extintr のサービススレッドからのコマンド受け付け処理(uコマンド(ソケット利用)の受け付け処理,e/dコマンド(割り込み有効化/無効化)の受け付け処理,wコマンド(データ送信)の受け付け処理)
  • extintr_main() ... 子プロセスを fork() して,ソケット生成する処理
うーんちょっとややこしいけど,以前よりはすっきりした構造になったような.

ハードウエアが行うような処理は子プロセスのほうにまとめたので,子プロセスを疑似ハードウエアとして扱うことができるようになっている.KOZOS側では,(操作する先がメモリマップドレジスタかソケットかの違いはあるが)基本的にはいわゆる普通のシリアルコントローラを扱うような感じで子プロセスを操作できる.このため,KOZOSをそのままどこかのハードウエアに移植して,本当のシリアルデバイス利用するような場合にも,extintr.c をシリアルデバイス利用に書き換えるだけで対処できるはずだ.

動作確認についてはめんどうなのでここでは省略するが,とりあえず確認はとれている.次の課題は割り込みの優先度づけかなあ...