认识保护模式

9 篇文章 0 订阅
4 篇文章 0 订阅

想看懂linux内核必须要理解保护模式,也许它并不复杂只是书上讲的太抽象了,下面让我们来认识一下保护模式,在这里我们只考虑全局描述符表GDT。

先来看下面两个重要的寄存器
CS:代码段寄存器(Code Segment Register),其值为代码段的段值
IP:指令指针寄存器,它用来存放代码段中的偏移地址。在程序运行过程中,它始终指向下一条指令的首地址,它与代码段寄存器CS联用确定下一条指令的物理地址

CPU要运行就要执行代码,执行那的代码呢?
CPU永远将CS:IP指向的内存单元中的内容看做要执行的下一条指令。

下面这一点其实也很重要:
jmp的实质就是修改CS和IP这两个寄存器

实模式下和保护模式最大的差别就是地址转换方式发生了变化(当然还有其他)

实模式下    :地址值=段值:偏移值=段值*0x10+偏移值((段值<<4)+偏移值)

保护模式下:地址值=选择子:偏移值=描述符表中第(选择子>>3)项描述符给出的段基址+偏移值


实模式下,假设在CS中存放的是0x8,IP中存入0xFFFF,
那么CS:IP=0x8*0x10+0xFFFF=0x1007F,程序下一步要执行的指令的地址就是0x1007F
这就是众所周知的“段地址左移4位加偏移”(CS<<4+IP)

保护模式下,假设CS=0x8,IP=0xFFFF
那么CS:IP=全局描述符表中第1(0x8>>3)项描述符给出的段基址+0xFFFF
其实CS中的值右移3位可理解为数组的下标,这个数组的首地址就存放在lgdtr寄存器中,这个数组就是全局描述符表GDT,数组中的数组项是一个叫做描述符的结构体

要用保护模式我们应该怎么做呢?我们用C语言来描述着个过程吧
1.定义一个描述符结构体Descriptor
typedef struct{
unsigned int lim_low,//段界限低16位(0-15).word lim&0xffff;
unsigned int base_low,//段基址低16位(0-15).word base&0xffff;
char base_mid.byte, //段基址中间8位(16-23)(base>>16)&0xff;
unsigned int type,//段界限高4位(16-19)与属性的组合.word ((lim>>8)&0xf00)|(type&0x0f0ff);
char base_high//段基址高8位(24-31).byte ((base>>24)&0xff)
}Descriptor;

2.设置区全局描述符表gdt
Descriptor gdt[3];
gdt[0] = Descriptor_DUMMY;//第0项不用里面全是0
gdt[1] = Descriptor_CODE32;//里面存放32位代码段的段基址和段界限
gdt[2] = Descriptor_VIDEO;//里面存放显存的段基址和段界限

