自作OSに挑戦する日記 6日目
「30日でできる!OS自作入門」を読んで分かったことや、とりあえず書いておきたいことなどを書いていきます。
この本はChapterが1から30まであるので、各チャプター毎に1記事書いていきます。
Chapter 6 「分割コンパイルと割り込み処理」
今回やった内容
- bootpack.cを分割する
- GDTの設定について(詳しく)学ぶ
- PICについて学ぶ
- PICを初期化してみる
- 割り込みを受け付けるようなコードを書く
今回の内容は本を読んで理解する部分が多かったので、作業記録として書くことがあまり無いですね…
環境
- メインPC
- MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports)
- macOS Mojave 10.14
- 自作OSを動作させるPC
- Core 2 Duo
- 4GB RAM
- (ハードオフのジャンクコーナにいた子)
作業記録
bootpack.cがかなり大きくなってきたので、ソースを分割します。既にやったことのある内容なのでヘッダファイルを書くところまで一気に進めました。分割後のソースの構成はこのようになっています。
これで、巨大なソースを見なくても良くなりました。1ファイルが巨大すぎるとウッってなりますからね…。
5日目に実装だけしたGDT/IDTの初期化について学びます。昨日ブログを書いた後なんとなくソースを読んで理解してみたのですが、 大体合ってたので嬉しかったです。
リミットやセグメントなど新出単語がたくさん出てきますが、5日目の内容と合わせて読むとちゃんと理解することが出来ました。セグメント属性についても学んだのですが、こんな低レベルな場所でもセキリュティの概念があるということに驚きました。ハード側でのセキリュティもあるんですね…。
本を読みつつ自分が理解しやすいようにコメントを挟んだコードがこれになります。
// bootpack.c(一部) void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar){ // limitが20ビットを超えるようならGビットを1にする if(limit > 0xfffff){ ar |= 0x8000; // G_bit = 1 limit /= 0x1000; } /* * 00000000000000000000000000000000 * ---------------- // base_low : base & 0xffff * -------- // base_mid : (base >> 16) & 0xff * -------- // base_high: (base >> 24) & 0xff */ /* * 000000000000000000000000 * ---------------- // limit_low : limit & 0xffff * ---- // limit_high: ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0) * // -> limit_highにはlimitの上位4ビットと拡張アクセス権が4ビットずつ置かれる */ sd->limit_low = limit & 0xffff; sd->base_low = base & 0xffff; sd->base_mid = (base >> 16) & 0xff; sd->access_right = ar & 0xff; sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0); sd->base_high = (base >> 24) & 0xff; }
コメントのようにどういう感じでメモリに情報が配置されるのかをメモすると、理解しやすかったです。
GDT/IDTの設定だけではまだ割り込みは扱えないので、次はPICについて学びます。特に引っかかったところはないのですが、次の説明がとても面白かったです。
PICがCPUを騙して任意の機械語を実行させるというのは、何かこう裏技感があって面白いですね。CPUは割り込みが発生するとPICにデータ送信命令を出すだけで、そのあと送られてきたデータが割り込みに関するものだとは全く理解していないみたいです(自己流の解釈なので間違っているかも)。面白い…。
ということで、PICについて理解できたので実装をしていきます。C側のコードで割り込みを受ける関数は名前と表示する文が違うだけでほぼ一緒です。また、アセンブリ側のコードもラベルと呼び出す関数がちょっと違うだけで、割り込みを受ける処理はほぼ一緒です。
// int.c // キーボード(IRQ1)からの割り込み void inthandler21(int *esp){ struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; boxfill8(binfo->vram, binfo->width, COL8_000000, 0, 0, 32 * 8 - 1, 15); putstr8(binfo->vram, binfo->width, 0, 0, COL8_FFFFFF, "INT 21 (IRQ-1) : Keyboard"); while(1){ io_hlt(); } } // マウス(IRQ12)からの割り込み 〜略〜
// nasmfunc.asm GLOBAL asm_inthandler21 EXTERN inthandler21 〜略〜 ; レジスタの値を全部退避させて割り込みを受けている asm_inthandler21: PUSH ES PUSH DS PUSHAD MOV EAX, ESP PUSH EAX MOV AX, SS MOV DS, AX MOV ES, AX CALL inthandler21 POP EAX POPAD POP DS POP ES IRETD
そして、アセンブリ側で書いた割り込み受け付け関数のアドレスをIDTに登録してあげれば、割り込み処理完成です!
// desctbl.c void init_gdt_idt(void){ struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT; struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) ADR_IDT; // GDT初期化 for(int 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); // bootpack.hrbのためのセグメント load_gdtr(LIMIT_GDT, ADR_GDT); // IDT初期化 for(int i = 0; i < LIMIT_IDT / 8; i++){ set_gatedesc(idt + i, 0, 0, 0); } // 割り込みが発生したらasm_inthander_2*を呼び出すようにIDTを設定している set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 << 3, AR_INTGATE32); set_gatedesc(idt + 0x2c, (int) asm_inthandler2C, 2 << 3, AR_INTGATE32); load_idtr(LIMIT_IDT, ADR_IDT); }
割り込みが発生した時の処理の呼ばれ方は、以下のような感じです。
割り込み発生 → CPUがIDTを確認する → 対応する関数を呼ぶ(asm_inthandler2*) → また関数を呼ぶ(inthandler2*)
こうして、最終的にはCで書いた関数が呼ばれるので、その中に処理を書いてあげれば良いというみたいです。
割り込み処理まで実装できたので早速実行してみます…
割り込みを受けれるようになった!!!! pic.twitter.com/YUq98IYnxr
— ゆん (@yn0014) 2019年1月21日
ちゃんとキーボードを押すと反応してくれました!!!!キーボードに反応させるまで長かった…感動です…。昨日から感動続きです。ただ、キーボードに反応させることはできたのですが、マウスについては無反応でした。本を見てみると、7日目の内容で修正する作業を行うみたいです。
まとめ
2日に渡って書いた割り込み処理がやっと動いてくれました!!ただ、マウスが無反応だったのが気に入らないので、早く直したいですね〜。
明日も頑張りたいと思います。