advance/FPU
の編集
http://hrb.osask.jp/wiki/?advance/FPU
[
リロード
|
差分
|
単語検索
|
一覧
] [
編集
|
バックアップ
|
添付
]
-- 雛形とするページ --
A
Akkie
Athlon64X2
Clover
DAsoran
Falcon
FormatRule
FrontPage
Genesis
Help
I
InterWikiName
InterWikiSandBox
InterWikiテクニカル
Jormungand
K
Kebo
Kor_Lee_Hee_Rak
Leaf
Linux
Linux/wako_memo
MOIZ99
MW
MenuBar
OSC
PG_MANA
ReadersOS
RecentDeleted
SKYDASH
SandBox
Sero
Sigle
Source
Triangle_Ld.
Zxcvbnm
advance
advance/CPU
advance/FDC
advance/FPU
advance/NotHariMain
advance/QEMUVGA
advance/RTC
advance/blike
advance/cpu_reset
advance/driver
advance/driver/01
advance/driver/02
advance/families
advance/filesystem
advance/fwrite
advance/hddboot
advance/he86
advance/hints
advance/ipl
advance/kernel
advance/keycode
advance/osselect
advance/smaller1
advance/startup
advnace/smaller2
anzy
aotatsu
banbi-
bluedwarf
bo
bugs
challengers
cybozulabsyouth11
deskmanta
esb02b
faq
faq/advance
faq/asm
faq/c00-03
faq/c04-07
faq/c08-15
faq/c16-23
faq/c24-31
faq/make
faq/others
faq/qemu
guide
guide03
guide05
guide07
hikarupsp
imp_log/0000
imp_log/0001
imp_log/0002
imp_log/0003
impressions
index
k
killer_elf
kota
lea
lea/10_memory
lea/4_color
lea/idea
lea/terms
links
logs
logs/osa_hrb/comments0000
logs/osa_hrb/rumors0000
masa
members
message
mistakes
moge32
moppoi5168
notice
osdevjp
populars
prog_index
projects
q_and_a
q_and_a_2
qa_log/0000
qa_log/0001
qa_log/0002
qa_log/0003
qa_log/0004
qa_log/0005
qa_log/0006
qa_log/0007
qa_log/0008
qa_log/0009
quark
rankings
rule
sakamoto
sasaki
spc09
spcc_30min_os
tatsu
tools
tools/bim2hrb
tools/bin2obj
tools/cc1
tools/edimg
tools/gas2nask
tools/makefont
tools/nask
tools/obj2bim
tools/sjisconv
uchan
uho
updates
violations
wako
white
win64-bit
x
ytakano
ヘルプ
リックス
質問します
整形ルール
本は買ったぞ!持ってるぞ!
練習用ページ
* FPUを使えるようにしよう! -(by [[K]], 2006.09.30) -発展課題のページ(全部読む前にここを読むのは混乱するのでおすすめじゃないです) ---- -30日目までの「はりぼてOS」のままでは、1.23や3.14などの小数を含む計算、つまりfloatやdoubleの計算ができません。タスク切り替え時のFPUレジスタの保存・読み込みに対応できていないためです。FPUというのは、floatやdoubleに関する計算を一手に引き受けるCPU内部の回路のことです(昔はCPUとは独立した半導体になっていましたが、486DX以降はCPU内に組み込まれました)。 -なお、このページのとおりにしても、387を拡張していない386機や486SX機ではFPUがないので、floatやdoubleは使えません。 *** 仕組み -タスク切り替え部分にFPUレジスタの保存と読み込みを付ければいいだけだろうと思うかもしれませんが、実はそうではありません。その方法だとタスク切り替え時間が長くなってしまい、ダメなOSになってしまいます。 -FPUのレジスタは80ビットのものが8本もあって、それに制御用のレジスタがいくつかついて、結果として合計108バイトを保存したり読み込んだりすることになるのですが、こんなたくさんのデータをタスク切り替えごとに読み書きしていたら、タスク切り替え時間が倍増してしまうのです。 -そもそも今までdoubleやfloatを使わなくても何とかやってこれたわけで、これは逆にいえば、FPUなんかめったに使わないのです(だから386までは別売りオプションだったわけですしね)。そのとき動いているすべてのタスクを見回してみても、FPU命令を使うタスクなんてきっと数個でしょう。もしかしたら1個かもしれません。もし1つしかないとしたら、そもそもタスク切り替えのたびにFPUレジスタを切り替える必要なんて全くありません(たとえそのときの総タスク数が100個だとしてもです)。・・・というか0個の場合が最も多いかもしれません。0個ならもちろんFPUレジスタの切り替えも不要です。 -ということでムダなFPUレジスタの保存や読み込みをしないようにするためにいろいろ工夫するのですが、CPUのほうもOSが当然そういう工夫をするだろうということでそれを前提に命令が用意されています。 ---- - struct TASK の中に int fpu[108 / 4]; を追加。これはタスクがFPUレジスタを使った場合の保存先・読み込み元。 - struct TASKCTL の中に int tss_fpu; を追加。これは現在のFPUレジスタがどのタスクに所属しているものなのかを記憶させておくためのもの。 -CPUはタスクスイッチをするたびに、自動でCR0レジスタ内のTSビットを1にする(これは今までもやっていた)。 -CPUはFPU命令を実行するにあたり、TSビットをチェックする。もしTSビットが1だったら、FPU命令は実行せずに、INT(0x07);の例外を起こす(これも特に設定しなくてもやってくれる)。 -OSはこのINT07の発生を確認したら、tss_fpuの値と現在のTRレジスタの値を比較する。もしこれが一致したら、FPUレジスタの切り替えの必要はないので、TSビットを0にするだけで他には何もせずにIRETDする。一致しない場合は、tss_fpuの値で示されるタスクのfpu[]へレジスタデータを保存し、現在のTRのfpu[]を読み込み、その後にTSビットを0にしてIRETDする。 *** 改造点 -bootpack.h (中略) /* naskfunc.nas */ (中略) void clts(void); void fnsave(int *addr); void frstor(int *addr); void asm_inthandler07(void); (中略) struct TASK { (中略) struct TSS32 tss; int fpu[108 / 4]; /* ココ! */ struct SEGMENT_DESCRIPTOR ldt[2]; (中略) }; (中略) struct TASKCTL { int now_lv; /* 現在動作中のレベル */ struct TASK *task_fpu; /* ココ! */ char lv_change; /* 次回タスクスイッチのときに、レベルも変えたほうがいいかどうか */ (中略) }; (中略) void task_sleep(struct TASK *task); int *inthandler07(int *esp); /* ココ! */ (中略) ---- -naskfunc.nas (中略) GLOBAL _clts, _fnsave, _frstor, _asm_inthandler07 EXTERN _inthandler07 _clts: ; void clts(void); CLTS RET _fnsave: ; void fnsave(int *addr); MOV EAX,[ESP+4] ; addr FNSAVE [EAX] RET _frstor: ; void frstor(int *addr); MOV EAX,[ESP+4] ; addr FRSTOR [EAX] RET _asm_inthandler07: STI PUSH ES PUSH DS PUSHAD MOV EAX,ESP PUSH EAX MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler07 CMP EAX,0 JNE _asm_end_app POP EAX POPAD POP DS POP ES IRETD ; INT07では ESP += 4; はいらない ---- -mtask.c (中略) struct TASK *task_init(struct MEMMAN *memman) { (中略) task_run(idle, MAX_TASKLEVELS - 1, 1); taskctl->task_fpu = 0; /* ココ! */ return task; } struct TASK *task_alloc(void) { (中略) for (i = 0; i < MAX_TASKS; i++) { if (taskctl->tasks0[i].flags == 0) { (中略) task->tss.ss0 = 0; task->fpu[0] = 0x037f; /* CW(control word) */ /* ココから */ task->fpu[1] = 0x0000; /* SW(status word) */ task->fpu[2] = 0xffff; /* TW(tag word) */ for (i = 3; i < 108 / 4; i++) { task->fpu[i] = 0; } /* ココまで */ return task; } } return 0; /* もう全部使用中 */ } int *inthandler07(int *esp) { struct TASK *now = task_now(); io_cli(); clts(); if (taskctl->task_fpu != now) { if (taskctl->task_fpu != 0) { fnsave(taskctl->task_fpu->fpu); } frstor(now->fpu); taskctl->task_fpu = now; } io_sti(); return 0; } ---- -bootpack.c (中略) void close_constask(struct TASK *task) { (中略) memman_free_4k(memman, (int) task->fifo.buf, 128 * 4); io_cli(); /* ココから */ task->flags = 0; /* task_free(task); の代わり */ if (taskctl->task_fpu == task) { taskctl->task_fpu = 0; } io_sti(); /* ココまで */ return; } ---- -dsctbl.c (中略) void init_gdtidt(void) { (中略) /* IDTの設定 */ set_gatedesc(idt + 0x07, (int) asm_inthandler07, 2 * 8, AR_INTGATE32); /* ココ! */ set_gatedesc(idt + 0x0c, (int) asm_inthandler0c, 2 * 8, AR_INTGATE32); (中略) } *** サンプルアプリ(1) -sincurve.c (stack=16K, malloc=0) #include "apilib.h" #include <math.h> void HariMain(void) { char buf[160 * 100]; int win, i; win = api_openwin(buf, 160, 100, -1, "sincurve"); for (i = 0; i < 144; i++) { api_point(win, i + 8, sin(i * 0.05) * 30 + 60, 0); } api_getkey(1); /* 何かキーを押せば終了 */ api_end(); } *** サンプルアプリ(2) -pi.c (stack=1K) #include "apilib.h" void HariMain(void) { /* 超頭悪い円周率計算 pi = 4 arctan(1) = 4(1-1/3+1/5-1/7+1/9-...) より */ double s = 0.0; int i, d; for (i = 1; i < 500000000; i += 4) { /* 5億くらいまでやらないと値がまともにならない */ s += 1.0 / i - 1.0 / (i + 2); } s *= 4.0; for (i = 0; i < 15; i++) { /* 10未満の正の数を表示 */ d = (int) s; api_putchar('0' + d); s = (s - d) * 10.0; if (i == 0) { api_putchar('.'); } } api_putchar('\n'); api_end(); } -これでも3.1415926までしか一致しません(正しくは3.14159265358979323...)。 -エミュレータなどで試す場合は5億の部分を減らしたほうがいいかもしれません。 -これはdoubleやfloatをprintf等を使わずに表示する例として出しました。 *** 注意事項 -この改造によりMMX命令も使えるようになります(MMX命令はFPU命令と同じレジスタを使うため)。もちろん対応していないCPUではダメですが。 -CD-ROMに付属のmath.hには、sin()、cos()、sqrt()くらいしか入っていません。もちろん自分で追加すれば他の関数だって使えますよ。 -CD-ROMに付属のsprintf()には、%fや%eを入れていないので、float/doubleの値の表示には使えません。 * こめんと欄 #comment
タイムスタンプを変更しない
* FPUを使えるようにしよう! -(by [[K]], 2006.09.30) -発展課題のページ(全部読む前にここを読むのは混乱するのでおすすめじゃないです) ---- -30日目までの「はりぼてOS」のままでは、1.23や3.14などの小数を含む計算、つまりfloatやdoubleの計算ができません。タスク切り替え時のFPUレジスタの保存・読み込みに対応できていないためです。FPUというのは、floatやdoubleに関する計算を一手に引き受けるCPU内部の回路のことです(昔はCPUとは独立した半導体になっていましたが、486DX以降はCPU内に組み込まれました)。 -なお、このページのとおりにしても、387を拡張していない386機や486SX機ではFPUがないので、floatやdoubleは使えません。 *** 仕組み -タスク切り替え部分にFPUレジスタの保存と読み込みを付ければいいだけだろうと思うかもしれませんが、実はそうではありません。その方法だとタスク切り替え時間が長くなってしまい、ダメなOSになってしまいます。 -FPUのレジスタは80ビットのものが8本もあって、それに制御用のレジスタがいくつかついて、結果として合計108バイトを保存したり読み込んだりすることになるのですが、こんなたくさんのデータをタスク切り替えごとに読み書きしていたら、タスク切り替え時間が倍増してしまうのです。 -そもそも今までdoubleやfloatを使わなくても何とかやってこれたわけで、これは逆にいえば、FPUなんかめったに使わないのです(だから386までは別売りオプションだったわけですしね)。そのとき動いているすべてのタスクを見回してみても、FPU命令を使うタスクなんてきっと数個でしょう。もしかしたら1個かもしれません。もし1つしかないとしたら、そもそもタスク切り替えのたびにFPUレジスタを切り替える必要なんて全くありません(たとえそのときの総タスク数が100個だとしてもです)。・・・というか0個の場合が最も多いかもしれません。0個ならもちろんFPUレジスタの切り替えも不要です。 -ということでムダなFPUレジスタの保存や読み込みをしないようにするためにいろいろ工夫するのですが、CPUのほうもOSが当然そういう工夫をするだろうということでそれを前提に命令が用意されています。 ---- - struct TASK の中に int fpu[108 / 4]; を追加。これはタスクがFPUレジスタを使った場合の保存先・読み込み元。 - struct TASKCTL の中に int tss_fpu; を追加。これは現在のFPUレジスタがどのタスクに所属しているものなのかを記憶させておくためのもの。 -CPUはタスクスイッチをするたびに、自動でCR0レジスタ内のTSビットを1にする(これは今までもやっていた)。 -CPUはFPU命令を実行するにあたり、TSビットをチェックする。もしTSビットが1だったら、FPU命令は実行せずに、INT(0x07);の例外を起こす(これも特に設定しなくてもやってくれる)。 -OSはこのINT07の発生を確認したら、tss_fpuの値と現在のTRレジスタの値を比較する。もしこれが一致したら、FPUレジスタの切り替えの必要はないので、TSビットを0にするだけで他には何もせずにIRETDする。一致しない場合は、tss_fpuの値で示されるタスクのfpu[]へレジスタデータを保存し、現在のTRのfpu[]を読み込み、その後にTSビットを0にしてIRETDする。 *** 改造点 -bootpack.h (中略) /* naskfunc.nas */ (中略) void clts(void); void fnsave(int *addr); void frstor(int *addr); void asm_inthandler07(void); (中略) struct TASK { (中略) struct TSS32 tss; int fpu[108 / 4]; /* ココ! */ struct SEGMENT_DESCRIPTOR ldt[2]; (中略) }; (中略) struct TASKCTL { int now_lv; /* 現在動作中のレベル */ struct TASK *task_fpu; /* ココ! */ char lv_change; /* 次回タスクスイッチのときに、レベルも変えたほうがいいかどうか */ (中略) }; (中略) void task_sleep(struct TASK *task); int *inthandler07(int *esp); /* ココ! */ (中略) ---- -naskfunc.nas (中略) GLOBAL _clts, _fnsave, _frstor, _asm_inthandler07 EXTERN _inthandler07 _clts: ; void clts(void); CLTS RET _fnsave: ; void fnsave(int *addr); MOV EAX,[ESP+4] ; addr FNSAVE [EAX] RET _frstor: ; void frstor(int *addr); MOV EAX,[ESP+4] ; addr FRSTOR [EAX] RET _asm_inthandler07: STI PUSH ES PUSH DS PUSHAD MOV EAX,ESP PUSH EAX MOV AX,SS MOV DS,AX MOV ES,AX CALL _inthandler07 CMP EAX,0 JNE _asm_end_app POP EAX POPAD POP DS POP ES IRETD ; INT07では ESP += 4; はいらない ---- -mtask.c (中略) struct TASK *task_init(struct MEMMAN *memman) { (中略) task_run(idle, MAX_TASKLEVELS - 1, 1); taskctl->task_fpu = 0; /* ココ! */ return task; } struct TASK *task_alloc(void) { (中略) for (i = 0; i < MAX_TASKS; i++) { if (taskctl->tasks0[i].flags == 0) { (中略) task->tss.ss0 = 0; task->fpu[0] = 0x037f; /* CW(control word) */ /* ココから */ task->fpu[1] = 0x0000; /* SW(status word) */ task->fpu[2] = 0xffff; /* TW(tag word) */ for (i = 3; i < 108 / 4; i++) { task->fpu[i] = 0; } /* ココまで */ return task; } } return 0; /* もう全部使用中 */ } int *inthandler07(int *esp) { struct TASK *now = task_now(); io_cli(); clts(); if (taskctl->task_fpu != now) { if (taskctl->task_fpu != 0) { fnsave(taskctl->task_fpu->fpu); } frstor(now->fpu); taskctl->task_fpu = now; } io_sti(); return 0; } ---- -bootpack.c (中略) void close_constask(struct TASK *task) { (中略) memman_free_4k(memman, (int) task->fifo.buf, 128 * 4); io_cli(); /* ココから */ task->flags = 0; /* task_free(task); の代わり */ if (taskctl->task_fpu == task) { taskctl->task_fpu = 0; } io_sti(); /* ココまで */ return; } ---- -dsctbl.c (中略) void init_gdtidt(void) { (中略) /* IDTの設定 */ set_gatedesc(idt + 0x07, (int) asm_inthandler07, 2 * 8, AR_INTGATE32); /* ココ! */ set_gatedesc(idt + 0x0c, (int) asm_inthandler0c, 2 * 8, AR_INTGATE32); (中略) } *** サンプルアプリ(1) -sincurve.c (stack=16K, malloc=0) #include "apilib.h" #include <math.h> void HariMain(void) { char buf[160 * 100]; int win, i; win = api_openwin(buf, 160, 100, -1, "sincurve"); for (i = 0; i < 144; i++) { api_point(win, i + 8, sin(i * 0.05) * 30 + 60, 0); } api_getkey(1); /* 何かキーを押せば終了 */ api_end(); } *** サンプルアプリ(2) -pi.c (stack=1K) #include "apilib.h" void HariMain(void) { /* 超頭悪い円周率計算 pi = 4 arctan(1) = 4(1-1/3+1/5-1/7+1/9-...) より */ double s = 0.0; int i, d; for (i = 1; i < 500000000; i += 4) { /* 5億くらいまでやらないと値がまともにならない */ s += 1.0 / i - 1.0 / (i + 2); } s *= 4.0; for (i = 0; i < 15; i++) { /* 10未満の正の数を表示 */ d = (int) s; api_putchar('0' + d); s = (s - d) * 10.0; if (i == 0) { api_putchar('.'); } } api_putchar('\n'); api_end(); } -これでも3.1415926までしか一致しません(正しくは3.14159265358979323...)。 -エミュレータなどで試す場合は5億の部分を減らしたほうがいいかもしれません。 -これはdoubleやfloatをprintf等を使わずに表示する例として出しました。 *** 注意事項 -この改造によりMMX命令も使えるようになります(MMX命令はFPU命令と同じレジスタを使うため)。もちろん対応していないCPUではダメですが。 -CD-ROMに付属のmath.hには、sin()、cos()、sqrt()くらいしか入っていません。もちろん自分で追加すれば他の関数だって使えますよ。 -CD-ROMに付属のsprintf()には、%fや%eを入れていないので、float/doubleの値の表示には使えません。 * こめんと欄 #comment
テキスト整形のルールを表示する