読者です 読者をやめる 読者になる 読者になる

OS自作入門 onLinux 6日目

Screenshot-QEMU.png

割り込みの仕組みについて

割り込み実験

ここ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か月もかけてここまでたどり着くと、感慨深いですね…。
これでひとまずは順調にすすみそうでよかったです。

それでは続きをお楽しみにー。