(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.
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() の終りのほうでスレッドのディスパッチ処理を行う直前に
のようにして SIGALRM の発生を見ている部分(ここでなぜこーいうことをしているのかは,第14回を参照)で,エミュレータ上での動作が遅すぎるためにSIGALRM の設定後に上記処理が行われる手前で再度 SIGALRM が発生してしまい,うーんでも考えてみたら SIGALRM 発生時の処理はスレッドにメッセージ送信するだけなので,べつに問題ないような気がしてきた.うーん不明.まあそのうちちゃんと考えよう.
とりあえず第24回や第25回のソースをQEMUのようなエミュレータ上や動作の遅いPC上で動作させる場合には,以下の修正のようにして,INTERVALを調整してください.PCのスペックに対して値が小さすぎると落ちるようです.ぼくの環境ではINTERVALを100にすることでうまく動きましたが,PCの速度に応じて値を調整してください.INTERVAL をてきとうに増やしていって,しばらく動かしても Segmentation Fault にならないようになったところで動作させるといいでしょう.
で,さらに Linux 用に以下の修正を入れることで,無事動作することを確認できました.
Linuxでコンパイルするための修正(その2)
上の修正は第23回のソースに対する修正差分なのだけど,第25回のソースに同様の差分を当てることで(さらにINTERVALを調整することで)第25回のソースも動作することを確認した.あと上の差分を当てる場合には,LinuxやWindowsで動かすには?で配布している差分は当てる必要がありません.上の差分だけ当てればよいです.
ちなみに以下が,第23回と第25回のソースコードに対して,Linux 対応を入れたソースです.
オリジナルからの差分は diff.txt にあるので参考にしてほしい.
以下に,修正内容についてちょっと説明.
まず Linux では ualarm() によるタイマの設定は,1000000マイクロ秒未満,つまり1秒未満でないとダメらしいので,その対応.
clock スレッドの時刻表示を999ミリ秒ごとに行うように修正している.これは本来ならば setitimer() 使うように書き直さなければならないと思うのだけど,まあとりあえずこうしておく.ちなみに FreeBSD だと ualarm() のタイマの上限は 100000000000000 マイクロ秒なのでとくに問題にはならなかったようだが,まあ Linux 側と合わせてsetitimer() 使うようにそのうち修正しよう.
次に,(これはLinuxやWindowsで動かすには?ですでに行っている修正だが)ucontext_t によるコンテキストからスタブ側にレジスタ情報を渡す部分の修正.glibc に合わせて修正している.
さらに,テキスト領域を書き込み化にするための t2w に対して,Linux でコンパイルを通すための修正.t2w については第11回を参照.
あとはコンパイルを通すためとワーニング削るためのちょろちょろっとした修正がいくつか入っている.
で,以下が第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 でもちゃんと動いたね.よかったよかった.
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 でもちゃんと動いたね.よかったよかった.