GDT是如何切换的

      首先要搞清楚一点,gdtr中存放的是线性地址,而不是物理地址。除了cr3以外,开启了分页所有地址都要通过页表的转换,这是硬件决定的,绕不过去的。为何除了cr3,这显然很好理解,线性地址是先通过cr3找到页目录表的基址,然后才能开始转换,如果cr3中的基址也是线性地址,将会造成死循环。

      我们以Orange's中的代码为例,首先看gdtr第一次载入的代码:

//Loader.asm

org  0100h

	jmp	LABEL_START		; Start

; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息
%include	"fat12hdr.inc"
%include	"load.inc"
%include	"pm.inc"


; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------
;                                                段基址            段界限     , 属性
LABEL_GDT:			Descriptor             0,                    0, 0						; 空描述符
LABEL_DESC_FLAT_C:		Descriptor             0,              0fffffh, DA_CR  | DA_32 | DA_LIMIT_4K			; 0 ~ 4G
LABEL_DESC_FLAT_RW:		Descriptor             0,              0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K			; 0 ~ 4G
LABEL_DESC_VIDEO:		Descriptor	 0B8000h,               0ffffh, DA_DRW                         | DA_DPL3	; 显存首地址
; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------

GdtLen		equ	$ - LABEL_GDT
GdtPtr		dw	GdtLen - 1				; 段界限
		dd	BaseOfLoaderPhyAddr + LABEL_GDT		; 基地址

; GDT 选择子 ----------------------------------------------------------------------------------
SelectorFlatC		equ	LABEL_DESC_FLAT_C	- LABEL_GDT
SelectorFlatRW		equ	LABEL_DESC_FLAT_RW	- LABEL_GDT
SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT + SA_RPL3
; GDT 选择子 ----------------------------------------------------------------------------------


BaseOfStack	equ	0100h


LABEL_START:

...

; 下面准备跳入保护模式 -------------------------------------------

; 加载 GDTR
	lgdt	[GdtPtr]

; 关中断
	cli

; 打开地址线A20
	in	al, 92h
	or	al, 00000010b
	out	92h, al

; 准备切换到保护模式
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax

; 真正进入保护模式
	jmp	dword SelectorFlatC:(BaseOfLoaderPhyAddr+LABEL_PM_START)


       LABEL_START是从boot中跳过来后开始执行的地方,可以看到,加载GDTR的那一句是在进入保护模式之前,也就是说此时GDTR中存储的地址是物理地址,即BaseOfLoaderPhyAddr + LABEL_GDT。


      下面是Loader开启分页机制以后跳入内核执行的代码。

//Loader.asm

; 从此以后的代码在保护模式下执行 ----------------------------------------------------
; 32 位代码段. 由实模式跳入 ---------------------------------------------------------
[SECTION .s32]

ALIGN	32

[BITS	32]

LABEL_PM_START:
	mov	ax, SelectorVideo
	mov	gs, ax
	mov	ax, SelectorFlatRW
	mov	ds, ax
	mov	es, ax
	mov	fs, ax
	mov	ss, ax
	mov	esp, TopOfStack

	push	szMemChkTitle
	call	DispStr
	add	esp, 4

	call	DispMemInfo
	call	SetupPaging

	;mov	ah, 0Fh				; 0000: 黑底    1111: 白字
	;mov	al, 'P'
	;mov	[gs:((80 * 0 + 39) * 2)], ax	; 屏幕第 0 行, 第 39 列。

	call	InitKernel

	;jmp	$

	;***************************************************************
	jmp	SelectorFlatC:KernelEntryPointPhyAddr	; 正式进入内核 

      开启分页机制以后,GDTR中的地址值是没有发生改变的,但该地址的实际意义已经发生变化,原先由于分页机制没有开启是直接寻址的,现在分页机制已经开启,这也就意味着现在该地址需要被通过cr3指向的页目录表及相应的页表转换成物理地址进行定位。

      显然,为了在开启分页机制后仍能使用原先的GDT表,至少需要BaseOfLoaderPhyAddr + LABEL_GDT到BaseOfLoaderPhyAddr + LABEL_GDT + GDT_LEN - 1这段线性地址映射到同样的物理地址。


      下面我们来看GDT在Kernel中的转换:

//Kernel.asm

[section .text]	; 代码在此

global _start	; 导出 _start

_start

