【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

Virtual PCでネットワーク (trunk)

【追記】MINIX 3.1.6がリリースされました。VPCドライバが入っています。 ⇒ id:n7shi:20100208

MINIXのtrunkにはVirtual PCのNICドライバが追加されましたが、動作しなかったためパッチを作成しました。

【追記】r6050で修正されました。 ⇒ Revision 6050: improve behaviour under VPC, fixes from nicolas tittley.

--- src.orig/drivers/dec21140A/dec21140A.c
+++ src/drivers/dec21140A/dec21140A.c
@@ -85,7 +85,7 @@
     notify(tasknr);
   else if(r != ESRCH)
     printf("%s unable to notify inet: %d\n", str_DevName, r);
-  return r;
+  return OK;
 }

 /*===========================================================================*

本家にも報告済みです。 ⇒ バグレポート 【追記】受理されました。

trunkのビルド

今回はtrunk(私が試したのはr6042)を対象としますが、多少の修正で3.1.5でも動作します。trunkをビルドするのが面倒な方は次を参照してください。 ⇒ id:n7shi:20100131

SVNでtrunkをダウンロードして、VPCに持って行きます。VPCではネットワークが使えないため、他のOSでダウンロードしてCDイメージをVPCでマウントすると簡単です。手順はtyatsumiさんに教えてもらいました。

SVNのtrunkのsrcをVirtual PCにインストールしたMINIX 3.1.5に持っていくためにCDイメージを作る。

LinuxでCDイメージを作る。
適当にディレクトminix/ を作り、その中で作業する。
svn --username anonymous checkout https://gforge.cs.vu.nl/svn/minix/trunk/src
mkdir 6030
tar zcf 6030/src.tgz src
genisoimage -o minix6030.iso 6030

次にこのCDイメージをVirtual PCのCDメニューでセットした後、
MINIX 3上で
cd /usr
isoread /dev/c0d2 src.tgz > src.tgz
として読み込む。そしたら展開する。
mv src src.orig
/usr/local/bin/zcat src.tgz | tar -xf -

ちなみにCDからコピーしなくてもパイプで直接展開できます。

isoread /dev/c0d2 src.tgz | zcat | tar xf -

ビルド方法はid:n7shi:20100129を参照してください。

問題点

一応使用できますが、いくつか問題があります。ドライバのREADMEにも記載されていることから作者も認識していた問題だと思われます。そのため仕様と割り切るしかなさそうです。

  1. hostaddrが固まる。
  2. ブリッジモードではホストに影響が出る。NATモード推奨。
  3. NICの初期化に時間がかかることがある。

これらの問題を除けばそこそこ安定して動作します。私の環境ではVMware Playerをしばらく使っていると突然ネットワークが止まってしまうという現象が頻発しましたが、Virtual PCでは今の所その現象は発生していません。残念ながらVirtual PCでも発生しました。

問題 1. hostaddr

ドライバのREADMEにも記載されている問題ですが、hostaddrが固まります。/usr/etc/rcの修正が必要です。

--- /usr/etc/rc.orig
+++ /usr/etc/rc
@@ -151,9 +151,9 @@
     if [ "$net" ]
     then
        # Get the nodename from the DNS and set it.
-       trap '' 2
-       intr -t 20 hostaddr -h
-       trap 2
+       #trap '' 2
+       #intr -t 20 hostaddr -h
+       #trap 2
     fi

     # Recover files being edited when the system crashed.

この修正をしないとブートプロセスで固まるため、[Ctrl]+[C]で抜ける必要があります。

Starting networking: dhcpd nonamed.
^C ← ここで応答がなくなるため[Ctrl]+[C]で抜ける

Minix  Release 3 Version 1.6 (console)

READMEにはNATモードで発生するとありますが、ブリッジモードでもNICの初期化が間に合わないと発生します。そのためモードに関係なく修正しておいた方が無難のようです。

問題 2. ブリッジモード

VPCのブリッジドライバはホストのパケットまで見えてしまうようです。しかしMINIXは処理しきれずに以下のようなエラーを出します。

ip[0]: no route to (ホストのアドレス)
icmp[0]: dropping ICMP packet #xxx to xxx.xxx.xxx.xxx type 3, code 1
        info 00000000, original dst (ホストのアドレス), proto 6, length xxxx

これらのパケットは捨てられてしまうためホストのDNSなどに影響が出ます。具体的には長いURLのWebサイトに接続できないという問題が発生します。VPCを停止すると直るため、ホスト自体が壊れることはありません。

対処にはドライバではなくinetを修正する必要があると思われます。

NATモードではこのような問題は発生しませんが、Virtual PCの仕様でホストからゲストへの接続はできません。スタティックルーティングで抜け道ができないか試しましたが無理のようです。そのためNATモードでホストへファイルを送りたいときは、ホスト側でサーバーを立ててMINIXゲストから接続する形になります。

問題 3. NICの初期化

バイスの準備が出来るまで時間が掛かることがあるようです。最大15秒くらい掛かることがありましたが、失敗することはないようです。

初期化前にパケットを送ろうとするとエラーが出ます。

eth_set_rec_conf: setting OEPF_NEED_CONF, state = 5

DHCPに間に合わないことも多く、以下のようにエラーが割り込みます。

Starting networking: dhcpdeth_set_rec_conf: setting OEPF_NEED_CONF, stat
e = 5
 nonamed.

DHCPに成功したかどうかはifconfigで確認できます。失敗したときは以下のようになります。

# ifconfig
ifconfig: /dev/ip: Host address not set

この場合、dhcpdを手動で実行してください。プロンプトに返って来たら失敗です。返って来なくなるまで繰り返して、[Ctrl]+[C]で止めてIPアドレスを確認してください。

# dhcpd ← エラーが返って来た(失敗)
eth_set_rec_conf: setting OEPF_NEED_CONF, state = 5
# dhcpd ← すぐに返って来た(失敗)
# dhcpd ← 返って来なくなったので止めた(成功)
^C
# ifconfig ← IPアドレスを確認
/dev/ip: address xxx.xxx.xxx.xxx netmask 255.255.255.0 mtu 1500

経緯

パッチを作るまでの経緯を書きます。

当初trunkに上げるのが面倒だったので、3.1.5のままドライバだけ持って来てコンパイルが通るように修正したところ、簡単に動作しました。

しかしtrunkでは動作しません。そのため正常に動作するリビジョンを探しました。しかし正常に動作するリビジョンは存在しませんでした。ドライバのタイムスタンプが2008になっていることから考えて、以前のバージョンで作成したドライバをtrunkの仕様に合わせて修正した際にエンバグしたと思われます。

プロセッサエラー

色々なリビジョンを試していると、プロセッサエラーで起動しないものがありました。具体的にはr5707〜r5979が起動しません。r5980で修正されました。

--- trunk/src/commands/i386/gas2ack/emit_ack.c	2009/12/02 13:01:48	5707
+++ trunk/src/commands/i386/gas2ack/emit_ack.c	2010/01/25 16:25:20	5980
@@ -642,7 +642,7 @@
 		}
 		/* unsupported fnstsw */
 		if (a->opcode == FNSTSW) {
-			ack_printf(".data1 0xDF, 0xD0\n"); /* FNSTSW [eax] */
+			ack_printf(".data1 0xDF, 0xE0\n"); /* FNSTSW [eax] */
 			return;
 		}
 		/* unsupported frstor */

