無限遠まで突撃中

ネット日記書きの徒然。

「30日でできる!OS自作入門」を今更ながらやってみる - 5日目

今日もOSがんばるぞい
4日目は こちら

5日目 - 構造体と文字表示とGDT/IDT初期化

1 - 起動情報の受け取り

ポインタを用いて asmhead.nas の情報を受け取っていますね。それだけです。

2 - 構造体を使ってみる

構造体の登場です。構造体は変数をかたまりで扱える便利なものです。まあここも難しくないでしょう。
もし構造体のポインタの扱い方に混乱したら、また 3日目 を見直すか、本のコラムをじっくり読んでください。わからなくなったら基本に立ち返ることが重要です。

3 - 矢印表記を使ってみる

(*binfo).scrnxbinfo->scrnx は等価です。この本では -> を積極的に使っていくようです。
おそらくこの -> にはもっと深遠な機能があると思うのですが、自分はC言語ナニモワカラナイのでここで詳しくは解説しません。

4 - とにかく文字を出したい

ドットをちょんちょんちょんと打って「A」を描いています。気が遠くなりますね…

5 - フォントを増やしたい

OSASKに用いられているフォントデータとコンパイラを用いてフォントを導入しています。省略〜

6 - 文字列を書きたい

文字列を描いています。はい。
文字列の補足ですが、文字列は文字コードを順番にメモリに並べて最後に 0x00 をつけたもの…のようです。

7 - 変数の値の表示

GO(ジーオー)というコンパイラsprintf という関数を提供してくれます。
sprintf 関数は指定した文字列をメモリの中に生成する機能を持っています。これを使ってどこかの番地に文字列を保存しておいて、それを putfonts8_asc() で読み出せば無事文字が表示されます。やったネ!

8 - マウスカーソルも描いてみよう

はい描くだけ〜カット!!!

9 - GDTとIDTを初期化しよう

ここまでで1000文字ですが、ここから多分3000文字くらい書くことになりそうです〜んご〜頑張るお

まず何がしたいかをはっきりさせておきましょう。私達は マウスカーソルを動かしたい のです。それには マウスが動いたときの信号をキャッチして、マウスカーソルの絵を動かせばいい のです。信号をキャッチするには IDT を設置する必要があります。 IDT を設定するには今度は GDT なるものを設定する必要があります。こういう流れで GDTIDT を設定する必要があるのです。それでは書いていきます〜

まずはセグメンテーションです。手っ取り早く言うと メモリを切り分けてそれぞれのブロックの最初の番地を0として扱える という機能です。今まで ORG 0x7c00 とか書いていましたが、セグメンテーションを設定すれば自動的に ORG 0 が設定されるというわけです。3日目 にセグメントレジスタというのがありましたが、今回もそれを使います、使いますが、あの頃はまだ16ビットモードでした。あの時 MOV AL,[SI]MOV AL,[DS:SI] で、 DS の16倍が SI に足されていました。しかし今は32ビット、事情が違います。 MOV AL,[DS:EAX] という時 AL に代入されるのは、 DSの開始番地 + EAXの値 です。ご注意を。
セグメントレジスタを省略したら自動的に DS が足されるというのは32ビットモードでも16ビットモードでも共通です。

そのセグメンテーションですが、セグメントを表すには次の情報が必要です。

  • セグメントの大きさ
  • セグメントの開始番地
  • セグメントの管理用属性(書き込み禁止、実行禁止、システム専用etc.)

CPUではこれらのデータを64ビット(8バイト)で表していますが、セグメントを指定するためのレジスタは16ビットしかありません。じゃあできないのかと思いますがそんなことはありません。以下の図がわかりやすいですので、この図を参照しながら説明します。

f:id:hrstmyk811m:20190318162730p:plain
http://softwaretechnique.jp/OS_Development/Image/Kernel_Loader2/gdt_segment_register.png より引用

まずセグメントレジスタにはセグメントの番号(一般にセグメントセレクタと呼ぶ)を格納します。セグメントレジスタとは別に、メモリ上に GDT(Global Descriptor Table) というセグメントの必要情報(箇条書きで挙げた3つですね)とセグメントセレクタを記載したテーブルを用意します。そしてメモリの空き領域を都合よく区切ります。そしたらセグメントレジスタに格納したセレクタGDTセレクタを紐づけて、更に GDT のそれぞれの情報と合致するメモリ領域を紐づけます。
こうすると、セグメントセレクタを指定したら対応する GDT の要素が指定され、 GDT の要素と対応するメモリが指定される、というわけです。2段クッションがかまされているんですね。
セグメントセレクタの数ですが 0~8191 が扱えます。セグメントレジスタは16ビットですが、CPUの都合上、下位3ビットが使えないためです。ということは8192個の GDT の要素が作れるわけで、つまり GDT8192 * 8 = 65,536 バイト(64KB)扱えるわけです。
GDT の作り方ですが、メモリの何処かにセグメントの情報をずらっと並べて、その先頭の番地と有効設定個数をCPUの GDTR というレジスタに設定すればよいです。

IDT(Interrupt Descriptor Table) は、割り込み番号とそれに対応した呼び出し関数の対応表です。まずは割り込みについて説明しましょう。割り込みとは、 CPU外部のデバイスが変化した時や内部エラーが起きたときに臨時の処理を発生させる機能 です。CPU外部のデバイスの変化とは、例えばキーボードが押されたとか、マウスがクリックされたとか、そういった類のことです。
なぜそういったものがあるかというと、CPUの処理スピードに比べて外部デバイスの変化が圧倒的に遅いからです。CPUは1秒間に100万回以上(今のCPUは1億回以上)動作します。それに比べればキーボードだって1秒に3回押されるくらいですし、マウスだって16連打が限界です。100万分の1秒とは次元が違うわけです。なので外部デバイスからの信号は、一種の例外として処理したほうが都合がいいのです。説明終わ〜り。

プログラムの解説は本を読んでください、わかると思います。ひとつだけ注意する点としては、ポインタの足し算です。 ポインタに足し算すると、もとの型のバイト分だけn倍されたものを足します。 何を言っているかわからないと思うので例を出しましょう。以下のポインタ型変数を宣言したとします。

int *p = (int *) 0x1000;

これに足し算をしていくと、結果は以下のようになります。

printf("+0 : %d\n", p); // +0 : 4096
printf("+1 : %d\n", p + 1); // +1 : 4100
printf("+3 : %d\n", p + 2); // +3 : 4108

int 型は4バイトなので、1足すと4バイト、2足すと8バイト増えていますね。 char だと1バイトずつ、 short だと2バイトずつ増えます。8バイトの構造体だったら8バイトずつ増えます。覚えておきましょう。
追記: どうやら型のサイズはCPUによって変わるようです。 ここ とか ここ などを参考にするとよい。

今日はここまで

ふー疲れましたね、それではまた明日〜