ucore lab1练习三报告

练习3:分析bootloader进入保护模式的过程

1.开启A20的原因

    原来的8086和8088只有20位地址线,寻址空间为1024KB。而PC机的寻址结构是segment:offset,segment和offset均为16位,能表达的最大寻址空间为10ffefh,约为1088KB,超过了20位地址线的寻址空间。在8086中,由于没有第21位地址线,在寻址超过20位地址线的寻址空间而小于PC机最大寻址空间的地址时(100000h–10ffefh),会得到寻址地址减去100000h后地址的内容。80286和80386的出现使PC机的地址线从20位变成24位又变成32位,100000h以上的寻址空间可以访问。其中,100000h–10ffefh称为高端内存区,10ffefh以上称为扩展内存。为了保持与8086的兼容,PC机设计了A20 Gate开关,开关关闭时,第21位地址线不能用,在实模式下,80286和其后续系统所表现的行为和8086/8088所表现的完全一样;开关打开时,第21位地址线可以正常使用。在保护模式下,如果不打开A20 Gate,第21位地址线永远为0,那么系统只能访问奇数兆的内存,即只能访问0–1M、2-3M、4-5M…,这显然是不行的。因此要进入保护模式,应开启A20 Gate。

2.bootloader进入保护模式的过程

.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

前面两个段选择子的作用是提供了gdt中代码段和数据段的索引,后面的CR0_PE_ON变量是开启A20地址线的标志,为1是开启保护模式。

关闭中断

start:
.code16                                             # Assemble for 16-bit mode
    cli                                             # Disable interrupts
    cld                                             # String operations increment

接下来的步骤不能被任何事情打扰,因此使用cli指令关闭中断。因为目前还处于实模式,所以需要这里的.code16告诉编译器代码以16位模式编译。

清理寄存器

 # Set up the important data segment registers (DS, ES, SS).
    xorw %ax, %ax                                   # Segment number zero
    movw %ax, %ds                                   # -> Data Segment
    movw %ax, %es                                   # -> Extra Segment
    movw %ax, %ss                                   # -> Stack Segment

开启A20

    A20 Gate被定义在output port(64h)的bit 1上,这个端口的操作是通过向64h发送命令,然后在60h进行读写的方式完成,只要对其操作就可以控制A20的开关。但当我们要向8042的输入缓冲区里写数据时,里面可能还有其他的数据没有被处理,所以我们要禁止键盘操作,并等数据缓冲区里没有数据后,才能开启A20。

首先等待input buffer为空

seta20.1:
    inb $0x64, %al               # Wait for not busy(8042 input buffer empty).
    testb $0x2, %al
    jnz seta20.1

inb指令表示从I/O端口读取一个字节,testb指令表示执行and指令,但不会保存and执行的结果,而是根据and的结果设置flags寄存器,jnz指令表示ZF不为1时跳转。

inb $0x64, %al是指从Status Register(64h)读取一个字节,并放入al寄存器中。之后testb $0x2, %al判断al寄存器中第二位的值,即Status Register中的bit 1是不是1。如果是1,则代表输入缓冲中有数据,ZF被置位为1;如果是0,则代表输入缓冲中已经没有数据,ZF被置位为0。jnz seta20.1判断ZF是否为1,如果为1,则代表输入缓冲中有数据,还要继续等待其为空,因此跳转到该程序段开头继续执行;如果ZF为0,程序继续向下执行。

接下来发送写Output Port命令

    movb $0xd1, %al              # 0xd1 -> port 0x64
    outb %al, $0x64              # 0xd1 means: write data to 8042's P2 port

outb指令表示向I/O端口写入一个字节。

0xd1命令代表写output port,这段代码表示向64h端口发送0xd1命令,以便接下来向60h写入Output Port。

再次等待input buffer为空

seta20.2:
    inb $0x64, %al               # Wait for not busy(8042 input buffer empty).
    testb $0x2, %al
    jnz seta20.2

向写60h端口发送将A20 Gate置为1的指令(0xdf)

    movb $0xdf, %al     # 0xdf -> port 0x60
    outb %al, $0x60     # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1

接下来开始初始化GDT表。

将GDT的入口地址装入GDTR(全局描述符表寄存器),从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT

    lgdt gdtdesc

这段代码中,lgdt指令是将GDT入口地址和大小以gdtdesc的结构放入寄存器里里。

设置cr0寄存器

    movl %cr0, %eax
    orl $CR0_PE_ON, %eax
    movl %eax, %cr0

orl是按位或操作指令。

cr0主要用于存储两个标志位——保护模式和分页。打开保护模式标志位,相当于按下了保护模式的开关。cr0寄存器的第0位就是这个开关,这里通过将CR0_PE_ON或cr0寄存器,将第0位置1。

切换32位模式

    # Jump to next instruction, but in 32-bit code segment.
    # Switches processor into 32-bit mode.
    ljmp $PROT_MODE_CSEG, $protcseg

.code32                                             # Assemble for 32-bit mode
protcseg:

ljmp $PROT_MODE_CSEG, $protcseg是远跳转指令。PROT_MODE_CSEG为0000 0000 0000 1000,是GDT中的第1个段,即代码段。protcseg是偏移量。

.code32的作用和前面提到.code16对应,因为已经打开了cr0的保护模式标志,所以cpu按保护模式的方式寻址,这里应该告诉编译器切换到32位模式。protcseg为段内偏移。

重新初始化各个段寄存器,将代码段的开始地址赋值给各个寄存器

    # Set up the protected-mode data segment registers
    movw $PROT_MODE_DSEG, %ax                       # Our data segment selector
    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

建立堆栈

    # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
    movl $0x0, %ebp
    movl $start, %esp

至此,从实模式转换为保护模式完成。

call函数将控制权交给bootmain

    call bootmain

    # If bootmain returns (it shouldn't), loop.
spin:
    jmp spin

构建GDT

# Bootstrap GDT
.p2align 2                                          # force 4 byte alignment
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

第一个描述符必须是Null Segment,也就是全0。接下来是代码段和数据段,代码段是可读可执行的, 数据段是可写的。
三个参数分别是类型、基地址和长度。

构造GDTR的内容

gdtdesc:
    .word 0x17                                      # sizeof(gdt) - 1
    .long gdt                                       # address gdt

GDTR由gdt大小和基地址构成,高位是大小,低位由地址组成。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值