OSC2010Tokyo/Springが終わりました。KOZOSのブースに来ていただいた方、セミナーを聞いていただいた方、どうもありがとうございました。スタッフの方やその他コミュニティの方々もお疲れ様でした。


KOZOSに関してですが、書籍化にともない、FreeBSDだけでなくGNU/LinuxとWindows+Cygwinの環境でも開発できることを確認しています。


これは書籍のほうで説明してあるため、まあ出版を待ってくださいということなのですが、OSCでKOZOSに興味を持っていただいたかたで「試したい」というひとのために、開発環境について、書籍原稿から説明を部分的に抜粋・修正したものを本家ホームページ のほうで公開します。GNU/LinuxやWindowsで試してみたいというかたは、そちらを参照してください。


AD
KOZOSですがさすがに書籍にするのに開発環境がFreeBSDだけというのはさびしいので,FedoraとWindowsXPで動作確認した.以下,注意する点のメモ.

これでFreeBSD,GNU/Linux,Windowsのどれでも開発できることになりました.う~ん,すばらしい.可能ならばubuntuも(たぶん大丈夫だとは思うが)確認して,実績どりしておきたいなあ.

(共通)

・マイコンボード付属のCD-ROMに添付されているクロスコンパイラでコンパイルするには,ソースを何箇所か修正する必要あり.(アセンブラの.type指定を削除するのと,Makefile修正と,乗算命令を利用しないための構造体のサイズ調整と,リンカスクリプトでELF指定を削除する修正)
・マイコンボード付属のクロスコンパイラはCOFF形式の実行ファイルを吐くので,利用できない.まあCOFFをELFに変換すればいいしブートローダーの作成には使えるのだけど,ソースコード修正も必要になってしまうし,OSはELFでないとブートローダーがロードできないので,いっそのことbinutilsとgccをクロスビルドしたほうがよいと思うのでそうする.
・USBシリアルアダプタだとh8writeやFDTでのフラッシュ書き込みがうまくいかないときがある.本体に初めからシリアルコネクタが付属しているPCを使った方が確実.(これでけっこうハマッた)
・XMODEMでの転送で,開始前のNAK送信の周期が短いとエラーになりやすい.っていうか必ずエラーになってしまう(開始前のNAKが,エラー通知のNAKとして解釈されてしまうようだ.なんかへんなプロトコル).なので間隔をもっと開ける必要あり.またXMODEMでの転送時は,loadコマンドを実行したらモタモタしてないでさっさと開始するようにする(ファイル選択に時間がかからないよう注意).これもけっこうハマッた.

(Fedora特有)

・Fedora含む多くのLinuxディストリビューションでは,ホストgccが入っていない.なのでプログラミング環境を追加インストールする必要がある.(Fedoraだとシステムメニューからマウスで選択するだけで簡単にインストールできる.binutilsとgccをインストールする必要あり)
・GNU makeとかも無ければ追加インストールする.
・端末アプリにはminicomが標準で入っているので,そのまま利用できる.使いかたは,初めに minicom -s で起動してセットアップして,次からは minicom -o で起動する.フロー制御をOFFにする必要あり.ファイル転送の設定はいじる必要無し.Ctrl-A, zでヘルプ.ファイル転送はminicomでできる.

(Windows特有)

・マイコンボード付属のCD-ROMのcygwin環境を期待していたがクロスコンパイラがCOFF形式を出力するのでダメ.まあ*.mot作成するだけならできるのだけど,binutilsとgccを自前でビルドすることにする.
・ホストgccやmakeが必要だが,付属CD-ROMのcygwinをインストールするとgccとかmakeとかがひととおり入ってくれるのでこれを利用すると楽.ビルド方法はFreeBSDと同じだが,binutilsは ./configure に --disable-werrorが必要だった.
・h8writeはCD-ROMに付属しているのと,cygwinに入っているのと,CD-ROMに付属しているソースから自分でビルドするのと,インターネット上からソースを持ってきてビルドする,という4通りの方法がとれる.まあ,どれでもいい.
・h8writeが嫌ならFDTが使える.いちおう,これで書き込みして起動できることは確認できた.

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

LinuxやWindowsで動かすには?ではLinuxでの動作にちょっと挑戦してみましたが,失敗してました.

で,いろいろ試してよーやく Linux での動作確認がとれたので,Linux での動作について説明.