3.将全局描述符表gdt的首地址和gdt的界限赋给gdtr寄存器,即lgdt命令
gdtr=((gdt<<16)|(gdt+sizeof(gdt));//
4.关中断,打开地址线A20,设置cr0寄存器

5.跳转,让CS:IP指向你要执行的代码即可
CS=0x8//取gdt表中的第1项,里面有段基址等信息,gdt表在那里?问gdtr寄存器
IP=0xff  //偏移地址

大致流程是这样的,下面看一下AT&T汇编代码吧:

.include "pm.inc"

.text
.globl start
.code16
start:
 jmpl $0x0, $code 
/**-----------------------------------------------------------------
 * Global Descriptor Table: GDT
 *-------------------------------*/
GDT_START:
Descriptor_DUMMY:       Descriptor      0,                              0, 0
Descriptor_CODE32:      Descriptor      0,                        0xFFFFF, (DA_C | DA_32)       
Descriptor_VIDEO:       Descriptor      0xB8000,                   0xFFFF, DA_DRW 

.set    GdtLen,(. - GDT_START)            /* GDT Lenght */

GdtPtr:                 .2byte  GdtLen    /* GDT Limit */
                        .4byte  GDT_START /* GDT Base */

msg:
 .string "Hello world!"
code:
    mov     %cs,%ax   
    mov     %ax,%ds 
    mov     %ax,%es    
    mov     %ax,%ss  
    mov  $0x8000,%sp  
/*显示HelloWorld字符串*/
    mov $msg   ,%ax
    mov %ax    ,%bp
    mov $12    ,%cx
    mov $0x1301,%ax
    mov $0x000c,%bx
    mov $0     ,%dl
		
    int $0x10

/*加载gdtr即将全局描述符表gdt的首地址和gdt的界限赋给gdtr寄存器*/       
    lgdt GdtPtr

/*关中断*/
    cli

/*打开地址线A20*/
    inb $0x92,%al
    or  $0x02,%al
    outb %al,$0x92

/*设置cr0寄存器,切换到保护模式*/
    movl %cr0,%eax
    or   $1,%eax
    movl %eax,%cr0


/*真正进入保护模式,执行此命令后CS=0x8,IP=LABEL_SEG_CODE32的偏移地址*/
    ljmp $0x8,$(LABEL_SEG_CODE32)
/*此时CS:IP=全局描述符表中第1(0x8>>3)项描述符给出的段基址+LABEL_SEG_CODE32的偏移地址*/


LABEL_SEG_CODE32:
.align  32
.code32
    movw $0x10,%ax
    movw %ax,%gs
    movl $((80*11+79)*2),%edi/*第11行,79列*/
    movb $0x0c,%ah/*高四位表示黑底,低四位表示红字*/
    movb $'P',%al/*显示的字符*/
    movw %ax,%gs:(%edi)

loop2:
    jmp loop2

.org 0x1fe, 0x90 
.word 0xaa55   


pm.inc中的代码:

/**-----------------------------------------------------------------
 * 描述符数据结构
 *-------------------------------------------------------------*/
.macro Descriptor Base, Limit, Attr
        .2byte  \Limit & 0xFFFF
        .2byte  \Base & 0xFFFF
        .byte   (\Base >> 16) & 0xFF
        .2byte  ((\Limit >> 8) & 0xF00) | (\Attr & 0xF0FF)
        .byte   (\Base >> 24) & 0xFF
.endm

/**-----------------------------------------------------------------
 * 描述符属性
 *----------------------------*/
.set    DA_32,          0x4000          /* 32-bit segment */
.set    DA_DRW,         0x92            /* Read/write */
.set    DA_C,           0x98            /* Execute-only */


 如果上面pm.inc中的代码不好理解也可以写成这样:

#define Descriptor(base,lim,type)\
.word lim&0xffff;\
.word base&0xffff;\
.byte (base>>16)&0xff;\
.word ((lim>>8)&0xf00)|(type&0x0f0ff);\
.byte ((base>>24)&0xff)

DA_C = 0x98
DA_32 = 0x4000
DA_DRW = 0x92

.text
.globl start
.code16
start:
 jmpl $0x0, $code 
		
GDT_START:	
Descriptor_DUMMY:Descriptor(0x0,0x0,0x0)
Descript_CODE32 :Descriptor(0x0,0xffffffff,DA_C+DA_32)
Descriptor_VIDEO:Descriptor(0xb8000,0x0ffff,DA_DRW)
GDT_END:

GdtPtr:
	.word (GDT_END-GDT_START)-1	# so does gdt 
	.long GDT_START 	# This will be rewrite by code.
msg:
 .string "Hello world!"
code:
	mov     %cs,%ax   
	mov     %ax,%ds 
	mov     %ax,%es    
	mov     %ax,%ss  
	mov  $0x8000,%sp  
	/*显示HelloWorld字符串*/
	mov $msg   ,%ax
	mov %ax    ,%bp
	mov $12    ,%cx
	mov $0x1301,%ax
	mov $0x000c,%bx
	mov $0     ,%dl
	
	int $0x10
	
/*加载gdtr即将全局描述符表gdt的首地址和gdt的界限赋给gdtr寄存器*/       
	lgdt GdtPtr

/*关中断*/
	cli

/*打开地址线A20*/
	inb $0x92,%al
	or  $0x02,%al
	outb %al,$0x92

/*设置cr0寄存器,切换到保护模式*/
	movl %cr0,%eax
	or   $1,%eax
	movl %eax,%cr0


/*真正进入保护模式,执行此命令后CS=0x8,IP=LABEL_SEG_CODE32的偏移地址*/
	ljmp $0x8,$(LABEL_SEG_CODE32)
/*此时CS:IP=全局描述符表中第1(0x8>>3)项描述符给出的段基址+LABEL_SEG_CODE32的偏移地址*/


LABEL_SEG_CODE32:
.align  32
.code32
	movw $0x10,%ax
	movw %ax,%gs
	movl $((80*11+79)*2),%edi/*第11行,79列*/
	movb $0x0c,%ah/*高四位表示黑底,低四位表示红字*/
	movb $'P',%al/*显示的字符*/
	movw %ax,%gs:(%edi)
	
loop2:
	jmp loop2

.org 0x1fe, 0x90 
.word 0xaa55 

其实你也可以在jmp前面添加如下代码:

/*初始描述符Descript_CODE32*/	
	xor     %eax, %eax
	mov     %cs, %ax      							# %cs -> % ax               
	shl     $4, %eax         						# 左移四位            
	addl    $(LABEL_SEG_CODE32), %eax   # 下面是用LABEL_SEG_CODE32填充描述符Descript_CODE32
	movw    %ax, (Descript_CODE32 + 2)
	shr     $16, %eax
	movb    %al, (Descript_CODE32 + 4)
	movb    %ah, (Descript_CODE32 + 7)

然后这样跳转:

ljmp $0x8,$0


我们可以讲初始化描述符定义成一个宏:

.macro InitDescrptor Descriptor, SegBase
        xor     %eax, %eax
        mov     %cs, %ax               /* %cs -> % ax */  
        shl     $4, %eax               /*左移四位 */
        addl    $(\SegBase), %eax      /* 下面是用SegBase填充描述符Descriptor */
        movw    %ax, (\Descriptor + 2)
        shr     $16, %eax
        movb    %al, (\Descriptor + 4)
        movb    %ah, (\Descriptor + 7)
.endm


当然也可以这样:

/*
*InitDescrptor(Descriptor,SegBase)初始化描述符函数
*Descriptor:要初始化的描述符
*SegBase:段基址
*/
#define InitDescrptor(Descriptor,SegBase)\
xor     %eax,%eax; \
mov     %cs,%ax  ; \                         
shl     $4,%eax  ; \       	            
addl    $(SegBase), %eax ;\     
movw    %ax, (Descriptor + 2);\
shr     $16, %eax;\
movb    %al, (Descriptor + 4);\
movb    %ah, (Descriptor + 7)


这样我们就可以像C语言一样使用了

/*初始描述符Descript_CODE32*/
InitDescrptor(Descript_CODE32,LABEL_SEG_CODE32);


还记得吗jmp的实质就是改变CS和IP寄存器的值,我们只要保证CS:IP=我们的目的地,然后你就会看到跳转成功。


 

上面代码含有宏,因as的预处理能力有限,所以要先用gcc做预处理可以先gcc  -E  myboot.S > myboot.s或gcc -E myboot.S -o myboot.s再进行编译

也可用下面的Makefile进行编译:

# Makefile for the simple example kernel.
AS    =as
LD    =ld
LDFLAGS_CODE32    =-m elf_i386 -e startup_32 -Ttext 0
LDFLAGS_CODE16   = --oformat binary -N -e start -Ttext 0x7c00


Image:myboot #head
	dd bs=512 if=myboot of=Image count=1 conv=notrunc
	sync

myboot:myboot.s
	$(AS) -o myboot.o -a myboot.s
	$(LD) $(LDFLAGS_CODE16) -o myboot myboot.o



clean:
	rm -f  Image myboot.s myboot head  *.o 



 贴出nasm的汇编代码以供参考:

DA_32		EQU	4000h	; 32 位段
DA_DRW		EQU	92h	; 存在的可读写数据段属性值
DA_C		EQU	98h	; 存在的只执行代码段属性值
%macro Descriptor 3
	dw	%2 & 0FFFFh				; 段界限1
	dw	%1 & 0FFFFh				; 段基址1
	db	(%1 >> 16) & 0FFh			; 段基址2
	dw	((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)	; 属性1 + 段界限2 + 属性2
	db	(%1 >> 24) & 0FFh			; 段基址3
%endmacro ; 共 8 字节
org	07c00h
	jmp	LABEL_BEGIN
[SECTION .gdt]
; GDT
;                                    段基址,    段界限 ,    属性
GDT_START:
Descriptor_DUMMY:	   Descriptor       0,       0,            0           ; 空描述符
Descript_CODE32:     Descriptor       0,       0xffffffff,   DA_C + DA_32; 非一致代码段
Descriptor_VIDEO:    Descriptor 0B8000h,       0ffffh,       DA_DRW	     ; 显存首地址
; GDT 结束

GdtLen		equ	$ - GDT_START	; GDT长度
GdtPtr		dw	GdtLen - 1	; GDT界限
					dd	0		; GDT基地址

; END of [SECTION .gdt]

[SECTION .s16]
[BITS	16]
LABEL_BEGIN:
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, 0100h
	
	mov ax, BootMessage  
         mov bp, ax          ; ES:BP = 串地址  
          mov cx, 16          ; CX = 串长度  
          mov ax, 01301h      ; AH = 13,  AL = 01h  
         mov bx, 000ch       ; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)  
         mov dl, 0  
         int 10h         ; 10h 号中断 

	
	; 为加载 GDTR 作准备
	xor	eax, eax
	mov	ax, ds
	shl	eax, 4
	add	eax, GDT_START		; eax <- gdt 基地址
	mov	dword [GdtPtr + 2], eax	; [GdtPtr + 2] <- gdt 基地址

	; 加载 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 8:LABEL_SEG_CODE32	; 执行这一句会把 SelectorCode32 装入 cs,
					; 并跳转到 Code32Selector:0  处
BootMessage:        db  "Hello, OS world!" 
; END of [SECTION .s16]


[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS	32]

LABEL_SEG_CODE32:
	mov	ax, 0x10
	mov	gs, ax			; 视频段选择子(目的)

	mov	edi, (80 * 11 + 79) * 2	; 屏幕第 11 行, 第 79 列。
	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
	mov	al, 'P'
	mov	[gs:edi], ax

	; 到此停止
	jmp	$

SegCode32Len	equ	$ - LABEL_SEG_CODE32
; END of [SECTION .s32]


 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,我之前的回答与你的问题不太符合。让我重新回答你的问题。 有一个难忘的生物学课堂案例是关于昆虫生态的学习实例。在这堂课上,老师采用了实地考察的模式,带领我们去学校附近的公园进行昆虫观察和生态环境调查。 在公园里,我们分成小组,配备了放大镜、昆虫捕捉器等工具,并按照老师提供的调查表格记录我们观察到的昆虫种类、数量和栖息环境等信息。 在整个考察过程中,我们看到了各种各样的昆虫,如蝴蝶、蜜蜂、蚂蚁等,还观察到它们在花朵间飞舞、采集花粉、建造巢穴等行为。通过实地考察,我们深入了解了昆虫的生活习性、种类和它们与环境的相互作用。 这次生物学课堂学习实例给我留下了深刻的印象,原因如下: 1. 知识层面:通过实地观察和调查,我对昆虫的生态习性和种类有了直观的了解。实践中,我能够亲眼观察昆虫的行为,加深了对它们的认识,并且能够将课堂上学到的理论知识与实际情况相结合,更好地理解生物学的概念。 2. 能力提升:这次实地考察锻炼了我们的观察力、实地调查和数据记录能力。我们需要仔细观察昆虫的特征和行为,并记录下来。这种实践中培养的能力对于我们今后的科学研究和实验能力的发展非常重要。 3. 情感共鸣:通过与昆虫亲密接触,我产生了对大自然的敬畏之情。在观察昆虫的过程中,我不仅了解了它们的生态习性,还体会到了它们与环境之间微妙的平衡关系。这种情感共鸣让我更加意识到保护环境和生物多样性的重要性。 综上所述,这次生物学课堂学习实例通过实地考察昆虫生态,不仅加深了对昆虫知识的理解,还提升了我们的观察和实地调查能力,同时也唤起了对大自然的敬畏之情。这样的学习方式既让我们亲身体验生物学的奇妙之处,又激发了对生物学的兴趣。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值