1: 2007-01-26 (金) 11:26:58 |
2: 2007-01-26 (金) 14:08:38 |
| OS自作入門はとりあえず一通り終わったんだけど、どうもオイラはタスクの理解が甘いようだ。ちょっとこれだけにスポットを当ててもう一回復習してみよう。 | | OS自作入門はとりあえず一通り終わったんだけど、どうもオイラはタスクの理解が甘いようだ。ちょっとこれだけにスポットを当ててもう一回復習してみよう。 |
| | | |
- | #comment | + | **15日目 [#ldbcdc6e] |
| + | -CPUは「切り替えろ!」と命令がくると、レジスタのほぼ全てをメモリに書き出す。(退避させるというほうがいいかも) |
| + | |
| + | -そして今度は、切り替わるべき別のタスク用のレジスタ内容を一気に読み込んでしまう。 |
| + | |
| + | -最期に、eip(次は何番地のメモリの命令を読み込んで実行するか?)の値をその新しいタスク用のものにパチンと切り替える。 |
| + | |
| + | -これで次の命令からは、あたかもずーっとその仕事をしていたかのように動作を始める。 |
| + | |
| + | -これを高速に交互に行うのがマルチタスク |
| + | |
| + | ***どこに退避させる?? [#l7bf7e72] |
| + | |
| + | -退避先のメモリはどこでもいいわけではない。あるフォーマットと属性で作られた、専用のセグメントを使わないといけない。この特殊なセグメントをtssという。 |
| + | |
| + | -tssはCPU内部のレジスタのほぼ全てを退避できる量(逆に言えばそれ以上の余計な量は必要ない)を確保する。通常104バイト。内訳は |
| + | |
| + | struct TSS32 { |
| + | int backlink,esp0,ss0,esp1,ss1,esp2,ss2,cr3; /*タスクの状態を表すもの |
| + | int eip,eflags,eax,ecx,edx,ebx,esp,ebp,esi,edi; /*通常のレジスタ |
| + | int es,cs,ss,ds,fs,gs; /* 〃 (16ビットのもの) |
| + | int ldtr,iomap; /*タスクの状態を表すもの |
| + | } |
| + | |
| + | |
| + | -tssは切り替わるタスクの退避場所なので、素直に考えると、タスクが10個平行して動く場合はtssも10個用意するべきもの。 |
| + | |
| + | ***tssの作成&登録 [#h0836893] |
| + | -先に書いたように、tssはセグメントの一種。なので、作成や登録はセグメント管理部分であるGDTに書き込みを行うことで実現する。 |
| + | |
| + | -tssの作成と登録の実際 |
| + | struct TSS32 tss_a,tss_b; /*構造体として親切する |
| + | |
| + | tss_a.ldtr = 0; /*ldtrとiomapはとりあえずこの数値にする。 |
| + | tss_a.iomap = 0x40000000; |
| + | tss_b.ldtr = 0; |
| + | tss_b.iomap = 0x40000000; |
| + | |
| + | /*gdtへの登録 |
| + | set_segmdesc(gdt + 3,103,(int) &tss_a,AR_TSS32); /*それぞれ、GDT3番と4番に |
| + | set_segmdesc(gdt + 4,103,(int) &tss_a,AR_TSS32); /*登録した。 |
| + | |
| + | COLOR(red){''疑問点:''一つのタスクにはそれぞれtssとメモリ空間が必要。で、tssはセグメント。メモリ空間も当然セグメント。ということは、理屈ではタスクは4,095個以上は絶対に作れないということでOKなのか?} |
| + | |
| + | ***タスクの準備 [#v2c835d5] |
| + | -タスクという概念で考えると、現在動いている部分とてタスクとなる。(つまり、一つ目のタスクということになる) |
| + | |
| + | -これまではマルチタスクではなかったので意識していなかったが、一番最初にやらなくてはならないことは、「俺も実はタスク! で、オレが動いているセグメントは2番!」ということのほかに、「俺が動いているタスクの退避用tssはセグメントの3番だよ!」という登録をしてやらないといけない。 |
| + | |
| + | -そこで、load_tr(3 * 8)関数を実行する。これにより、「俺が動いているタスクの退避用tssはセグメントの3番だよ!」という設定になる。(言い方を変えれば、このタスクが別のタスクに切り替わる時には、セグメント3番のtssに退避される) |
| + | |
| + | -タスクを複数用意しなければ当然切り替えなどできない。 |
| + | |
| + | -タスクは仕事を続けるものでないといけない。(一つ仕事をしてすぐ終わってしまうのでは単なる関数) |
| + | |
| + | -通常はなんらかのループ構造にするもの。たとえば |
| + | void task_b_main(void){ |
| + | for(;;){io_hlt();} |
| + | } |
| + | |
| + | -よし! これでOKだ! と思うがそうではない。task_bにはまだ準備がいる。 |
| + | -まず、そもそもtask_bは''まだ一度も動いていない''のだ。最初の切り替わりで始めて動き出す。しかも、その最初の動作時も「切り替わり」なのだから、tss4からドバっとレジスタの値を読み込んで動き始める。tss4にはまだいい加減な数字しか入っていない。(これでは切り替わった瞬間に暴走の危険がある) |
| + | |
| + | -そのため、とりあえず各レジスタの初期値を設定しておく。また、タスク、そもそもこのタスクに切り替わった場合、どこを実行するのかやスタックなどの設定も調整しておかなくてはならない。 |
| + | |
| + | P296の初期設定 |
| + | tss_b.eip = (int) &task_b_main; /* 関数task_b_mainの先頭アドレス |
| + | |
| + | |
| + | /* これはtask_b専用のスタックの設定。mamman_allocと使い64kbを確保。 |
| + | /* 後ろの計算式はスタックの最終番地を計算したもの |
| + | int task_b_esp; |
| + | task_b_esp = memman_alloc_4k(memman,64*1024)+64*1024; |
| + | tss_b.esp = tssk_b_esp; |
| + | |
| + | |
| + | tss_b.eflags = 0x00000202; /*??? どういう意味だ? |
| + | |
| + | tss_b.eax = 0; /* 通常の各レジスタの初期値。 |
| + | tss_b.ecx = 0; |
| + | tss_b.edx = 0; |
| + | tss_b.ebx = 0; |
| + | tss_b.ebp = 0; |
| + | tss_b.esi = 0; |
| + | tss_b.edi = 0; |
| + | |
| + | tss_b.cs = 2 * 8; /* task_bはGDT2番のセグメントでプログラムを実行する。 |
| + | /* 今回は単に面倒なので。もちろん別に用意してもいい。 |
| + | tss_b.es = 1 * 8; /* 同じ理由でGDTの1番を使用する。 |
| + | tss_b.ss = 1 * 8; |
| + | tss_b.ds = 1 * 8; |
| + | tss_b.fs = 1 * 8; |
| + | tss_b.gs = 1 * 8; |
| + | |
| + | ***準備完了! タスクスイッチ! [#lb65bb25] |
| + | -ここまでできれば、あとはtaskswich4関数(中身は単にアセンブラでfarJumpしているだけ。切り替えたいタスクのtss(task_bは4番)にfarJumpする。 |
| + | |
| + | ***P298のタスク戻し [#abc9a649] |
| + | -これは説明するまでもない。解る。 |