30日OS自作入門6日目(Win10)

初めに

まだまだ6日目です...でも最近完全にはまったのか、ひたすら自作OSをしているのでどんどん進めそうです。

目次

本文

ソースファイルの分割(hairb03a)

可読性や管理を楽にする目的でソースファイルを分割していくようです。
まずはboootpack.cをそれぞれの処理を基準にして
・描画関係
・GDT・IDT関連
・それ以外
といった感じみたいです。
といっても

ファイル名 中身
bootpack.c Harimainを実行
graphic.c 描画関係の関数
dsctbl.c GDT・IDT関係の関数

といった具合になりました。ほんとすっきりしますね…役割をチームに割り振っているみたい…
後はこれらを一つずつobjファイルに変換し、bootpack.bimに仕上げるようMakefileに依頼文をだしておかないといけないそうなので、いじったら…
f:id:No000:20191112192516p:plain 起動した!

Makefileの整理(harib03b)

一般規則を利用して、6つの生成規則を2つの生成規則にまとめるようです。

ヘッダー整備(harib03c)

拡張子がhのファイルとして、それぞれのソースコードで重複しているコードをヘッダーファイルとしてまとめてあげるみたいです。

やり残した説明

GDT、IDTに関してやり残していた説明をするみたいです。

まず、naskfunc.nasで設定していたラベルの_load_gdtrがどのような動作をしているかの説明で、このラベルの中身はGDTRという48ビットで構成された特殊なレジスタに、引数として渡したリミットとアドレスの情報を代入してあげる関数となるそうです。また、GDTRにはMOVを利用して代入するのは禁止されているので、専用のLGDT命令という命令を使用して代入を行ってあげます。
load_idtrの動作も同じような動作をすると考えるみたいです。

次に、dsctbl.cで設定している関数のset_segmdescがどのような処理を行う関数かの説明があります。
ソースコードを見ればわかりやすいですが、5日目に説明された、大きさや番地、管理属性をまとめた8バイトのデータを構造体を利用して設定してあげるための関数のようです。しかし言葉でけでは簡単そうに見えますが途中で&を使用していたりして複雑なので、本をしっかり読もうと思います。

・セグメントの番地
セグメントのアドレスはlow(2バイト)mid(1バイト)high(1バイト)に分割してそれを配置します。その際も1列に並べるのではなくmidhighの間にはaccess_rightを挟んでいたりしています。この理由は、80286時代のプログラムとの互換のために存在しているようです。
・セグメントの大きさ
リミットというnaskfunc.nasの引数で出てきた名前の説明があります。これはセグメントの大きさを示しています。しかし、セグメントは最大4GBなので数値を表すには32ビット必要…ですが、そのまま大きさを代入してしまうとレジスタのリソースが足りなくなってしまいます。そこで解決するために登場するのがGビット(サテライトキャノンを打つ無人兵器ではないみたい…残念)というフラグです。これが1になっているとリミットのバイト単位から1ページ4KBのページ単位での管理になるみたいです。(CPUでどんな動きがあってるんだ…?)そして、入れる構造体の箇所もlimit_highlimit_lowに代入されます。これだと24ビットあるかなと思ったら、limit_highの上位4ビットは管理属性が含まれているみたいです。だからarってあるのか…うーんこの…
・セグメントの管理属性
12ビットで構成されたセグメントのアクセスに関する設定のデータを保管する場所です。まず先ほど出ていたlimit_highの上位4ビットというのは拡張アクセス権と呼ばれ、これもまた80286時代のプログラムとの互換性を維持するために存在するみたいです。構成はGD00となっておりGはGビット・Dはセグメントのモードで1だと32ビット、0だと16ビットとなるようです。しかし、これを利用してリアルモードとプロテクトモードを行き来することはできないようです。そして、80286から存在しているらしい下位8ビットに関しては本では簡単に説明されていますが気になるので、今度初めて読む486(80486のこと386の後継)で調べてみようかと思います。30日本では次の感じでまとめられていました。

下位8ビット 意味
0000_0000 未使用
1001_0010 システム専用で読み書き可、実行不可
1001_1010 システム専用で実行・読み込み可、書き込み不可
1111_0010 アプリケーション専用で読み書き可、実行不可
1111_1010 アプリケーション専用で実行・読み込み可、書き込み不可

