lab1-练习3 实模式与保护模式的转换
练习3:分析bootloader进入保护模式的过程。(要求在报告中写出分析)
BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。请分析bootloader是如何完成从实模式进入保护模式的。
提示:需要阅读小节“保护模式和分段机制”和lab1/boot/bootasm.S源码,了解如何从实模式切换到保护模式,需要了解:
- 为何开启A20,以及如何开启A20
- 如何初始化GDT表
- 如何使能和进入保护模式
实验报告 练习3
实验指导书: 实模式&保护模式&段表的建立及其使用:
https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_3_2_1_protection_mode.html?q=
bootasm.S源码:(个人的注释用//标注)
#include <asm.h>
# Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.
.set PROT_MODE_CSEG, 0x8 # kernel code segment selector
.set PROT_MODE_DSEG, 0x10 # kernel data segment selector
.set CR0_PE_ON, 0x1 # protected mode enable flag
# start address should be 0:7c00, in real mode, the beginning address of the running bootloader
.globl start
start://从start开始是实模式到保护模式的转变
.code16 # Assemble for 16-bit mode
cli # Disable interrupts 屏蔽中断
cld # String operations increment 字符串比较的时候指针自增
# Set up the important data segment registers (DS, ES, SS).
xorw %ax, %ax # 将ax清零 异或自己,这样就能快速的清零
movw %ax, %ds # -> Data Segment 清零数据段
movw %ax, %es # -> es一般是数据段的偏移寄存器 清零
movw %ax, %ss # -> 清零栈寄存器
# Enable A20:
# For backwards compatibility with the earliest PCs, physical
# address line 20 is tied low, so that addresses higher than
# 1MB wrap around to zero by default. This code undoes this.
// 等待8042键盘控制器不忙
seta20.1:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.1
movb $0xd1, %al # 0xd1 -> port 0x64
outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port
seta20.2:
inb $0x64, %al # Wait for not busy(8042 input buffer empty).
testb $0x2, %al
jnz seta20.2
movb $0xdf, %al # 0xdf -> port 0x60
outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1
//CR0中包含了6个预定义标志,0位是保护允许位PE(Protedted Enable),用于启动保护模式,如果PE位置1,则保护模式启动,如果PE=0,则在实模式下运行。
//初始化GDT表:一个简单的GDT表和其描述符已经静态储存在引导区中,载入即可
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax //或操作赋1,将PE置为1
movl %eax, %cr0
//跳转进保护模式代码
ljmp $PROT_MODE_CSEG, $protcseg
.code32 # Assemble for 32-bit mode
protcseg:
# Set up the protected-mode data segment registers
movw $PROT_MODE_DSEG, %ax # 内核的数据段选择子全部初始化给有关数据的寄存器
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment
# 这一步是初始化C语言运行所需要的栈 ebp是栈底指针,esp是栈顶指针,程序到这里就进入bootmain执行
movl $0x0, %ebp
movl $start, %esp
call bootmain
//如果程序返回,则进入spin,并且在spin
spin:
jmp spin
# Bootstrap GDT
.p2align 2 # force 4 byte alignment
gdt://gdt表分段
SEG_NULLASM # null seg
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel
SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel
gdtdesc://gdt表的地址和长度
.word 0x17 # sizeof(gdt) - 1
.long gdt # address gdt
总结一下,从实模式到保护模式干的事情:
1.将cr0 PE位置1 以保护模式的寻址方式寻址
2.加载固化在引导区里的GDT表
3/初始化C语言堆栈