割り込みの仕組みについて
割り込み実験
ここ2カ月程、OS自作入門がずっと6日目で止まっていました。
割り込み処理がうまくできなかったからです。
ところが、ついに割り込みハンドラを呼ぶのに成功しました!
ようやく努力が報われました。すごく嬉しいです(´∀`)ノ
それでは続きから。
原因は簡単です。セグメントの設定がいけなかったのです。
前回の「
割り込み実験」で、最後にこう結論付けました。
つまり、CPUは割り込みハンドラを呼んだつもりだけど、アドレスを間違えちゃった、みたいな。
これ、ビンゴでした。
今回のソースは
こちら。
さて、重要なのはdsctbl.cだけです。
void init_gdtidt() {
SEGMENT_DESCRIPTOR *gdt = (SEGMENT_DESCRIPTOR *)ADR_GDT;
GATE_DESCRIPTOR *idt = (GATE_DESCRIPTOR *)ADR_IDT;
int i;
// GDTの初期化
for( i = 0; i <= LIMIT_GDT / 8; i++ ) {
set_segmdesc(gdt + i, 0, 0, 0);
}
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW);
set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);
set_segmdesc(gdt + 3, 0xffffffff, 0x00000000, AR_CODE32_ER);
load_gdtr(LIMIT_GDT, ADR_GDT);
// IDTの初期化
for( i = 0; i <= LIMIT_IDT / 8; i++ ) {
set_gatedesc(idt + i, 0, 0, 0);
}
load_idtr(LIMIT_IDT, ADR_IDT);
// IDTの設定
set_gatedesc(idt + 0x21, (int)inthandler21, 3 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x2c, (int)inthandler2c, 3 * 8, AR_INTGATE32);
}
18行目に注目。新しいセグメントを勝手に定義してます。
それでもって、その3番セグメントを割り込みゲートのセレクタ値に指定しています(28,29行目)。
これで正常動作しました。
はっきりとは理由はわからないんですが、おそらく、
5日目あたりで強引にエントリアドレスを
設定したあたりから2番セグメントで動作しないんじゃないかと思います。たぶん。
以下適当な推測。
この時点で%ds=(1<<8)なんですよね。head.sで設定したままなので。
すると、set_gatedesc関数でセレクタ値を設定するときに、inthandler21のアドレスを
もちろんメモリオペランドとして読み込みます。
dsはセグメント1番を指しているので、セグメント1番から読み込みます。
セグメント1番ってメモリ全体じゃないですか。つまり、inthandler21の物理アドレスですよね。
inthandler21の物理アドレスを今、0xHANDLERとしておきます。
そして、割り込みが入って割り込みハンドラをCPUが呼ぶとき…
%cs = (2 << 8)
%eip = 0xHANDLER
になります。
そしたら、セグメント2番の0xHANDLER番地に割り込みハンドラがあると思って実行するじゃないですか。
セグメント2番ってbootpack全体のセグメントですよ。ベース番地は0x280000です。
つまり、物理アドレス0x280000+0xHANDLERを実行することになっちゃうでしょ。
実行して欲しいのは物理アドレス0xHANDLER。
じゃあ、もうひとつメモリ全体を指す実行可能なセグメントを作って、それを設定しとけーってことです。
セグメント1番は読み込み用セグメントで、実行できないので。
って考えたんですが、書き終わってもう一度考えると、inthandler21のアドレスの部分ですが、
メモリオペランドとして読み込まれているのではなくて、リンカが再配置して即値として
バイナリに埋め込まれているんですよね。結局原因分からずじまい。
※追記
よく考えましたが、上記でだいたいあってると思います。
5日目にリンカスクリプトでC言語部分のエントリアドレスを0x280000にしています。
ということは、リンカは再配置時にすべて0x280000を基準に再配置するので、
inthandler21も実際のアドレスに0x280000が足されて、結果的にos.sys内における物理アドレスになったみたいです。
一応、割り込みハンドラについても、GASに書き直しているので軽く解説を。
inthandler21:
pushw %es
pushw %ds
pusha
movl %esp, %eax
pushl %eax
movw %ss, %ax
movw %ax, %ds
movw %ax, %es
call hInt21
popl %eax
popa
popw %ds
popw %es
iret
諸事情により、割り込みハンドラを記述しているソースファイル名がint.sからgasint.sになっています。
さて、基本的には自作本通りで解説するところはないのですが、
まあGASだと変わっちゃうところだけ触れておきます。
PUSHAD->PUSHA
POPAD->POPA
IRETD->IRET
命令名が違うだけですが^^;
画像だけだと、割り込みが起こったのかさっぱりわからないので、
スクリーンキャストを投下。
いやー、2か月もかけてここまでたどり着くと、感慨深いですね…。
これでひとまずは順調にすすみそうでよかったです。
それでは続きをお楽しみにー。
PR
COMMENT