この時、アプリケーション用はリング3、システム用がリング0設定されており、アプリケーションがLGDTを実行しようとしだすと命令を拒否して、OSに知らせてくれます。この際になぜLGBT命令をブロックするのかというと、このままだと悪意のあるアプリケーションが自前で用意したGDTを利用できるようにLGDT命令で再設定することができるようになるから見たいです。これは、OSでは防げない大変なことになるので、対策されているみたいです。 ちなみに、この説明で感動したんですよ…システム用とアプリケーション用の違いでリングという言葉が出てきているのですが、これはリングプロテクションって言葉で出てきます。この概念って低レイヤーを学んでいた時の私が混乱してた概念でして、こういう風に設定されているのだと感動しました…

PICの初期化(harib03d)

GDTとIDTの初期化を行ったので、マウスを動かす設定をしよう!の前にまだ初期化するべきものがあったみたいです。それがPICというものです。PICの役割は8個の割り込み信号を1つにまとめる役割のある装置みたいで、まとめる流れはを自分が考えた例えにすると、このPICの役割は「周辺機器の動きを監視する監視員」みたいなやつで、二人いるみたいです。この二人はマスターとスレーブに分かれており、マスターはCPUとは1本の電話回線でつながっており、8つの監視モニターがあり0番から7番となっています。この監視モニターのうち2番目のモニターにはスレーブが写っています。また、スレーブもまた8つのモニターを見張っており9番から15番となっているみたいです。そしてこの監視モニターのことはIRQと呼ぶみたいです。といった感じになります。このようなつなぎ方をカスケード接続と呼ぶみたいです。
その後、初期化するための関数をint.cファイルの中に、関数init_picとして設定します。このコードの中身の仕組みは、まずbootpack.hでinit_picの宣言とポートのデータをマクロ置換を使って定義しておき、int.cに処理内容を書いています。

まず、PICは外部装置なのでOUT命令を使用して設定します。のでアセンブリで定義したio_out8を使用するみたいです。また同じポート番号もICW1が来るとICW2が来るんだ…という決まりがあるみたいです。そして、PICのレジスタはすべて8ビットとなっていて、今回の初期化で使用するレジスタは、IMRICWとなります。

IMRはInterrrupt mask registerと呼ばれ、8ビットのひとつひとつがIRQ信号の番号に対応しており1だとマスク(PICから見えなくなる)され、0だと普通に信号を受け付けるみたいです。

ICWはInitial cnteol wordと呼ばれ、初期化の処理を担当するようです。1~4まで存在し、ICW1とICW4はPICがどのように動くかの設定をするみたいです。ICW3はマスタースレーブ接続に関する設定なので、ハードウェアの仕様上値は決まっているのでいじらない…ということで、いじるのはICW2みたいで、ここではIRQを殿割り込み番号にしてCPUに通知を送るかを決める設定を行うようです。これを設定することにより、CPUとつながるinやoutに使用する信号線から0xcd(INT)0x??(番号)の2バイトとしてCPUにデータが送られ、それがメモリからデータを受け取ったとCPUが思い込むことによって割り込み命令が実行されるようです。

また、その際に使用する割り込み番号としては、0x00~0x1fはアプリケーションの不穏な動作をCPUが感じ取ったときに警告してくれる割り込みが割り当てられているので使えないみたいです。よって、PICの割り込みは0x20~0x2fに割り振ることにするようです。

makefileも変わってるので注意

割り込みハンドラ作成(harib03e)

はりぼてOSで想定されているPICはIntel 8259なので、マウスの割り込みはIRQ12、キーボードがIRQ1となっていますので、INT 0x2cINT 0x21用の割り込みがあった場合に動くプログラムである、割り込み受付プログラム(割り込みハンドラ)を作成するようです。

参考

IRQ - Security Akademeia

Intel 8259 - Wikipedia

まず、割り込みが起こった際の処理をint.cinthandler21に記述する。その内容は見ればわかる通り、画面に指定の文字を表示してHLTする関数です。しかし、割り込み処理はこれだけの処理ではいけないらしく、RET命令ではなくIRETD命令が必要とのことです。ということでnaskfunc.nasに記述を…
まず上の方に

    EXTERN  _inthandler21, _inrthandler27, _inrthandler2c

