标签:extra ack long 跳转 含义 代码段 force blog 重置
PC的软盘和硬盘分为多个512个字节大小的的区域,称为扇区。 扇区是磁盘的最小传输单位:每个读或写操作必须是一个或多个扇区,并且必须在扇区边界上开始。 如果磁盘是可引导的,则第一个扇区称为引导扇区,因为这是引导加载程序代码所在的位置。 当BIOS找到可引导的软盘或硬盘时,它将512字节的引导扇区加载到物理地址0x7c00
至0x7dff
的内存中,然后使用jmp
指令将CS:IP
设置为0000:7c00
,将控制权传递给引导程序装载机。
对于6.828,我们将使用常规的硬盘启动机制。 引导加载程序由一个汇编语言源文件boot/boot.S
和一个C源文件boot/main.c
组成。浏览源文件,了解其具体做了什么。 引导加载程序必须执行两个主要功能:
1MB
以上的所有内存。了解了引导加载程序的源代码之后,请查看文件 obj/boot/boot.asm
,这是引导加载程序的反汇编版本,该文件可以轻松地准确查看所有引导加载程序代码在物理内存中的位置,并且可以更轻松地跟踪在 GDB 中逐步引导加载程序时发生的情况,对于调试很有帮助。
b --- 在指定地址设置断点
,如 b *0x7c00
c --- 执行到下一个断点或直到按 Ctel C 为止
si N --- 执行到后面的第 N 条
x/i --- 检查内存中的指令(除即将执行的下一条指令外)
,如x/Ni addr
现实从 addr 开始的 N 条指令
cli # Disable interrupts
cli
是 boot.S 的第一条指令,关全局中断。
cld # String operations increment
cld
用于将 DF 位(Direction Flag) 置零,DF 用于串操作指令中决定内存地址的变化方向,DF 置零使得串操作朝地址增加方向。
# 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
通过 xorw
指令使得 ax 寄存器内容清零,在通过 movw
指令给 ds, es, ss 寄存器置零。因为经历了 BIOS 后,这三个寄存器存放的内容不确定,需要重置,为进入保护模式做准备。
# 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.
seta20.1:
# 从 0x64 端口读取一个字节,存于 ax 寄存器的低八位
inb $0x64,%al # Wait for not busy
# 测试 al 中的第二位(与操作),若结果为 0 则 ZF = 1
testb $0x2,%al
# ZF 为 0 就跳转循环 seta20.1
jnz seta20.1
# 0x64 端口空闲,将 0xdl 送往该端口
movb $0xd1,%al # 0xd1 -> port 0x64
outb %al,$0x64
seta20.2:
inb $0x64,%al # Wait for not busy
testb $0x2,%al
jnz seta20.2
# 将 0xdf 送往 0x64 端口
movb $0xdf,%al # 0xdf -> port 0x60
outb %al,$0x60
在 http://bochs.sourceforge.net/techspec/PORTS.LST 中找到了 0x64 端口及其各位的作用:
该端口属于键盘控制器 804x,名称是控制器读取状态寄存器。
testb 0x02, %al
测试 0x64
端口的 bit 1, 若其为 1(该指令会将 ZF 标志置 1),则说明输入缓冲满,不能马上往该端口写入数据,需要重新测试端口(jnz seta20.1
指令),直到端口空闲。
上述代码分别给0x64 和 0x60
端口写入两条指令0xd1,0xdf
,查到这两条指令的含义如下
0xd1
指令将下一条写到0x60
端口的指令写到键盘控制器 804x 的输出端口,即0xdf
被写到键盘控制器 804x 的输出端口。
0xdf
指令使能 A20 线,代表可以进入保护模式。
# Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to their physical addresses, so that the
# effective memory map does not change during the switch.
lgdt gdtdesc
# 修改寄存器 cr0 的 bit0,使其为 1
movl %cr0, %eax
orl $CR0_PE_ON, %eax # CR0_PE_ON 定义于 mmu.h 文件,内容为 0x00000001
movl %eax, %cr0
lgdt
指令用于加载全局描述符,用于将gdtdesc
这个标识符的值送入全局映射描述符表寄存器 GDTR 中。gdtdesc
是一个标识符,标识着一个内存地址。从这个内存地址开始之后的6个字节中存放着 GDT 表的长度和起始地址,GDT 表是处理器在保护模式下一个很重要的表。(我没弄清楚,参考 这儿。
加载 LGDT 结束后,将 CR0 寄存器的 bit1 置 1。
# Bootstrap GDT
.p2align 2 # force 4 byte alignment
# gdt 表,共三个表项
gdt:
SEG_NULL # null seg
SEG(STA_X|STA_R, 0x0, 0xffffffff) # code seg
SEG(STA_W, 0x0, 0xffffffff) # data seg
gdtdesc:
# 表的大小
.word 0x17 # sizeof(gdt) - 1
# 起始地址
.long gdt # address gdt
由于 xv6 其实并没有使用分段机制,也就是说数据段和代码段的位置没有区分,所以数据段和代码段的起始地址都是0x0
,大小都是0xffffffff
。
# Jump to next instruction, but in 32-bit code segment.
# Switches processor into 32-bit mode.
ljmp $PROT_MODE_CSEG, $protcseg
该指令跳转至 protcseg
,并将运行模式切换至 32 位地址模式
.code32 # Assemble for 32-bit mode
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
在加载完 GDTR 后必须重新加载上述所有寄存器的值,这是必须要求的:https://en.wikibooks.org/wiki/X86_Assembly/Global_Descriptor_Table 。
# Set up the stack pointer and call into C.
movl $start, %esp
call bootmain
设置当前 esp 寄存器的值,然后跳转至 main.c 中的 bootmain 函数。
MIT 6.828 JOS学习笔记5. Exercise 1.3
标签:extra ack long 跳转 含义 代码段 force blog 重置
原文地址:https://www.cnblogs.com/joe-w/p/12554360.html