実機ではどちらのopcodeでも同じように動作しますが、VPCでは片方しかサポートしていないようです。VPCのプロセッサエラーはたまに見掛けますが、この手の非互換性は他にもあるかもしれません。

System Event Framework (SEF)

3.1.5とtrunkのドライバを比較して違いがあった部分がSEFです。

ドライバやサーバーを管理するためr5786で導入された仕組みです。詳細はコミットログを参照してください。

r5786の時点ではmain()でsef_startup()を呼んで、receive()をsef_receive()に置き換えるだけです。

r5861では初期化がコールバック経由で呼ばれるようになりました。そのためmain()内で行っていた初期化処理を関数として抽出して、sef_setcb_init_fresh()とsef_setcb_init_restart()に渡すようになりました。詳細はコミットログを参照してください。

dec21140AのSEFデバッグ

trunkのdec21140AはSEFを使うようになっていましたが、まったく動作しませんでした。SEFを外して3.1.5にバックポートすると正常動作したため、SEF関係で何か問題が発生しているようです。

試行錯誤した結果、初期化関数をコールバックせずに直接呼ぶと動作することがわかりました。

sef_setcb_init_fresh(sef_cb_init);
sef_setcb_init_restart(sef_cb_init);
sef_startup();
  ↓
/*sef_setcb_init_fresh(sef_cb_init);
  sef_setcb_init_restart(sef_cb_init);*/
sef_startup();
sef_cb_init(0, NULL);

コールバックを設定してしまうと、直接呼んでもダメでした。

条件が合わずにコールバックが呼ばれていないのかと思い確認しましたが、sef_startup()の中でsef_setcb_init_fresh()で設定したコールバックは呼ばれていました。直接呼ぶのとコールバック経由とでは何かが違うようです。

sef_startup()をインライン展開して確認しました。条件分岐は決め打ちでエラーメッセージを省略したものが以下です。

sef_startup();
  ↓
endpoint_t ep;
char buf[32];
message m;
sef_init_info_t info;
sys_whoami(&ep, buf, sizeof(buf));
receive(RS_PROC_NR, &m);
/* ここから do_sef_init_request() */
info.rproctab_gid = m.RS_INIT_RPROCTAB_GID;
m.RS_INIT_RESULT = sef_cb_init(m.RS_INIT_TYPE, &info);
asynsend(RS_PROC_NR, &m);

sys_whoami()を呼ぶとRSから初期化メッセージが来ます。これに対して初期化の成否をasynsend()で返しています。RS_INIT_RESULTで返した値を見ると-3でした。

sef_cb_init()の返り値を決めている部分を抽出すると以下のようになります。

int r;
endpoint_t tasknr;

/* Try to notify inet that we are present (again) */
r = ds_retrieve_label_num("inet", &tasknr);
if (r == OK)
  notify(tasknr);
else if(r != ESRCH)
  printf("%s unable to notify inet: %d\n", str_DevName, r);
return r;

RS_INIT_RESULTに代入された-3はESRCHです。他のドライバを見るとrの値に関係なくOKを決め打ちで返しています。どうやらOKを返さないとドライバが機能しないようです。

というわけでOKを決め打ちで返すようにしたのが冒頭のパッチです。これで無事に動作するようになりました。