次に、IRETD命令用のラベルがあり

_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

となっています。この際に利用する新しい概念がスタックとなります。今回使用されているスタックはFILO型のバッファになるみたいです。その際にスタックを操作するのに使う命令がPUSH命令とPOP命令になります。これらを利用してまで何をしているかというと、レジスタの値をスタックを利用してバックアップしているみたいです。その理由は、割り込み処理というのが関数の実行最中でも構わずに割り込みを行うので、その影響でレジスタの値が狂ってしまわないようにするための対策のようです。また、CALL命令で_inthandler21を呼び出しているのですが、これってC言語のコードから呼び出してるっぽいですね…びっくり!

要は、 レジスタをFILO型のスタックにバックアップ

割り込み用処理を書いた関数をCALL命令で呼び出す

レジスタFIFO型のスタックからレジスタに移す

IRETD命令で戻る

という流れです。
ここでDSとESにSSの値を代入する理由に関してなのですが、C言語がDSとESもSSと同じセグメントであると認識しているため、中身を同期してあげる準備をしてあげる必要があるのです。
そのあとset_gatedesc()で2*8の2というのはセグメントの登録する番号のことを表しており、これを8倍にすることで、3ビット左シフトをしています。このシフトする理由は、セグメント番号の下位3ビットには特別な意味があり0にしておく必要があるそうです。
で、セグメント番号の2番目がどのようなセグメントかというと、bootpack.hrbをすっぽりカバーするセグメントになるのです。
そして、最後にAR_INTGATE32でIDTの属性(権限)を0x008eとすることで割り込み処理用の設定ということにすることができます。

これで設定を終えると…
f:id:No000:20191120213017p:plain キーボードをたたいたら変化した!イヤー変化があるって最高ですね…
しかし、マウスは設定が足りないらしくうんともすんとも言わないので、次回設定していくようです。

C言語

ヘッダーファイル

ヘッダーファイルをinclude "hoge.h"として宣言してあげると、コンパイルするときに指定されたコードを埋め込んでくれるという動作をします。その際に指定したコードにdefineや関数の宣言をまとめたファイルをヘッダーファイルト言うそうです。
個人的にはdefineをまとめることができるのが大きいと感じました。だって見やすくて楽じゃん!すごい!

プロトタイプ宣言

ヘッダーファイル等に関数の宣言をなんでしているのだろうと、素で思ったので調べてみるました。するとC言語では、使用する関数は使用する前の行で定義しておくという決まりがあるのですが、それを面倒なので簡単にしたいというのがプロトタイプ宣言となるみたいです。でも書き方次第ではコンパイルを通す方法もあるらしくて…難しい世界…

アセンブリ言語

PUSH命令

下に書いているLIFO型のスタックにデータを突っ込む命令なのです。
例えば、PUSH EAXがやっていることは

ADD    ESP,-4
MOV    [SS:ESP],EAX

みたいですね、この例えのコードすごいわかりやすい

POP命令

上と同じようにLIFO型のスタックからデータを抜く命令です。
例えば、POP EAXがやっていることは

MOV   EAX, [SS:ESP]
ADD   ESP,4

みたいです。

PUSHAD命令

EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDIをスタックにpushする命令で

PUSH    EAX
PUSH    ECX
PUSH    EDX
PUSH    EBX
PUSH    ESP
PUSH    EBP
PUSH    ESI
PUSH    EDI

に相当します。

POPAD命令

PUSHAD命令のPOP版です

POP    EAX
POP    ECX
POP    EDX
POP    EBX
POP    ESP
POP    EBP
POP    ESI
POP    EDI

EXTERN命令

関数の呼び出しの際に他のソースにあることを示している。
CALL命令等と一緒に使う

CALL命令

関数呼び出しに使用する

IRETD命令

オペランド32bitでの割り込みハンドラから戻ってくる命令

用語

バッファ

大量の情報を一気に保管し、必要になれば適宜出ししていく仕組み

FIFO

first in, first outの略で、仕組みは管のように入り口と出口が違うもののことです。

LIFO

first in, last outの略で、仕組みはコップのように入口と出口が同じもののこと。

メモ

・wordという言葉の定義は装置によって変化するようです