Linux勉強 パート7 GRUBコード後半 プロテクトモードへ移行

Linux

プロテクトモードへ移行

GURBU後半へジャンプ

ここを参照しながら読んでみてください。
GRUB前半コード
ソースコード
MBRに読み出されるファイルはdiskboot.Sというファイルです。
2セクター目に配置されています。

GRUB後半コード
ソースコード

ようやくGRUBの後半

LOCAL(bootit):
MSG(notification_done)
popw	%dx
ljmp $0,$(GRUB_BOOT_MACHINE_KERNEL_ADDR + 0x200)

GRUBののコードを読み込んだメモリのアドレスに0x200(512)、つまり1セクタ分加算して新しく読み込んだGRUBの残りへジャンプします。

今までの復習

色いろやったので、ちょっと一息入れて整理してみます。

先ずMBRがGRUBの始めの方を0x8000にコピーした場所にジャンプして処理を移したところからスタートします。
MBRからGRUBに処理を移したCPUはfirstlist構造体から残りのGRUBのセクタ数と位置を特定し、読み出します。

bootloopが残りのセクタ数を確認し、残っていればsetup_sectorsを繰がり返すことですべてのセクタを0x70000以降へ読み出します。
copy_bufferが読み込んだデータを適切なアドレスへ移します。
これは互換性のために古いモデルのメモリマップをそのまま使うためです。
こうしないと古い規格で作ったプログラムが動かなかった。

新しいモデルで使えるようになったメモリ空間に読み込んだセクタのデータをコピーし、すべて移し終わったらそこへ移動して処理を再開します。

一旦0x70000(狭い場所)に置いて0x8000(広い場所)へ移しかえて0x8000へジャンプします。

0x8000にはMBRが読み込んだGRUBの始めの方があるので続きは512バイト足して0x8200へコピーしています。

GRUBの後半へ突入

2番目のソースコードを参照してください。

マクロ

#define ABS(x)	((x) - LOCAL (base) + GRUB_BOOT_MACHINE_KERNEL_ADDR + 0x200)
.file	"startup_raw.S"
.text
.code16
.globl	start, _start

start:
_start:
LOCAL (base):
#ifdef __APPLE__
	ljmp $0, $(ABS(LOCAL (codestart)) - 0x10000)
#else
	ljmp $0, $ABS(LOCAL (codestart))
#endif

#ifdefはコンパイル時に以下に続くマクロ名が#define マクロ名で定義されていれば、
#ifdef __APPLE__
#endif
で囲まれている命令を実行します。
もし定義されていなければコンパイルされません。
デバッグなど本来の処理と関係ないけど、プログラムの動いている間にどんな動きをするのか確かめたい時などに使用されます。
ブートするOSがAPPPLE製の場合のみ#ifdef #endifの間の処理を実行します。
__APPLE__という名前が定義されていれば#ifdefの内容、それ以外は#else以下の内容を実行していきます。

ljmp $0, $(ABS(LOCAL (codestart)) – 0x10000)は#define ABS(x)で定義されているマクロを利用しジャンプする先を分岐します。
#else以下の内容はcodestartへ普通にジャンプするだけなので、プログラムを読み込むメモリの配置がappleは他のOSと違うのかもしれません。

#defineはコンパイル時に文字列の置換処理にを行います。
この場合はxに与えられた数値を((x) – LOCAL (base) + GRUB_BOOT_MACHINE_KERNEL_ADDR + 0x200)で出力した内容と置き換えます。
cordstartからbaseの値を引いて、さらに0x8200を追加します。
baseが0x8200に読み込まれているのでその値を引いてさらに同じ0x8200を加えています。
必ずメモリの0x8200からこのcordstartを実行するためだと思われます。
マシンやOSによる誤差を吸収しているんですかね。

cordstart

48~73行目にはコメントで
This is a special data area.
とあるのでそのデータエリアを飛び越えてcordstartへジャンプします。

以下は圧縮されているGRUBの内容を展開する手続きです。

LOCAL (codestart):
cli
xorw	%ax, %ax
movw	%ax, %ds
movw	%ax, %ss
movw	%ax, %es
movl	$GRUB_MEMORY_MACHINE_REAL_STACK, %ebp
movl	%ebp, %esp
sti

ADDR32	movb	%dl, LOCAL(boot_drive)	
int	$0x13
DATA32   call  real_to_prot

cliで割り込みを禁止しaxレジスタを0で埋めて他のレジスタの値も同じように0クリア。
また割り込みを受け付けます。

ADDR32はオペランドのアドレスが32ビットであることをCPUに伝えルもので、boot_driveにドライブ番号を保存しています。

real_to_protでリアルモードからプロテクトモードへ移行

