CPU実験室

誰も見向きもしない古いCPUをいじって動かしてみようというプロジェクトです

GCCプログラミング(4)

プロテクトモードに切替えるといっても
何も特権機能や保護機能を使ったりマルチタスクを走らせようとかではありません。

単に32ビットセグメントを使いたいだけで、必要最小限の設定にとどめ、
実行後にリアルモードに復元させることもやりません。

手順としては次のような流れになります。

①セグメントディスクリプタの作成

以下のようにコード、データ、スタック用の3個だけ定義します
4Gbyteの全領域読み書き可能なフラットな空間に設定しました。

セレクタ ベース  リミット	属性
08: 00000000 FFFFF TypeCode Execution/Read        32bitモード
10: 00000000 FFFFF TypeData Read/Write             32bitモード 
18: 00000000 00000 TypeStack Read/Write,Expand Down 32bitモード 

②GDTRに登録
 セグメントディスクリプタのアドレスをCPU内部のレジスタGDTRにセットします

③プロテクトモードに切り替え
 これは単純でCPU内部のレジスタCR0のPEビットに1を立てるだけです

④セグメントレジスタセレクタ値をロード

⑤ユーザプログラムをコール


これらの処理をアセンブラで記述するとこんな感じです。

/********************************************************************************
*							   *
*	start up routine	80386/80486 Protected Virtual Address Mode	   *
*							   *
********************************************************************************/

.globl _start
.globl _end
.text
.code16

_start:				/* エントリポイント		*/
	movl	$gdtdat,%ebx
	lgdt	(%ebx)		/* GDTのセット		*/
realtoproto:			/* プロテクトモードへの移行	*/
	cli
	movl	%cr0,%eax
	orl	$1,%eax		/* PEビットをセット		*/
	movl	%eax,%cr0

	jmp	flush_q1		/*  パイプラインフラッシュ	*/
.code32
flush_q1:
	.byte	0xea		/*  セレクタ付jumpの命令	*/
	.word	set_cs
	.word	0x08		/*  セレクタ 0x08		*/
set_cs:
	movw	$0x10,%ax		/*  セレクタ0x10をds,esに	*/
	movw	%ax,%ds
	movw	%ax,%es
	movw	$0x18,%ax		/*  セレクタ0x18をssに	*/
	movw	%ax,%ss
	movl	$0x00080000,%eax	/*  スタックの新しい値	*/
	movl	%eax,%esp

	call _main			/* 32ビットコードをcall	*/
	hlt

/************************************************************************
*						    *
*	GDT用データ				    *
*						    *
************************************************************************/

gdt:
	.byte	0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00	/* セレクタ 0x00 なし	 */
	.byte	0xff,0xff,0x00,0x00, 0x00,0x9a,0xcf,0x00	/* セレクタ 0x08 code  */
	.byte	0xff,0xff,0x00,0x00, 0x00,0x92,0xcf,0x00	/* セレクタ 0x10 data  */
	.byte	0x00,0x00,0x00,0x00, 0x00,0x96,0xc0,0x00	/* セレクタ 0x18 stack */
gdtdat:
	.word	0x0fff		/* Limit = 4k 			*/
	.word	gdt		/* offset 0-15			*/
	.word	0x0000		/* offset 16-31			*/

_end:
	.end

これをスタートアップルーチンstart.oとして先頭にリンクさせました。