...

	; 把 esp 从 LOADER 挪到 KERNEL
	mov	esp, StackTop	; 堆栈在 bss 段中

	sgdt	[gdt_ptr]	; cstart() 中将会用到 gdt_ptr
	call	cstart		; 在此函数中改变了gdt_ptr,让它指向新的GDT
	lgdt	[gdt_ptr]	; 使用新的GDT

	;lidt	[idt_ptr]

	jmp	SELECTOR_KERNEL_CS:csini

         (在这里插一句,有人可能不太理解最后一句,解释一下:cs的值在保护模式是下不能直接更改的,此处利用这个方法,将cs强制赋值为8,相当于LABEL_DESC_FLAT_C的选择子,由于此值为0,0:csinit也就跳到了csinit执行。)

这里用了cstart这个函数来改变GDT,它在start.c中,让我们来看一下:

//start.c

#include "type.h"
#include "const.h"
#include "protect.h"

PUBLIC	void*	memcpy(void* pDst, void* pSrc, int iSize);

PUBLIC	void	disp_str(char * pszInfo);

PUBLIC	u8		gdt_ptr[6];	/* 0~15:Limit  16~47:Base */
PUBLIC	DESCRIPTOR	gdt[GDT_SIZE];

PUBLIC void cstart()
{

	disp_str("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
		 "-----\"cstart\" begins-----\n");

	/* 将 LOADER 中的 GDT 复制到新的 GDT 中 */
	memcpy(&gdt,				   /* New GDT */
	       (void*)(*((u32*)(&gdt_ptr[2]))),    /* Base  of Old GDT */
	       *((u16*)(&gdt_ptr[0])) + 1	   /* Limit of Old GDT */
		);
	/* gdt_ptr[6] 共 6 个字节:0~15:Limit  16~47:Base。用作 sgdt/lgdt 的参数。*/
	u16* p_gdt_limit = (u16*)(&gdt_ptr[0]);
	u32* p_gdt_base  = (u32*)(&gdt_ptr[2]);
	*p_gdt_limit = GDT_SIZE * sizeof(DESCRIPTOR) - 1;
	*p_gdt_base  = (u32)&gdt;
}

此处的gdt即是新的GDT表了,由于现在是在保护模式中,分页机制也已经开启,现在只需要重新定义它的值,把它的地址(此处即是线性地址)和长度放到gdt_ptr中,然后用lgdt将其载入GDTR中即可完成GDT的转换了。因为这就是我们需要的线性地址。

      GDT的切换到此已完结,下面是分析Orange's的实现方式,由于它的实现为求简单,分页机制是全部地址直接对应物理地址的,不具备通用性,因此只留给在看Orange's的朋友看看。

---------------------------------------------------------------------------------------------------------------------------------------------------------

      Orange's中并没有重新定义GDT表,而是沿用了老GDT表,重点在memcpy这个函数,Kernel.asm中的

sgdt [gdt_ptr]一句已经将原先GDTR中的值赋值给了gdt_ptr,这个值的低16位是界限,即GDT的长度,高32位是GDT的基址。

       参数中的&gdt是新GDT表的基址,此值为虚拟地址,第二个参数是将gdt_ptr中的基址拿出来,该值也是虚拟地址,我们前面已经说过开启分页机制时保证了线性地址与物理地址相同,此处是通过将ds、es等数据段值赋值0实现了 逻辑地址 = 线性地址 = 物理地址,第三个参数是将gdt_ptr中的长度拿出来。

       这个函数所做的事情是将从老GDT的基址开始复制老GDT的长度个字节到新GDT的基址开始的地方,即将老GDT赋值给新GDT。该函数是用汇编实现的,在string.asm中:

//string.asm

; ------------------------------------------------------------------------
; void* memcpy(void* es:pDest, void* ds:pSrc, int iSize);
; ------------------------------------------------------------------------
memcpy:
	push	ebp
	mov	ebp, esp

	push	esi
	push	edi
	push	ecx

	mov	edi, [ebp + 8]	; Destination
	mov	esi, [ebp + 12]	; Source
	mov	ecx, [ebp + 16]	; Counter
.1:
	cmp	ecx, 0		; 判断计数器
	jz	.2		; 计数器为零时跳出

	mov	al, [ds:esi]		; ┓
	inc	esi			; ┃
					; ┣ 逐字节移动
	mov	byte [es:edi], al	; ┃
	inc	edi			; ┛

	dec	ecx		; 计数器减一
	jmp	.1		; 循环
.2:
	mov	eax, [ebp + 8]	; 返回值

	pop	ecx
	pop	edi
	pop	esi
	mov	esp, ebp
	pop	ebp

	ret			; 函数结束,返回
; memcpy 结束------------------------------------------------------------

      函数很简单,不做更进一步的说明了。注意这个函数默认ds和es值是相等的且同时为0的。


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值