ようやくリアルモードからプロテクトモードへ切り替えるところ。長い。
DATA32 call real_to_prot
このサブルーチンを呼び出して16ビットのリアルモードから32ビットのプロテクトモードへ移行します。

コードは別のファイルにあり、includeしています。
ソースコード

プロテクトモードへ移行する前にGDTとIDTについて勉強しておきます。

GDT(グローバルディスクリプタテーブル)

プロテクトモードへの移行には以下の2つのデータ構造体が必要になります。
ひとつはGDT。
Global Descriptor Table(GDT)
について少し補足すると(GDTはLinuxでは使われませんメモリ全体を一つのセグメントとして認識させページングという機能を使います)、プロテクトモードとリアルモードはメモリのアクセス方法が異なります。

リアルモードにはメモリを保護する仕組みがありませんが、プロテクトモードはそれを実現しています。
そのため様々なチェックに必要なデータを用意する必要があります。
32ビットレジスタではそれらの情報は収まりきらないためメモリにデータ構造として用意します。
それがディスクリプタと言われるものです。

具体的にはセグメントのアクセス範囲、アクセス権限などの管理をする属性をメモリに配置ています。
セグメントにアクセスする時は属性や範囲に異常がないことが確認し問題ない場合、セグメントのメモリアドレスにアクセスできます。
リアルモードではセグメントアドレスを直接指定していたcs、dsレジスタはプロテクトモードではディスクリプタのメモリアドレスの先頭を指すのに使われ、ベースとなるアドレスはそのディスクリプタのデータ構造から参照します。
直接セグメントをアプリケーションが弄くれる状態だと危険なので安全のためです。

IDT(インターラプトディスクリプタテーブル)

割り込みディスクリプタテーブルですね。訳すなら。

gdtに続いてidtについて。

Interrupt Descriptor Table(IDT)
割り込みの制御に用いられます。
8byteの連続したデータ配列で割り込みや例外によって処理をそれぞれ割り当てます。
割り込みは他のプログラムがcpuを使っていいない間他のプログラムに使用を明け渡します。
こうすることでメモリなどへの不正アクセスなどの例外処理やメモリにアクセスしている間の余った時間を効率よく良く使うことができます。。

リアルモードとプロテクトモードを行き来するルーチンを見てみます。

real_to_prot:
.code16
cli
xorw %ax,  %ax
movw     %ax,  %ds
DATA32   ADDR32 lgdt     gdtdesc


movl	%cr0, %eax
orl	$GRUB_MEMORY_CPU_CR0_PE_ON, %eax
movl	%eax, %cr0

DATA32	ljmp	$GRUB_MEMORY_MACHINE_PROT_MODE_CSEG, $protcseg

lgdt命令でgdtレジスタにgdtのアドレスを設定します。

プロテクトモードへ以降するにはcr0の最下位のビットに1を設定します。
eaxをcr0にコピーしorlで論理積をとることで他のcr0の値を変えずに最下位のビットに1だけを加えています。
GRUB_MEMORY_CPU_CR0_PE_ONには0x1が格納されています。

インテルのcpuはプリフェッチキューをフラッシュするためにプロテクトモードに移行した場合callかjmpをしなければなりません、
なのでljmp命令を実行します、ついでにljmp命令でcsをセットもしています。
プリフェッチとはコンピューターが要求するかもしれない命令を空いた時間で先に読み込んでおく仕組みです。
高速化の技術です。
プロテクトモード移行の前にキャッシュをフラッシュしなけらばなりません。

cr0の最下位をセットしたのでプロテクトモード、32ビットで動くようにしています。

.code32
protcseg:
movw	$GRUB_MEMORY_MACHINE_PROT_MODE_DSEG, %ax
movw	%ax, %ds
movw	%ax, %es
movw	%ax, %fs
movw	%ax, %gs
movw	%ax, %ss

他のセグメントレジスタにロード。

プログラミングおすすめ書籍

おすすめ書籍

なぜプログラムが動くのか。
平易な言葉で学べますが、初心者には難しいかもしれません。
でも読み終わった頃にはなんとなく理解しているでしょう。
初心者には家にあって損はありません。

UNIXの思想を学べます。
高尚なタイトルですが、コードはおろか難しい言葉も殆どありません。取っ掛かりに最適でその後のプログラミングに対する理解を一変させます。
なぜならこの思想はプログラミングそのものだからです。
一読の価値ありで、これは良書なので今後紹介したいと思っています。

まだまだありますがまたの機会に紹介したいと思います。

Linux コンピューター
スポンサーリンク
himazinをフォローする
私の頭の上の消しゴム

コメント

タイトルとURLをコピーしました