MIT 6.828 学习笔记1 阅读boot.S

来源:互联网 发布:达达数据 编辑:程序博客网 时间:2024/05/19 20:42
#include <inc/mmu.h>// mmu.h 内含有需要使用的宏定义与函数# Start the CPU: switch to 32-bit protected mode, jump into C.// 这些代码的作用为转换到 32 位保护模式,然后跳转到 main.c# The BIOS loads this code from the first sector of the hard disk into// BIOS 读取硬盘第一扇区的内容到 0x7c00# memory at physical address 0x7c00 and starts executing in real mode// 设置 CS、IP 寄存器,执行实模式# with %cs=0 %ip=7c00..set PROT_MODE_CSEG, 0x8         # kernel code segment selector// 内核代码段 selector,用于寻找 GDT 条目.set PROT_MODE_DSEG, 0x10        # kernel data segment selector// 内核数据段 selector,用于寻找 GDT 条目.set CR0_PE_ON,      0x1         # protected mode enable flag// 用于设置 CR0 的 PE 位,目的为开启保护模式.globl start// 设置全局符号 startstart:  .code16                     # Assemble for 16-bit mode// 16位指令  cli                         # Disable interrupts// 屏蔽中断,Bootloader 执行过程中不响应中断  cld                         # String operations increment// 从低地址到高地址  # 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// 栈段寄存器


# 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:  inb     $0x64,%al               # Wait for not busy  testb   $0x2,%al  jnz     seta20.1  movb    $0xd1,%al               # 0xd1 -> port 0x64  outb    %al,$0x64seta20.2:  inb     $0x64,%al               # Wait for not busy  testb   $0x2,%al  jnz     seta20.2  movb    $0xdf,%al               # 0xdf -> port 0x60  outb    %al,$0x60
以上代码用于打开 A20 gate,先来简单介绍下 A20 gate
在 8086 中有 20 根地址总线,通过 CS:IP 对的方式寻址,最大访问地址为 1MB
然而,FFFFH:FFFFH = 10FFEFH,也就是说从 100000H 到 10FFEFH 无法访问
当访问这段地址时,会产生 wrap-around,也就是实际访问地址会对 1MB 求模
到了 80286 中有 24 根地址总线,最大访问地址为 16MB
这个时候,不会产生 wrap-around,为了向下兼容 8086,需要使用第 21 根地址总线
所以 IBM 的工程师使用 PS/2 Controller 输出端口中多余的端口来管理 A20 gate,也就是第 21 根地址总线(从 0 开始)
在解释上面的代码之前,先看下面一些关于 PS/2 Controller 的资料(表格只列出相关内容,Bit 从 0 开始,即 Bit1 为第二位):

PS/2 Controller IO PortsIO PortAccess TypePurpose0x60Read/WriteData Port0x64ReadStatus Register0x64WriteCommand Register
Status RegisterBitMeaning1

Input buffer status (0 = empty, 1 = full)
(must be clear before attempting to write data to IO port 0x60 or IO port 0x64)

PS/2 Controller CommandsCommand Byte
Meaning
Response Byte0xD1
Write next byte to Controller Output Port
Note: Check if output buffer is empty first
NoneIf there is a "next byte" then the next byte needs to be written to IO Port 0x60 after making sure that the controller is ready for it (by making sure bit 1 of the Status Register is clear).


PS/2 Controller Output PortBitMeaning1A20 gate (output)
第 6 行,inb 指令的意思是从 I/O 读取 1byte 的数据,存入 al 寄存器中,而读取 0x64 端口可以从上表中看出意思是读取状态寄存器的值
第 7 行,testb 指令的意思是对两个操作数执行逻辑 AND 并设置 flags 寄存器,在这里也就是读取 al 寄存器中的数据的第二位是否为 0
第 8 行,jnz 指令的意思是如果不是 0 则跳转到 seta20.1,从上表中可以看出,如果第二位为 0 代表输入缓存为空,即可以向端口 0x60 或者 0x64 写数据
第 9 、10 行,outb 指令德意思是向 I/O 写入 1byte 的数据,也就是向命令寄存器写入 0xD1,即命令 PS/2 Controller 将下一个写入 0x60 的字节写出到 Output Port
第 16 行,将 0xdf 写入 0x60,即将 Output Port 的第二位设置为 1
至此,就打开了 A20 gate

  # 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  movl    %cr0, %eax  orl     $CR0_PE_ON, %eax// 参考文件开头宏定义 CR0_PE_ON = 0x1  movl    %eax, %cr0# Bootstrap GDT.p2align 2                                # force 4 byte alignmentgdt:  SEG_NULL# null seg  SEG(STA_X|STA_R, 0x0, 0xffffffff)# code seg  SEG(STA_W, 0x0, 0xffffffff)        # data seggdtdesc:  .word   0x17                            # sizeof(gdt) - 1  .long   gdt                             # address gdt
