OS自作本の6日目。P124。
ここは難しくはありませんが、とても煩雑でややこしい部分。
初めて読む方でもしうまく理解できなかったら、「とにもかくにも合計80ビット分くらい必要なデータを、むりやり64ビットに押し込んでいる」程度に理解して、先に進んじゃうのがいいかも。(それで後でもう一回よく読んでみれば意外とスルスルっと解ることも多いので)
・・・まあでも、これじゃちょっとあんまりなので、オイラなりの解説〜♪
GDTへの設定に必要なもの
- とにかく、GDTには 8,192個分の設定ができる。(セグメントを 8,192種類設定できる)
- 各1個の設定には、大きさ・開始番地・属性の三つを設定する必要がある。
まずはこれだけ。属性はちょっと特殊なのでちょっと置いておく。とりあえずは16ビット必要ということで。
そう考えると、こういう設定パラメータが必要になると。
C大きさ | C開始番地 | C属性 | |
範囲 | 0x0〜0xFFFFFFFF | 0x0〜0xFFFFFFFF | 0x0〜0xFFFF |
---|---|---|---|
必要ビット数 | 32 | 32 | 16 |
必要バイト数 | 4 | 4 | 2 |
例 | 0x00230000 | 0x0000C000 | 0xA4FD |
さて、これを見ると、一つのセグメントに設定しなくちゃいけないパラメータの合計は32+32+16で、合計80ビットが必要になるわけです。
しかし!!
GDTという表が、一行をこのとおり80ビット(10バイト)とってくれていれば単なる当て込みでいいのですが、なぜかi386では一行は64ビット(8バイト)ということになっています。
なので、全部で10バイト必要なパラメータを8バイトに押し込む(一種の圧縮?)必要があるのです。ここはそのための処理なのです。
解りやすくこんなことを考えてみます。
セグメント0を、 ・大きさ0x11223344(278MB) ・開始番地0x55667788 ・属性を0xAABB として定義したいとします。
セグメントの開始番地は本でとりあえず0x00270000と決めています。これをそのまま使いましょう。
ようするにGDTは表です。なので、こんな表を妄想できますね?
アドレス | セグメントNo | 大きさ | 開始番地 | 属性 |
0x00270000 | 0 | 0x11223344 | 0x55667788 | 0xAABB |
?? | 1 | : | ||
2 | : | |||
: | ||||
8191 |
さて、この表の「??」の部分の番地。いくつになると思いますか?
0x00270000から始まって4バイト+4バイト+2バイト分進むのですから、0x00270010になるはずです。「??」のアドレスは0x00270010。つまりここから次のセグメント(セグメント1)の設定が始まると。ばんざーい!!
ダメなのです!!!
正解は0x00270008。そう。正しくは、GDT表の一行は8バイトなのです!(インテルが決めた)
- 「まてこのやろう!10バイト必要なのは明白だろう!なんで一行が8バイトなんだよ!2バイト分足りないじゃないかよ!!!」
- はいそのとおりです! インテルに変わってお詫びします!。でももう決まっているので、なんとかヤリクリしてください。あしからず!
- ・・・いっちゃった・・・
そういうわけでP124のコード。押し込み(圧縮?)作業が必要になると!
さて、実際に押し込む作業を追ってみましょう。
一行はとにかく8バイトなので、本では SEGMENT_DESCRIPTOR という構造体を定義しています。なるほど。構造体の中身をバイト単位で表してみると・・・
0000 | 0000 | 00 | 00 | 00 | 00 |
limit_low | base_low | base_mid | access_right | limit_heigh | base_heigh |
さてさっそく本のように、ここにデータを押し込んで見ましょう。まずは開始アドレス(0x55667788)から。
本によると、開始アドレスは32ビット(4バイト)が必要です。GDTの場合はこれをbase_low(2バイト)・base_mid(1バイト)・base_heigh(1バイト)の三つに割ってバラバラに格納しなければいけないようです。そのための処理が、
sd->base_low = base & 0xffff; sd->base_mid = (base >> 16) & 0xff; sd->base_heigh = (base >> 24) & 0xff;
これらです。ひとつひとつ見てみましょう。(ちなみにこのサイトの例として、baseは0x55667788を使用します。)
sd->base_low = base & 0xffff;
0x55667788 & 0xffffですね。
これはアセンブラでよく使う、「特定のビットをゼロにしたい!」という処理です。
ようするにここは、「下2バイトを切り出す」のではなく、 逆に「頭2バイトを全部0にしちゃう」という処理です。 2進数にするとかえってわかりやすいかも。
01010101011001100111011110001000 ←0x55667788の2進数 AND 00000000000000001111111111111111 ←0x0000ffffの2進数 ──────────────────── 00000000000000000111011110001000 ←0x00007788になる
なるほど! これでbase_low(2バイト)に代入できます!
sd->base_mid = (base >> 16) & 0xff;
( 0x55667788 >> 16) & 0xff ですね。
まず括弧の中身。これはビットシフト命令ですね。(本のP117に図が載ってます)
これも、かえって2進数にしたほうがわかりやすいかもしれません。
01010101011001100111011110001000 ←0x55667788の2進数 ↓ 16個分右にシフト ↓ 00000000000000000101010101100110 ←0x00005566の2進数
そしてさらに、これを0xffでAND演算しています。
00000000000000000101010101100110 ←0x00005566の2進数 AND 00000000000000000000000011111111 ←0x000000ffの2進数 ─────────────────── 00000000000000000000000001100110 ←0x00000066になる
これで base_mid が取り出せました。
sd->base_heigh = (base >> 24) & 0xff;
上の処理とほぼ同じですね。
01010101011001100111011110001000 ←0x55667788の2進数 ↓ 24個分右にシフト ↓ 00000000000000000000000001010101 ←0x00000055の2進数
なお、この時点ですでに頭2バイトだけの状態になっていますが、あえて0xffでANDしていますね。おそらくこれは、「念のため」の処理でしょう。
さて、これで、「開始番地」を三つに分割することができましたね。本のコードでは処理してすぐに構造体に当て込んでいます。今は構造体はどんな状態でしょうか?
0000 | 7788 | 66 | 00 | 00 | 55 |
limit_low | base_low | base_mid | access_right | limit_heigh | base_heigh |
こうなっています。
大きさ(limit)
さて、セグメントの大きさです。これは本に書いてある通り、0から4GBまで指定できないとおかしいですよね?。なので、32ビット(4バイト)のデータになります。
上記の開始アドレスは幸いただ割ればよかったのですが、limitは20ビットしか入れ物が用意されていません。なので、特殊な方法で指定します。
例として、大きさは0x11223344(278MB)を使います。
if (limit > 0xffff) { ar |= 0x80000; /* G_bit = 1 */ limit /= 0x10000; } sd->limit_low = limit & 0xffff; sd->limit_heigh = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0x0f);
これがlimitに関する演算です。最初から見てみましょう。
if(limit > ・・・・
これはなにをやっているかというと、P125に書いてある通りです。
大きさがもし1MB以上ならG_bitを1にしてx4kbモード?にします。当然limitの値もあらかじめ4kbで割り算しておきます。
・・・しかし、G_bitを1にするのはわかりますが、なんで ar |= 0x80000 なんて処理をしているのでしょう? arはそもそも属性の値じゃないですか・・・
この表をもう一度見てください。
0000 | 7788 | 66 | 00 | 00 | 55 |
limit_low | base_low | base_mid | access_right | limit_heigh | base_heigh |
これは構造体の定義にそって変数を宣言しています。C言語では、設定(というか、指定?)できる変数はchar(8ビット)、short(16ビット)、int(32ビット)等等がありますが、残念ながら20ビットなんて単位の変数はありません。やむを得ず16ビットや8ビットでごまかしていますが、本当は上記の表の区切りはまちがっているのです。本当はこうなります。(色が付いている部分はそこだけ2進数です。ご注意)
↓区切る所が厳密には正しくない | |||||
0000 | 7788 | 66 | 00000000 | 00000000 | 55 |
---|---|---|---|---|---|
limit_low | base_low | base_mid | access_right | limit_heigh | base_heigh |
↓正しい区切り位置 | |||||
0000 | 7788 | 66 | 000000000000 | 0000 | 55 |
---|---|---|---|---|---|
limit_low | base_low | base_mid | access_right | limit_heigh | base_heigh |