フルスクラッチでWebサーバを書いてみた

フルスクラッチっていうのは、既存のOSの上に書くとかでは無くて、言ってしまえばOSから作った様なものです。
結果から言うと、Webブラウザでアクセスすると苺ましまろのリンクが出るとかっていう程度のものは作れました。
3-wayhandshake出来ますし、FINも裁けます。まぁ"何とか動く"程度にして満足したので止めましたけど...


今度の自分の為のメモとして。もしかしたら他の人の参考に成るかも。

何故そんな事をしたのか?

まず、先日のエントリにも書いた通りだが圧倒的にやる事が無かったから。虚しい毎日。これはWebサーバを書く事で解消されるに違いないとかっていう事では無くてですね。


こうカーネルとのオーバーヘッドがどうこうで、どの処理をするのにどのシステムコールを使って云々。
スレッドとイベントはどうこうで、たくさん過ぎるetcetc


皆さん大変だなーと。でもまぁ用途が決まってるんだったらKernelの上でWebサーバ書くんじゃなくて、Kernelと結合した様なのを作れば良いんじゃないでしょうか、と思ったのでやってみたまで、です。


本当は既存のWebサーバをけちょんけちょんにするような性能が出せると良かったのですが色々としんどく成って来たので、今回はここでおしまい。

何をどうしたの?

OSごと作ったと述べましたが、その前に開発を行った環境を述べたり。


私は現在Leopardを使っているのですが、こいつの上であんまり良く無い事をすると大変良く無いというのと、どうもTAPを作るのは良いもののbridgeの仕方が分からんので、とか色々あってVirtualBoxUbuntuを入れて開発していました。


作った物を動かすのはBochsQEMUで。前半本当にOSっぽい事をやる(割り込みをどうたらとか)あたりはbochsをdebuggerとして活用しました。
本当にBochsは良いものです。でBochsでなんか上手く行かない所が出て来たので途中からQEMUにシフトしました。


さて、今回は直にNICを叩く必要があったのですが、これからの事も考えるとISAなNICをやる価値も見いだせなかったので、PCINICを叩きました。BochsQEMUでは運良くも...というかNE2000系のPCINICであるRTL8029ASをサポートしているので、思いっきりこいつを使いました。


紆余曲折を経て、イーサフレームを吐き出せる様になって、パケットキャプチャツールで自作OSというかWebServerから吐かれたARPを見たのが書き始めて1週間経った時点でした。


まぁWebServerを書きたいので、それだけを狙うならば本来IPv4TCPを実装すれば良かっただけなのですが、折角なのでICMPとUDPも実装してみました、という話です。


まぁプロトコルスタックを大体書いたとかそれぐらいですか。
ちなみに生まれて初めてプロトコルスタックを書きました。しかもその下で動いてるのが自作のNICドライバってんだから中々面白いものですね。

苦労した点

まず、何も無いCを使ったので、ただでさえC++に比べるとライブラリ的な意味でアレなCが更にアレでした。
データ構造と言えばあるものは配列とstructのみ、今回はprocessを作ったりするのも面倒臭かったので、Single Process Event-Driven OS+Web Serverという事にしました。


Single Processというのは文字通り、1つのプロセスがCPUを独占します。Event-Drivenなのはどこかというと、NICの割り込みです。
NIC実装の戦略としては割り込みとポーリングがあります。ですが今回は1つのプロセス(Webサーバ)しか無い訳なのでポーリングするのはあまりに賢く無いのと、やりづらい。


主な戦略としては、TaskQueueというのを勝手に考えて使いました。
NICの割り込みハンドラがTaskQueueに受信したフレームを処理する様な関数をぶち込みます。でメインループはひたすらTaskQueueからpopしてその関数を実行するわけです。まぁ妥当なモデルかなと。


ここで問題なのが、IPv4を使って送る時に、宛先IPアドレスが同一サブネット内(ちなみにDHCPは実装していないのでこれはハードコーディングですwww)にあるがARPCacheには無い...!!という状況でARPを打った後にこのIPv4を送り出す関数"自身"をTaskQueueに入れたいというシーンが出ました。


もともと、TaskQueueはvoid (*func)()というまぁこれで良いだろうという関数ホルダだったわけですが、このIPv4を送り出す関数の引数がvoidではないとかで、コレは困ったなぁという状況になったり。


その時思いついたのが、自分自身をTaskQueueに入れるこいつは、適切な引数を知っている(当然の話です)のだから、それを使ってカリー化してvoid (*func)()にしちまえば良いんじゃないかとか思ったのですが、gccのスタックの扱いが可変なのでそれはまぁ無理だなーとかいう事で、void (*func)(void*)を使い、自分で疑似スタックを(void*)として引数にぶち込んでそれを後で復元させるという手法を使いました。
詳しく書くと

