SmlOS一 操作系统的引导

  • 内容
  • 评论
  • 相关

MBR介绍

简单来说就是设备的主引导扇区的512字节。

主引导扇区通常是设备的第一个扇区,以512字节为一个单位。

计算机启动会检查磁盘第一个扇区的最后两个字节,如果是0x55 AA,那么便认为存在引导程序,并开始加载。

在打开电源后最开始的引导操作将由机器完成,机器会读取MBR扇区载入到0x7c00的地址位置,然后跳转到这个区域运行。

我们一般在这里放入自己的bootloader。


bootloader任务

bootloader要干嘛呢,因为MBR只有512字节,实在是太小了,根本无法承载操作系统的很多功能,我们在这里做一些简单初始化操作,然后把操作系统内核的相关代码拷贝到自己指定的内核空间然后跳转过去。

而我们的bootloader主要的把磁盘的18个扇区的内容装载到内存0x8200处(这个0x8200地址是我们自定义的内核地址),从磁盘加载完内核代码后,bootloader就完成了它的任务。


引导代码

代码在boot.nas中:
最终会由boot.nas 生成的boot.bin将用来填充img映像首部。

这个引导区负责从磁盘加载,然后跳转到系统内核。

首先 使用汇编,指明程序加载的内存地址:

ORG 0x7c00 ;伪指令 启动程序装载地址

为什么加载地址0x7c00呢

在x86平台,0x7c00是计算机加载引导区到内存的起始地址,是由早期的IBM提出,而后来的计算机为了兼容一直使用。

在这个地址之前被中断向量和BIOS相关内容占用。

JMP     entry       ;跳转到程序入口
DB      0x90        ;NOP 空指令,
DB      "SML-OS  "  ;启动区的名称,8字符
DW      512         ;每扇区字节数
DB      1           ;每簇扇区数
DW      1           ;代表FAT文件分配表之前的引导扇区

DB      2           ;FAT表的个数
DW      224         ;根目录 文件最大数
DW      2880        ;逻辑 扇区 总数
DB      0xf0        ;磁盘种类
DW      9           ;每个FAT占用 多少扇区
DW      18          ;每磁道 扇区数
DW      2           ;磁头数
DD      0           ;隐藏扇区数

DD      2880        ;如果逻辑扇区总数为0,则在这里记录扇区总数
DB      0,0,0x29    ;中断13的驱动器号,未使用,扩展引导标志
DD      0Xffffffff  ;卷序列号
DB      "SML-OS     "   ;卷标,必须11个字符
DB      "FAT12   "  ; 文件系统类型,必须是8个字符,不足填充空格
RESB    18

;---------------------------------------------

可以看到这块并没有执行,但是写入了一些硬盘的相关信息。

具体信息含义可以参考注释内容。

介绍下相关16位寄存器

1.AX:累加寄存器
2.CX:计数寄存器
3.DX:数据寄存器
4.BX:基址寄存器
5.SP:栈指针寄存器
6.BP:基址指针寄存器
7.SI:源变址寄存器
8.DI:目的地址寄存器

1.AL:累加寄存器低位
  AH:累加寄存器高位
2.CL:计数寄存器低位
  CH:计数存储器高位
3.DL:数据存储器低位
  DH:数据存储器高位
4.BL:基址寄存器低位
  BH:基址寄存器高位

因为操作系统刚启动的时候还是实模式,只能操作16位寄存器。


初始化相关寄存器。

;程序主体入口
entry:
    MOV     AX,0        ;初始化寄存器
    MOV     SS,AX
    MOV     SP,0x7C00
    MOV     DS,AX
    MOV     ES,AX

可以看到我们把栈寄存器sp指向了0x7c00。

不然是不能进行堆栈操作和函数调用的。

;开始加载磁盘内容
    MOV     AX,0X0820
    MOV     ES,AX
    MOV     CH,0        ;柱面0
    MOV     DH,0        ;磁头0
    MOV     CL,2        ;扇区2

readLoop:
    MOV     SI,0        ;代表失败重试次数

初始化完毕后,开始加载磁盘内容:

不过在刚开始启动的16模式下,单个16位寄存器表示的范围是0~0xFFFF,差不多64k左右。

用来表示内存地址远远不够,便引入段寄存器机制。

例如:

[ES:BX]    ;代表ES*16+BX 的内存地址

不过最大地址也只能支持大约1M左右,这也是早期计算机最大可支持的内存容量。

指定ES的目的是将软盘数据加载到内存指定的位置,用于加载系统文件。

;读盘程序段
retry:
    MOV     AH,0X02     ;读盘指令
    MOV     AL,1        ;读取扇区个数,1个
    MOV     BX,0        
    MOV     DL,0X00     ;驱动器A
    INT     0X13        ;调用BIOS
    JNC     next        ;本次加载成功

    ADD     SI,1        ;否则失败了,SI+1
    CMP     SI,5
    JAE     error       ;失败次数大于5,跳转到error

    MOV     AH,0x00
    MOV     DL,0x00     ; A驱动器
    INT     0x13        ; 重置驱动器
    JMP     retry
next:

将指令参数放到AH寄存器中,然后调用0x13号中断加载磁盘数据,调用中断后的硬盘读取程序是由BIOS提供的。

这里使用了失败重读方式,用SI寄存器用来表示失败重试的次数。

如果磁盘读取数据发生错误,且错误数步超过5,那么将继续加载,否则将报错,并调用BIOS中断,显示错误信息。

error:
    MOV     SI,errorMsg

putloop:                ;显示文字
    MOV     AL,[SI]
    ADD     Si,1
    CMP     AL,0
    JE      fin
    MOV     AH,0x0e
    MOV     BX,15
    INT     0x10

    JMP     putloop

;---------------------------------------------
errorMsg:
    DB      0x0a, 0x0a      ; 换行
    DB      "Load error"
    DB      0x0a            ; 换行
    DB      0

可以看到显示文字的BIOS中断号是0x10,文字内容在errorMsg标处,用于显示错误信息,显示完毕后跳转到fin标签处,进入死循环。

我们需要读取完扇区1-18,磁头0-1,柱面数0-10 (总大小: 30 * 2 * 18 *512 = 552960 byte)

汇编表示为:

ADD CL,1 ;扇区加一,直到18
CMP CL,18
JBE readLoop
MOV CL,1
ADD DH,1
CMP DH,2
JB readLoop ;磁头0,1
MOV DH,0
ADD CH,1
CMP CH,CYLS
JB readLoop ;读入指定柱面数

加载完毕后:

MOV     [0x0ff0],CH ;保存 柱面数到内存,以备以后调用
JMP     0xc200      ;跳转的OsHead 程序

然后跳转到0xc200处,这个是系统内核代码区。


为什么是0xc200

因为在后期的链接中,我们会将内核代码保存在img映像的文件偏移0x004200地址上,这是我们自己定义的地址。

我们的bootloader从第二扇区开始,也就是映像文件0x200位置开始读映像文件到内存地址0x8200的位置。

也就是说映像文件的0x200位置以后的印象文件会保存到内存0x8200的位置上。

那么内核在内存的位置就是:

0x8200 + 0x4200 - 0x200 = 0xc200

RESB 0x7dfe-$ ; 填充字符
 DB 0x55, 0xaa ;最后两个字节代表启动扇区
这段指令是先填充字符,然后最后两个字节写入0x55 AA.

我们需要保证最后两个字节是0x55 AA,这样才能符合MBR的标准,我们的bootloader就制作完成了。