/* * Macros to build GDT entries in assembly.// mmu.h 中的片段 */#define SEG_NULL\.word 0, 0;\.byte 0, 0, 0, 0#define SEG(type,base,lim)\// 权限状态 基地址 大小.word (((lim) >> 12) & 0xffff), ((base) & 0xffff);\// 2 个 word 数据,即前 32 位,详细信息看下文图片.byte (((base) >> 16) & 0xff), (0x90 | (type)),\// 4 个 byte 数据,即后 32 位(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)// Application segment type bits#define STA_X0x8    // Executable segment#define STA_E0x4    // Expand down (non-executable segments)#define STA_C0x4    // Conforming code segment (executable only)#define STA_W0x2    // Writeable (non-executable segments)#define STA_R0x2    // Readable (executable segments)#define STA_A0x1    // Accessed
有关 GDT 的作用请自行上网搜索,一些相关资料如下:
GDTR 是存放 GDT 地址与大小的寄存器,下图为结构,16 位 Size 代表大小,32 位 Offset 代表线性地址

The offset is the linear address of the table itself, which means that paging applies. The size is the size of the table subtracted by 1. 

GDT Entry 是 GDT 中存放的条目,大小为 8 个字节,下图为结构,Base 代表段基地址,Limit 代表段大小,其他为一些状态权限相关的信息


第 5 行,lgdt 的作用为设置 GDTR,可以看到 gdtdesc 的信息从第 17 行开始
第 18 行,.word 的作用可以理解为设置一个大小为 word 的数据,由于 0x17 = 23,所以 GDTR 中的 Size 为 24,即存在 3 个 GDT Entry
第 19 行,.long 的作用与 .word 同理,可以看到 gdt 的信息从第 12 行开始
第 13、14、15 行,分别设置了NULL段,代码段,数据段,这里调用了 SEG 函数,相关定义可以从 mmu.h 中找到
上面几行执行的结果大概可以视作如下:
GDT[0] = { base = 0x0, limit = 0x0, type = 0x0 }
GDT[1] = { base = 0x0, limit = 0xffffffff, type = 0xA }
GDT[2] = { base = 0x0, limit = 0xffffffff, type = 0x2 } 
回到第 6、7、8 行,通过设置 cr0 寄存器的 PE 位,将 CPU 从 real mode 转换到 protected mode,有关这两个模式的信息请自行上网搜索


.set PROT_MODE_CSEG, 0x8         # kernel code segment selector// 文件开头的宏定义,代码段的 selector.set PROT_MODE_DSEG, 0x10        # kernel data segment selector// 文件开头的宏定义,数据段的 selector  # 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 modeprotcseg:  # 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
利用 Segment Selector 寻找 GDT Entry,然后根据 GDT Entry 的 Base 寻址,Segment Selector 的结构如下:


索引,即 GDT 表上的索引,用来获取 GDT 中的条目
TI,若为 1 则代表该条目为 LDT,这里没有用到 LDT,相关信息有兴趣可以到网上搜索
RPL,权限等级
接下来看代码:
第 6 行,ljmp 的作用为跳转到指定地址,可以看到 PROT_MODE_CSEG = 0x8 = 0000000000001000,即索引 = 1,TI = 0,RPL = 0
由于在保护模式下,根据上文得出的条目得知 GDT[1] = { base = 0x0, limit = 0xffffffff, type = 0xA }
所以线性地址为 0H +  protcseg 的地址 = protcseg 的地址,即跳转到 protcseg 的地址(第 9 行)
第 8 行,代表下面的指令为 32 位指令
第 11 行,与第 6 行同理,得到 GDT[2] = { base = 0x0, limit = 0xffffffff, type = 0x2 } ,然后接下来的几行分别设置各个段寄存器


  # Set up the stack pointer and call into C.  movl    $start, %esp// 设置栈指针  call bootmain// 调用 bootmain 函数,位于 main.c  # If bootmain returns (it shouldn't), loop.spin:  jmp spin
接下来,便执行 main.c 中的 bootmain 函数



参考资料:
http://wiki.osdev.org/%228042%22_PS/2_Controller
http://www.win.tue.nl/~aeb/linux/kbd/A20.html
http://wiki.osdev.org/GDT
http://www.csie.ntu.edu.tw/~wcchen/asm98/asm/proj/b85506061/chap2/segment.html

0 0