//wrap_transIPをTaskQueueに入れる
void wrap_transIP(void *pointer)
{
    //pointerから引数を"復元"する処理
    transIP(tbi,dstip,tos,flag);
}

//transIPは型的な意味でTaskQueueに入れられない!!
int transIP(struct TRANS_BUF_INFO tbi,uint32_t dstip,uint8_t tos,int flag)
{
    宛先IPアドレスがARPCacheに無い場合、ARPを打った後に
  自分自身をTaskQueueに入れる。
  もしARPをこの関数の中で打って、
  forループでCacheに入るのをポーリングする場合
  そもそもSingle-ProcessなのでNICがTaskQueueにpushする
  イベントハンドラが"実行"されない => ARPCacheが更新されない
  よって無限ループ
}

とまぁこういう訳です。


Stackともっと親和性が高いようなCはあばばという話はまた別で。
ちなみにC言語でカリー化しようと固執していたのはこの記事のおかげです。http://nicosia.is.s.u-tokyo.ac.jp/pub/essay/hagiya/h/curry


後もう1つ。それはエンディアンのお話です。
今回x86系をBochs/QEMUで使ったので、ネットワークエンディアンにし忘れるというミスを何度もしてしまいました。


こうこういうのでは幽霊型とか...別に幽霊型とかじゃなくてエンディアンを型(とかメタ情報)として押し込む事が出来ればなーとか思ったんですが、C言語は型はあってもなくても...という感じなのでしょうがないですね。

C言語について思った事

Cが素晴らしいのは、メモリをこねくり回すのが非常に得意という事です。得意というか、静的型付けではやりにくい事を動的型付けで出来る時と同じような感触があります。ありますというか再認識しました。いや、もしかしたら初認識かも。


void*(じゃなくても良いですが)に押し込んで、復元するのはカリー化よりもあんま良さげでは無いので悔しかったのですが、まぁ満足は出来ました。


まぁ結局はメモリだろう...と。


後はやっぱりまぁこいつはスタックがついて回るのに、某アセンブリ言語さんとかと比べるとスタックとの親和性は良く無いよなーという。アセンブリではスタックはfirst class object(?)なのにうん。


それとまぁ、Functional OrientedなCを作るとかいう事に成ってくると、C++とかCmm(C--)とかとかC#は取られてるので、Cx(Cダブルシャープ)とかに成るんでしょうかねぇ・・・という。


途中興味を持ったのは、
http://logic.cs.tsukuba.ac.jp/~kam/Continuation2008/abstract.html#kono
あたりです。継続がらみの話なんですが、これは面白そう。

まとめ

MonaOSPCINIC(RTL8029AS)周りのコードがコンパクトで非常に参考に成りました。


後非常に重宝した本を紹介したいと思います。
http://www.amazon.co.jp/Ethernet%E3%81%AE%E3%81%97%E3%81%8F%E3%81%BF%E3%81%A8%E3%83%8F%E3%83%BC%E3%83%89%E3%82%A6%E3%82%A7%E3%82%A2%E8%A8%AD%E8%A8%88%E6%8A%80%E6%B3%95%E2%80%95%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB%E3%81%AE%E8%A9%B3%E7%B4%B0%E3%81%8B%E3%82%89%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E5%AF%BE%E5%BF%9C%E6%A9%9F%E5%99%A8%E3%81%AE%E4%BD%9C%E6%88%90%E3%81%BE%E3%81%A7-TECHI%E2%80%95Bus-Interface-%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E7%B7%A8%E9%9B%86%E9%83%A8/dp/4789833437/ref=sr_1_1?ie=UTF8&s=books&qid=1235054335&sr=8-1
RTL8019AS(こっちはISA版です。が8029ASと同じ感覚で使えます。)の詳しい説明があります。


他にもASIX AX88796シリーズ、SMSC LAN91C111、SMSC LAN9118、AMD Am79C97x、Intel i8255xなど。
他にもEthernetの基礎知識、内部インタフェース、FPGAEthernetコントローラを作る、やWin/Linuxでのデバイスドライバの作り方など盛りだくさんでした。


RTL8029ASを叩くのにはもちろん、大変勉強になる書籍でした。


後はosdevのwikiですね。特に8259AやPCIは非常に役に立ちました。ありがとうございました。


お粗末ですが、スクリーンショットでも張っておこうと思いますw


Cute is Justice!!とIchigo Mashimaro Encore!!の文字が出とります。ちっこいなぁ・・・。


苺ましまろ6巻は2/27に発売するらしいので、生きてて良かったです。

etc

後、ブートローダ書くのは流石に本義では無いと思ったので、初めてGrub-Legacyを使ってみました。
基本的にはkernel.cにどんどん継ぎ足して行った感じです。