まずぼくの Linux 環境ですが,Windows 上で QEMU を動かして作った仮想PCにFedora Core 8 をインストールして使っています.これは動作が結構遅いのだけど,まあ実験用の環境なので速度はそれほど問わないのでいいかな,という感じで使っています.遅すぎてダメという人は,QEMU用のアクセラレータというものがあるようなので使ってみるといいでしょう.

で,LinuxやWindowsで動かすには?では,Linux上で動作させたら Segmentation Fault で落ちてしまいまともに動かなかったのですが,main.c の INTERVAL の値を増やすことで回避できるようになりました.どうもエミュレータ上での動作のために動作速度がとっっっても遅かったことが原因だったようです.予想される原因なのですが,thread.c の thread_intrvec() の終りのほうでスレッドのディスパッチ処理を行う直前に

on_os_stack = 1;
sigprocmask(SIG_UNBLOCK, &block, NULL);
sigprocmask(SIG_BLOCK, &block, NULL);
on_os_stack = 0;

のようにして SIGALRM の発生を見ている部分(ここでなぜこーいうことをしているのかは,第14回を参照)で,エミュレータ上での動作が遅すぎるためにSIGALRM の設定後に上記処理が行われる手前で再度 SIGALRM が発生してしまい,うーんでも考えてみたら SIGALRM 発生時の処理はスレッドにメッセージ送信するだけなので,べつに問題ないような気がしてきた.うーん不明.まあそのうちちゃんと考えよう.

とりあえず第24回や第25回のソースをQEMUのようなエミュレータ上や動作の遅いPC上で動作させる場合には,以下の修正のようにして,INTERVALを調整してください.PCのスペックに対して値が小さすぎると落ちるようです.ぼくの環境ではINTERVALを100にすることでうまく動きましたが,PCの速度に応じて値を調整してください.INTERVAL をてきとうに増やしていって,しばらく動かしても Segmentation Fault にならないようになったところで動作させるといいでしょう.

diff -ruN kozos25.orig/main.c kozos25/main.c
--- kozos25.orig/main.c 2007-12-05 22:16:58.000000000 +0900
+++ kozos25/main.c 2007-12-08 14:19:44.000000000 +0900
@@ -12,7 +12,7 @@
char buf[128];
char *p = buf;

