认识保护模式

来源:互联网 发布:机房的网络结构示意图 编辑:程序博客网 时间:2024/05/16 08:50

想看懂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.code16start: jmpl $0x0, $code /**----------------------------------------------------------------- * Global Descriptor Table: GDT *-------------------------------*/GDT_START:Descriptor_DUMMY:       Descriptor      0,                              0, 0Descriptor_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 = 0x98DA_32 = 0x4000DA_DRW = 0x92.text.globl start.code16start: 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   ,%axmov %ax    ,%bpmov $12    ,%cxmov $0x1301,%axmov $0x000c,%bxmov $0     ,%dlint $0x10/*加载gdtr即将全局描述符表gdt的首地址和gdt的界限赋给gdtr寄存器*/       lgdt GdtPtr/*关中断*/cli/*打开地址线A20*/inb $0x92,%alor  $0x02,%aloutb %al,$0x92/*设置cr0寄存器,切换到保护模式*/movl %cr0,%eaxor   $1,%eaxmovl %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.code32movw $0x10,%axmovw %ax,%gsmovl $((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, %eaxmov     %cs, %ax      # %cs -> % ax               shl     $4, %eax         # 左移四位            addl    $(LABEL_SEG_CODE32), %eax   # 下面是用LABEL_SEG_CODE32填充描述符Descript_CODE32movw    %ax, (Descript_CODE32 + 2)shr     $16, %eaxmovb    %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    =asLD    =ldLDFLAGS_CODE32    =-m elf_i386 -e startup_32 -Ttext 0LDFLAGS_CODE16   = --oformat binary -N -e start -Ttext 0x7c00Image:myboot #headdd bs=512 if=myboot of=Image count=1 conv=notruncsyncmyboot:myboot.s$(AS) -o myboot.o -a myboot.s$(LD) $(LDFLAGS_CODE16) -o myboot myboot.oclean:rm -f  Image myboot.s myboot head  *.o 



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

DA_32EQU4000h; 32 位段DA_DRWEQU92h; 存在的可读写数据段属性值DA_CEQU98h; 存在的只执行代码段属性值%macro Descriptor 3dw%2 & 0FFFFh; 段界限1dw%1 & 0FFFFh; 段基址1db(%1 >> 16) & 0FFh; 段基址2dw((%2 >> 8) & 0F00h) | (%3 & 0F0FFh); 属性1 + 段界限2 + 属性2db(%1 >> 24) & 0FFh; 段基址3%endmacro ; 共 8 字节org07c00hjmpLABEL_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 结束GdtLenequ$ - GDT_START; GDT长度GdtPtrdwGdtLen - 1; GDT界限dd0; GDT基地址; END of [SECTION .gdt][SECTION .s16][BITS16]LABEL_BEGIN:movax, csmovds, axmoves, axmovss, axmovsp, 0100hmov 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 作准备xoreax, eaxmovax, dsshleax, 4addeax, GDT_START; eax <- gdt 基地址movdword [GdtPtr + 2], eax; [GdtPtr + 2] <- gdt 基地址; 加载 GDTRlgdt[GdtPtr]; 关中断cli; 打开地址线A20inal, 92horal, 00000010bout92h, al; 准备切换到保护模式moveax, cr0oreax, 1movcr0, eax; 真正进入保护模式jmpdword 8:LABEL_SEG_CODE32; 执行这一句会把 SelectorCode32 装入 cs,; 并跳转到 Code32Selector:0  处BootMessage:        db  "Hello, OS world!" ; END of [SECTION .s16][SECTION .s32]; 32 位代码段. 由实模式跳入.[BITS32]LABEL_SEG_CODE32:movax, 0x10movgs, ax; 视频段选择子(目的)movedi, (80 * 11 + 79) * 2; 屏幕第 11 行, 第 79 列。movah, 0Ch; 0000: 黑底    1100: 红字moval, 'P'mov[gs:edi], ax; 到此停止jmp$SegCode32Lenequ$ - LABEL_SEG_CODE32; END of [SECTION .s32]


 

原创粉丝点击