「OS自作入門 五日目」の編集履歴(バックアップ)一覧はこちら
「OS自作入門 五日目」(2007/07/27 (金) 00:33:27) の最新版変更点
追加された行は緑色になります。
削除された行は赤色になります。
*OS自作入門5日目のまとめ
自分のためのまとめなので、他の人には意味不かも。
**構造体(struct)
struct BOOTINFO {
char cyls, leds, vmode, reserve;
short scrnx, scrny;
char *vram;
}
このように型宣言する。使いかたは
struct BOOTINFO *binfo;
としてBOOTINFO型のポインタを定義、
binfo = (struct BOOTINFO *) 0x0ff0;
としてアドレスを格納。参照は
xsize = (*binfo).scrnx;
のようにドットを用いる。
xsize = binfo->scrnx;
と書くこともできる。
**文字の描画
フォントデータ↓
********
*** ***
*** ***
*** ***
*** ***
** ** **
** ** **
** ** **
** ** **
* *
* **** *
* **** *
* **** *
**
********
********
たとえばこれがAになっている。アスキーアートですね。これが連続したデータ列となっているような感じ。それでこれが↓のように
00000000
00011000
00011000
00011000
00011000
00100100
00100100
00100100
00100100
01111110
01000010
01000010
01000010
11100111
00000000
00000000
1と0の羅列だとすれば、バイナリデータとして扱うことができる。各ビットを画面上の画素として当てはめて、1のところに対応する画素には色データを入れれば文字が描画できる。
このデータは1列8ビットで16列ある。なので一文字16バイトのデータ。これを描画するには、各列について以下の処理を行う。
例えば1列目の1ビット目は
|0|0000000
|1|0000000
AND)
|0|0000000
次に1列目の2ビット目は
0|0|000000
0|1|000000
AND)
0|0|000000
これを繰り返し一列目が終わるまで繰り返す。ANDをとることで、そのビットのみの1か0の判定を行う。もしANDをとった結果が0であれば、そのビットは0ということなので、その画素に対応するアドレスには何も値を入れない。もし0でなければ、そのビットが1ということになるので、その画素に対応するアドレスに値を入れる。具体的には以下の様にする。
void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
int i;
char *p, d /*date*/;
for(i=0; i < 16, i++)){
p = vram + (y+i) * xsize + x;
d=font[i];
if((d & 0x80)!= 0){p[0]=c;}
if((d & 0x40)!= 0){p[1]=c;}
if((d & 0x20)!= 0){p[2]=c;}
if((d & 0x10)!= 0){p[3]=c;}
if((d & 0x08)!= 0){p[4]=c;}
if((d & 0x04)!= 0){p[5]=c;}
if((d & 0x02)!= 0){p[6]=c;}
if((d & 0x01)!= 0){p[7]=c;}
}
return;
}
**フォントデータ
外部で用意したフォントデータを扱うためには、
extern char hankaku[4096];
とする。
**文字コード
OSASKで使用されているフォントデータは、一般的な文字コードであるASCIIに準じて256文字が並んでいる。Aの文字コードは0x41なので、Aのフォントデータは
hankaku + 0x41 * 16
からの16バイトに納められている。C言語では文字コードを
'A'
のように表現することができるので、上の式は
hankaku + 'A' * 16
と書き換えることができる。
**文字列
C言語では文字列は0x00で終わることになっている。そのため文字列を表示することは以下のように実現できる。
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s)
{
extern char hankaku[4096];
for(; *s != 0x00; s++){
putfont8(vram, xsize, x, y, c, hankaku + *s * 16);
x += 8;
}
return;
}
文字列の格納されている番地を1つづつずらしていって、終端である0x00が登場するまで文字を表示する。xを毎回8足しているのは、表示する位置を右にずらしている(8pix)。
**マウスカーソル
マウスカーソル描画のときのvramの番地指定がよくわからん。
**セグメント、セグメンテーション
:セグメント、セグメンテーション| segment,segmentation.4GBあるメモリ空間を切り分けて、ブロックの最初の番地を0として扱えるようにしたもの。切り分けたブロックをセグメントと呼ぶ。これによって様々なプログラムを自身が0番地から読み込まれるものとして作成することができる。ORG 0.
-セグメントの大きさ
-セグメントの開始番地
-セグメントの管理属性
これらを表現するために8バイトのデータを使用する。セグメントレジスタは16ビットしかないので、セグメントレジスタにはセグメント番号を入れる。
:セグメント番号| 一般的にはセグメントセレクタと呼ばれている。セグメントレジスタは16ビットだが、下位3ビットは使用できないので、13ビットで表現できる0〜8191個のセグメント番号が使用できる。
8192個のセグメントが定義できて、1個につき8バイトのデータを保持しているので
8192*8=1024*8*8=64KB
このセグメント情報の表を全部格納するには64KB必要になる。この表のことをGDT(Global Discriptor Table)と呼ぶ。また、その先頭野番地と有効設定個数をCPUのGDTR(Global Discriptor Table Register)に保存する。
***segment discriptor
構造体を定義している。これがGDTの中身になっている。
struct SEGMENT_DISCRIPTOR{
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
}
全部で8バイト。
ここではinit_gdtldt関数において
struct SEGMENT_DISCRIPTOR *gdt = (struct SEGMENT_DISCRIPTOR*) 0x00270000;
として0x00270000番地を開始番地にしている。ここから0x0027ffff番地までがGDTとなる。
初期化は以下のようにして行う。
for(i=0; i<8192; i++){
set_segmdsec(gdt+i, 0, 0,, 0);
}
8192まで順にセグメントディスクリプタをセットしている。8192を16進数で表すと0x1fffにしかならないが、ポインタの足し算は実際にはそのポインタの型のバイト数分をかけたものが行われるので、
この初期化によって0x00270000-0x0027ffff番地が埋められたことになる。
設定は以下のように。
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092)
set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a)
上のものはセグメント番号1のセグメント、下のはセグメント番号2のセグメント、に対する設定。
番号、リミット、開始番地、属性を設定している。1の設定は0x00000000〜4GB分なので、
全メモリを扱うことができるセグメント。
2のセグメントは0x00280000を開始番地として、そこから0x0007ffff(512KB)分を扱えるセグメント。
ここにはちょうどbootpack.hrbがくる。セグメントの開始番地とプログラムの開始番地(ORG命令で指定された番地)が一致しないといけない。
GDTRにこのアドレスやリミット値を格納する方法。
_load_gdtr: ; void load_gdtr(int limit, int addr);
MOV AX,[ESP+4] ; limit
MOV [ESP+6],AX
LGDT [ESP+6]
RET
この意味がわからなかったんだけど、調べてたらこんなことが書いてあった。
>_load_gdtr(void load_gdtr(int limit, int addr)関数)は指定されたリミットと番地を48ビットレジスタであるGDTRに代入します。48ビットレジスタには単にMOV命令では代入できないので、LGDT命令を使用します。LGDT命令は指定されたアドレスから48ビット分の内容を読み取ってGDTRレジスタに代入します。なので、第一引数が入っている[ESP+4]を[ESP+6]に移動して、16ビット分ずらして、2つのデータで64-16=48ビットになるようにして、LGDT命令で[ESP+6]を指定してます。
この人もOS自作入門をやってたみたい。
これ見てたらわかったような木になったんだけど、スタックの形とかがまだ良くわからん。
***gate discriptor
これがIDTの中身になっている。
struct GATE_DISCRIPTOR{
short offset_low, selector;
char dw_count, access_right;
short offset_high;
}
初期化はGDTと同様。ポインタの足し算を行っている。
**割り込み
CPUが機器の反応をいちいち待っていなくても大丈夫なように、機器側からCPUに働きかける仕組みのことを割り込みという。PCには様々な機器がつながっているため、これがないとたいへん。
:IDT| Interupt Discriptor Table.割り込み記述子表。割り込みが発生したら、その割り込みに応じた処理を行うための情報が記述されている。
**stack(スタック)
#ref(stack growing.gif)
スタックは下位のメモリ番地に伸びていく。スタックを積む(PUSH)ということは、ESPを4減らす、減らしたところから上位32ビットに値を格納するということ。
----
*OS自作入門5日目のまとめ
自分のためのまとめなので、他の人には意味不かも。
**構造体(struct)
struct BOOTINFO {
char cyls, leds, vmode, reserve;
short scrnx, scrny;
char *vram;
}
このように型宣言する。使いかたは
struct BOOTINFO *binfo;
としてBOOTINFO型のポインタを定義、
binfo = (struct BOOTINFO *) 0x0ff0;
としてアドレスを格納。参照は
xsize = (*binfo).scrnx;
のようにドットを用いる。
xsize = binfo->scrnx;
と書くこともできる。
**文字の描画
フォントデータ↓
********
*** ***
*** ***
*** ***
*** ***
** ** **
** ** **
** ** **
** ** **
* *
* **** *
* **** *
* **** *
**
********
********
たとえばこれがAになっている。アスキーアートですね。これが連続したデータ列となっているような感じ。それでこれが↓のように
00000000
00011000
00011000
00011000
00011000
00100100
00100100
00100100
00100100
01111110
01000010
01000010
01000010
11100111
00000000
00000000
1と0の羅列だとすれば、バイナリデータとして扱うことができる。各ビットを画面上の画素として当てはめて、1のところに対応する画素には色データを入れれば文字が描画できる。
このデータは1列8ビットで16列ある。なので一文字16バイトのデータ。これを描画するには、各列について以下の処理を行う。
例えば1列目の1ビット目は
|0|0000000
|1|0000000
AND)
|0|0000000
次に1列目の2ビット目は
0|0|000000
0|1|000000
AND)
0|0|000000
これを繰り返し一列目が終わるまで繰り返す。ANDをとることで、そのビットのみの1か0の判定を行う。もしANDをとった結果が0であれば、そのビットは0ということなので、その画素に対応するアドレスには何も値を入れない。もし0でなければ、そのビットが1ということになるので、その画素に対応するアドレスに値を入れる。具体的には以下の様にする。
void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
int i;
char *p, d /*date*/;
for(i=0; i < 16, i++)){
p = vram + (y+i) * xsize + x;
d=font[i];
if((d & 0x80)!= 0){p[0]=c;}
if((d & 0x40)!= 0){p[1]=c;}
if((d & 0x20)!= 0){p[2]=c;}
if((d & 0x10)!= 0){p[3]=c;}
if((d & 0x08)!= 0){p[4]=c;}
if((d & 0x04)!= 0){p[5]=c;}
if((d & 0x02)!= 0){p[6]=c;}
if((d & 0x01)!= 0){p[7]=c;}
}
return;
}
VRAM上のメモリと画面上の画素は下の図の矢印の順に対応している。
#ref(screen and vram.gif)
**フォントデータ
外部で用意したフォントデータを扱うためには、
extern char hankaku[4096];
とする。
**文字コード
OSASKで使用されているフォントデータは、一般的な文字コードであるASCIIに準じて256文字が並んでいる。Aの文字コードは0x41なので、Aのフォントデータは
hankaku + 0x41 * 16
からの16バイトに納められている。C言語では文字コードを
'A'
のように表現することができるので、上の式は
hankaku + 'A' * 16
と書き換えることができる。
**文字列
C言語では文字列は0x00で終わることになっている。そのため文字列を表示することは以下のように実現できる。
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s)
{
extern char hankaku[4096];
for(; *s != 0x00; s++){
putfont8(vram, xsize, x, y, c, hankaku + *s * 16);
x += 8;
}
return;
}
文字列の格納されている番地を1つづつずらしていって、終端である0x00が登場するまで文字を表示する。xを毎回8足しているのは、表示する位置を右にずらしている(8pix)。
**マウスカーソル
マウスカーソル描画のときのvramの番地指定がよくわからん。
**セグメント、セグメンテーション
:セグメント、セグメンテーション| segment,segmentation.4GBあるメモリ空間を切り分けて、ブロックの最初の番地を0として扱えるようにしたもの。切り分けたブロックをセグメントと呼ぶ。これによって様々なプログラムを自身が0番地から読み込まれるものとして作成することができる。ORG 0.
-セグメントの大きさ
-セグメントの開始番地
-セグメントの管理属性
これらを表現するために8バイトのデータを使用する。セグメントレジスタは16ビットしかないので、セグメントレジスタにはセグメント番号を入れる。
:セグメント番号| 一般的にはセグメントセレクタと呼ばれている。セグメントレジスタは16ビットだが、下位3ビットは使用できないので、13ビットで表現できる0〜8191個のセグメント番号が使用できる。
8192個のセグメントが定義できて、1個につき8バイトのデータを保持しているので
8192*8=1024*8*8=64KB
このセグメント情報の表を全部格納するには64KB必要になる。この表のことをGDT(Global Discriptor Table)と呼ぶ。また、その先頭野番地と有効設定個数をCPUのGDTR(Global Discriptor Table Register)に保存する。
***segment discriptor
構造体を定義している。これがGDTの中身になっている。
struct SEGMENT_DISCRIPTOR{
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
}
全部で8バイト。
ここではinit_gdtldt関数において
struct SEGMENT_DISCRIPTOR *gdt = (struct SEGMENT_DISCRIPTOR*) 0x00270000;
として0x00270000番地を開始番地にしている。ここから0x0027ffff番地までがGDTとなる。
初期化は以下のようにして行う。
for(i=0; i<8192; i++){
set_segmdsec(gdt+i, 0, 0,, 0);
}
8192まで順にセグメントディスクリプタをセットしている。8192を16進数で表すと0x1fffにしかならないが、ポインタの足し算は実際にはそのポインタの型のバイト数分をかけたものが行われるので、
この初期化によって0x00270000-0x0027ffff番地が埋められたことになる。
設定は以下のように。
set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092)
set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a)
上のものはセグメント番号1のセグメント、下のはセグメント番号2のセグメント、に対する設定。
番号、リミット、開始番地、属性を設定している。1の設定は0x00000000〜4GB分なので、
全メモリを扱うことができるセグメント。
2のセグメントは0x00280000を開始番地として、そこから0x0007ffff(512KB)分を扱えるセグメント。
ここにはちょうどbootpack.hrbがくる。セグメントの開始番地とプログラムの開始番地(ORG命令で指定された番地)が一致しないといけない。
GDTRにこのアドレスやリミット値を格納する方法。
_load_gdtr: ; void load_gdtr(int limit, int addr);
MOV AX,[ESP+4] ; limit
MOV [ESP+6],AX
LGDT [ESP+6]
RET
この意味がわからなかったんだけど、調べてたらこんなことが書いてあった。
>_load_gdtr(void load_gdtr(int limit, int addr)関数)は指定されたリミットと番地を48ビットレジスタであるGDTRに代入します。48ビットレジスタには単にMOV命令では代入できないので、LGDT命令を使用します。LGDT命令は指定されたアドレスから48ビット分の内容を読み取ってGDTRレジスタに代入します。なので、第一引数が入っている[ESP+4]を[ESP+6]に移動して、16ビット分ずらして、2つのデータで64-16=48ビットになるようにして、LGDT命令で[ESP+6]を指定してます。
この人もOS自作入門をやってたみたい。
これ見てたらわかったような木になったんだけど、スタックの形とかがまだ良くわからん。
***gate discriptor
これがIDTの中身になっている。
struct GATE_DISCRIPTOR{
short offset_low, selector;
char dw_count, access_right;
short offset_high;
}
初期化はGDTと同様。ポインタの足し算を行っている。
**割り込み
CPUが機器の反応をいちいち待っていなくても大丈夫なように、機器側からCPUに働きかける仕組みのことを割り込みという。PCには様々な機器がつながっているため、これがないとたいへん。
:IDT| Interupt Discriptor Table.割り込み記述子表。割り込みが発生したら、その割り込みに応じた処理を行うための情報が記述されている。
**stack(スタック)
#ref(stack growing.gif)
スタックは下位のメモリ番地に伸びていく。スタックを積む(PUSH)ということは、ESPを4減らす、減らしたところから上位32ビットに値を格納するということ。
----