-#define INTERVAL 5 /* CPU能力に応じて調整してください */
+#define INTERVAL 100 /* CPU能力に応じて調整してください */
while (1) {
count = 0;
kz_timer(INTERVAL - argc);

で,さらに Linux 用に以下の修正を入れることで,無事動作することを確認できました.

Linuxでコンパイルするための修正(その2)

上の修正は第23回のソースに対する修正差分なのだけど,第25回のソースに同様の差分を当てることで(さらにINTERVALを調整することで)第25回のソースも動作することを確認した.あと上の差分を当てる場合には,LinuxやWindowsで動かすには?で配布している差分は当てる必要がありません.上の差分だけ当てればよいです.

ちなみに以下が,第23回と第25回のソースコードに対して,Linux 対応を入れたソースです.

オリジナルからの差分は diff.txt にあるので参考にしてほしい.

以下に,修正内容についてちょっと説明.

まず Linux では ualarm() によるタイマの設定は,1000000マイクロ秒未満,つまり1秒未満でないとダメらしいので,その対応.

diff -ruN kozos23.orig/clock.c kozos23/clock.c
--- kozos23.orig/clock.c 2007-12-04 22:43:50.000000000 +0900
+++ kozos23/clock.c 2007-12-08 15:01:07.000000000 +0900
@@ -13,7 +13,7 @@
char *p;

while (1) {
- kz_timer(1000);
+ kz_timer(999);
kz_recv(NULL, NULL);

t = time(NULL);

clock スレッドの時刻表示を999ミリ秒ごとに行うように修正している.これは本来ならば setitimer() 使うように書き直さなければならないと思うのだけど,まあとりあえずこうしておく.ちなみに FreeBSD だと ualarm() のタイマの上限は 100000000000000 マイクロ秒なのでとくに問題にはならなかったようだが,まあ Linux 側と合わせてsetitimer() 使うようにそのうち修正しよう.

次に,(これはLinuxやWindowsで動かすには?ですでに行っている修正だが)ucontext_t によるコンテキストからスタブ側にレジスタ情報を渡す部分の修正.glibc に合わせて修正している.

diff -ruN kozos23.orig/stublib.c kozos23/stublib.c
--- kozos23.orig/stublib.c 2007-12-04 22:43:50.000000000 +0900
+++ kozos23/stublib.c 2007-12-08 14:21:48.000000000 +0900
@@ -3,6 +3,7 @@
#include
#include

+#define __USE_GNU
#include "kozos.h"
#include "thread.h"
#include "stublib.h"
@@ -101,42 +102,42 @@
{
memset(registers, 0, sizeof(registers));

- registers[EAX] = thp->context.uap.uc_mcontext.mc_eax;
- registers[ECX] = thp->context.uap.uc_mcontext.mc_ecx;
- registers[EDX] = thp->context.uap.uc_mcontext.mc_edx;
- registers[EBX] = thp->context.uap.uc_mcontext.mc_ebx;
- registers[ESP] = thp->context.uap.uc_mcontext.mc_esp;
- registers[EBP] = thp->context.uap.uc_mcontext.mc_ebp;
- registers[ESI] = thp->context.uap.uc_mcontext.mc_esi;
- registers[EDI] = thp->context.uap.uc_mcontext.mc_edi;
- registers[PC] = thp->context.uap.uc_mcontext.mc_eip;
- registers[PS] = thp->context.uap.uc_mcontext.mc_eflags;
- registers[CS] = thp->context.uap.uc_mcontext.mc_cs;
- registers[SS] = thp->context.uap.uc_mcontext.mc_ss;
- registers[DS] = thp->context.uap.uc_mcontext.mc_ds;
- registers[ES] = thp->context.uap.uc_mcontext.mc_es;
- registers[FS] = thp->context.uap.uc_mcontext.mc_fs;
- registers[GS] = thp->context.uap.uc_mcontext.mc_gs;
+ registers[EAX] = thp->context.uap.uc_mcontext.gregs[REG_EAX];
+ registers[ECX] = thp->context.uap.uc_mcontext.gregs[REG_ECX];
+ registers[EDX] = thp->context.uap.uc_mcontext.gregs[REG_EDX];
+ registers[EBX] = thp->context.uap.uc_mcontext.gregs[REG_EBX];
+ registers[ESP] = thp->context.uap.uc_mcontext.gregs[REG_ESP];
+ registers[EBP] = thp->context.uap.uc_mcontext.gregs[REG_EBP];
+ registers[ESI] = thp->context.uap.uc_mcontext.gregs[REG_ESI];
+ registers[EDI] = thp->context.uap.uc_mcontext.gregs[REG_EDI];
+ registers[PC] = thp->context.uap.uc_mcontext.gregs[REG_EIP];
+ registers[PS] = thp->context.uap.uc_mcontext.gregs[REG_EFL];
+ registers[CS] = thp->context.uap.uc_mcontext.gregs[REG_CS];
+ registers[SS] = thp->context.uap.uc_mcontext.gregs[REG_SS];
+ registers[DS] = thp->context.uap.uc_mcontext.gregs[REG_DS];
+ registers[ES] = thp->context.uap.uc_mcontext.gregs[REG_ES];
+ registers[FS] = thp->context.uap.uc_mcontext.gregs[REG_FS];
+ registers[GS] = thp->context.uap.uc_mcontext.gregs[REG_GS];
}

void stub_restore_regs(kz_thread *thp)
{
- thp->context.uap.uc_mcontext.mc_eax = registers[EAX];
- thp->context.uap.uc_mcontext.mc_ecx = registers[ECX];
- thp->context.uap.uc_mcontext.mc_edx = registers[EDX];
- thp->context.uap.uc_mcontext.mc_ebx = registers[EBX];
- thp->context.uap.uc_mcontext.mc_esp = registers[ESP];
- thp->context.uap.uc_mcontext.mc_ebp = registers[EBP];
- thp->context.uap.uc_mcontext.mc_esi = registers[ESI];
- thp->context.uap.uc_mcontext.mc_edi = registers[EDI];
- thp->context.uap.uc_mcontext.mc_eip = registers[PC];
- thp->context.uap.uc_mcontext.mc_eflags = registers[PS];
- thp->context.uap.uc_mcontext.mc_cs = registers[CS];
- thp->context.uap.uc_mcontext.mc_ss = registers[SS];
- thp->context.uap.uc_mcontext.mc_ds = registers[DS];
- thp->context.uap.uc_mcontext.mc_es = registers[ES];
- thp->context.uap.uc_mcontext.mc_fs = registers[FS];
- thp->context.uap.uc_mcontext.mc_gs = registers[GS];
+ thp->context.uap.uc_mcontext.gregs[REG_EAX] = registers[EAX];
+ thp->context.uap.uc_mcontext.gregs[REG_ECX] = registers[ECX];
+ thp->context.uap.uc_mcontext.gregs[REG_EDX] = registers[EDX];
+ thp->context.uap.uc_mcontext.gregs[REG_EBX] = registers[EBX];
+ thp->context.uap.uc_mcontext.gregs[REG_ESP] = registers[ESP];
+ thp->context.uap.uc_mcontext.gregs[REG_EBP] = registers[EBP];
+ thp->context.uap.uc_mcontext.gregs[REG_ESI] = registers[ESI];
+ thp->context.uap.uc_mcontext.gregs[REG_EDI] = registers[EDI];
+ thp->context.uap.uc_mcontext.gregs[REG_EIP] = registers[PC];
+ thp->context.uap.uc_mcontext.gregs[REG_EFL] = registers[PS];
+ thp->context.uap.uc_mcontext.gregs[REG_CS] = registers[CS];
+ thp->context.uap.uc_mcontext.gregs[REG_SS] = registers[SS];
+ thp->context.uap.uc_mcontext.gregs[REG_DS] = registers[DS];
+ thp->context.uap.uc_mcontext.gregs[REG_ES] = registers[ES];
+ thp->context.uap.uc_mcontext.gregs[REG_FS] = registers[FS];
+ thp->context.uap.uc_mcontext.gregs[REG_GS] = registers[GS];
}

int stub_proc(kz_thread *thp, int signo)

さらに,テキスト領域を書き込み化にするための t2w に対して,Linux でコンパイルを通すための修正.t2w については第11回を参照.

diff -ruN kozos23.orig/t2w.c kozos23/t2w.c
--- kozos23.orig/t2w.c 2007-12-04 22:43:50.000000000 +0900
+++ kozos23/t2w.c 2007-12-08 14:24:27.000000000 +0900
@@ -6,6 +6,15 @@
#include
#include

+typedef Elf32_Ehdr Elf_Ehdr;
+typedef Elf32_Phdr Elf_Phdr;
+
+#define IS_ELF(ehdr) ( \
+ ((ehdr).e_ident[EI_MAG0] == ELFMAG0) && \
+ ((ehdr).e_ident[EI_MAG1] == ELFMAG1) && \
+ ((ehdr).e_ident[EI_MAG2] == ELFMAG2) && \
+ ((ehdr).e_ident[EI_MAG3] == ELFMAG3))
+
int main(int argc, char *argv[])
{
int fd, i;

あとはコンパイルを通すためとワーニング削るためのちょろちょろっとした修正がいくつか入っている.

で,以下が第23回のソースを Linux (Fedora Core 8)で動作させたときのスクリーンショット.

画像はこちら

実行形式を起動,gdbで接続し,さらに telnet 接続して break コマンドを実行し,ブレークしたところ.ブレーク前までは時刻表示が行われていること,telnet が break コマンドを発行したところで停止していること,gdbでブレーク位置のソースが表示できていることに注目してほしい.

ちなみに注意として,gdbでの接続,telnet での接続はどちらもLANインターフェースのアドレス(QEMUではDHCPで10.0.2.15が渡される)ではうまくいかず,ループバックアドレス(127.0.0.1)でうまくいった.

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

KOZOSとは?でも書いたように,KOZOSはFreeBSD上で動作します.

しかし,まあぼくみたいに普段から FreeBSD をメインOSとして利用しているならばそれでもいいのだけど,KOZOSを試してみるためにわざわざFreeBSDをインストールしなければならないというのは,やっぱり面倒です.ここでは他OS上で動かす方法についてちょっと説明します.

FreeBSD以外のOS上で動かそうとするならば,いくつか選択肢があると思います.
  • Windows で cygwin 上で動かす.
  • Linux 上で動かす.
  • VMwareなどのエミュレータを利用して,FreeBSD環境を仮想的に作る.
まず cygwin についてですが,見てみたところ setjmp()/longjmp() は利用できるようですが,getcontext() は使えないみたいです.なので,第12回以降のソースコードは利用できません.

次に Linux ですが,やはり多少の修正が必要になるようです.とくに setjmp()/longjmp() 利用のコードでは,jmp_buf の構造が違うのでなんか適当に修正しないとダメでしょう.あと getcontext()/setcontext() を使ったソースについてはちょっとぼくのほうでも試してみたのですが,とりあえず第25回のソースに以下の修正を加えることでコンパイルはできました.

Linuxでコンパイルするための修正

が,実行してみても Segmentation Fault で落ちてしまい,まともに動きませんでした.ちょっと詳しくは調べてないのですが,ということで Linux の動作も現状,確認できていません.ちなみに上で試したディストリビューションは,Fedora Core 8 でした.

※ もしも,Linuxで動いたというかたがいればぜひ連絡ください.

で,どうするかなのですが,エミュレータを使うのがとりあえず手軽で確実だと思います.QEMUというフリーのPCエミュレータがあって,Windows などのOS上に仮想PCを作成し,そこに FreeBSD をインストールして動かすことができます.ぼくも使ってみましたが,実行速度さえ気にしないのならば,何の問題もなく確実に動きます.

QEMUについては,以下を参照してください.

で,本来ならばここでインストール済みのHDDイメージを配布しようかなーなんて思ってたのですが,ためしにインストールしてみたら,最小構成でもやっぱしそれなりのサイズになってしまったのであきらめました.まあ FreeBSD のインストールはそんなに難しくないので,各自,やってみてくださいな.

参考までに,Windows 上で QEMU で FreeBSD 環境を作成する手順は以下の通り.FreeBSDのインストール方法に関しては,まあ他にも探せばいっぱい出てくると思うので,てきとうになんか参考にしてほしい.
  1. FreeBSDのインストール用CD-ROMを作成しておく.
  2. Windows にQEMUをインストールする.
  3. 空のHDDイメージを作成する.(DOSプロンプトで実行)

    qemu-img create -f qcow freebsd.qcow 8G

  4. CD-ROMからブートし,HDDにインストールする.(DVDも同様の操作で利用可能)(e:のところには,CD-ROMのドライブ名を指定する)

    qemu -L . -m 256 -boot d -cdrom //./e: -hda freebsd.qcow -localtime

  5. インストールが終了したら,HDDイメージからブートすることでFreeBSDが利用可能.

    qemu -L . -m 256 -hda freebsd.qcow -localtime

ちなみにCD-ROMを利用したい場合には,以下のオプションをつけて起動する.CD-ROMがE:ドライブならば

-cdrom //./e:

あと snapshot モードというのがあって,起動時に -snapshot オプションを指定すると,HDDイメージに書き込まないで動作する.実験用で,常に同じ状態で立ち上げたい場合には便利.

あとQEMUは,Cirrus Logic GD5446ビデオカードをエミュレートするので,Xのドライバに cirrus を指定することでXも利用できる.具体的には,/usr/X11R6/lib/X11/xorg.conf でドライバの指定部分を以下のように書けばよい.

# Driver "i810"
# VendorName "Intel Corporation"
# BoardName "945G Integrated Graphics Controller"
# BusID "PCI:0:2:0"
Driver "cirrus"

あとQEMUでは,最低限,以下の操作を覚えておくといいだろう.
  • マウスカーソルをウィンドウ内に出し入れする ... Ctrl + Alt
  • フルスクリーンで使用する ... Ctrl + Alt + f
  • 終了する ... Ctrl + Alt + 2 でモニタに入って quit する.
ネットワークは,普通に使えば DHCP でアドレス取って,telnet, ftp, ssh などを使って仮想PCから外に繋げることができます(内部で仮想的にDHCPサーバ,DNSサーバ,NATなどが動作しています).ただし仮想PCから外に対して ping は通らないので注意.僕はこれでハマりました...あと外から中に繋げるには別途設定が必要.あとftpはうまく繋がらなかったらPASSIVEモードでやってみること(もしくはsftpを使う).

使ってみて思ったけど,こーいう実験のためには,仮想PCはいろいろと融通が効いて,とっても便利で良いです.実は Linux (Fedora Core 8)上での動作実験も,Windows 上のQEMU環境で行いました.おすすめ!