手写简易操作系统(三)--加载Loader

前情提要

上一节我们讲了如何启动计算机,这一节我们讲如何加载内核,内核是存在于硬盘上的一段程序,要加载这段程序,那么必然需要从硬盘上读取数据,这里我们就需要使用 ATA PIO 模式

根据ATA规范,所有符合ATA的驱动器必须始终支持PIO模式作为默认的数据传输机制。

现在较为流行的SATA硬盘也是一种符合ATA标准的硬盘,所以当然也需要支持 ATA PIO,而 ATA PIO 较为简单,所以我们就将其当做默认的读取硬盘的模式

在实际应用中,为了获得更好的性能和效率,通常会选择更高级的硬盘访问模式,如 DMA 或 Ultra DMA,以及操作系统提供的直接访问硬盘的接口(如 Windows 的AHCI模式)。这些模式能够更有效地利用系统资源,提供更快速的数据传输速度。甚至是NVME,直接走PCIE通道与CPU直连。但是这些比较复杂,不在本文的考虑范围内。

一、硬盘的主要端口

image-20240309213607741

其中Primary为主通道,Secondary为从通道

其中主通道读时

  • 0x1F0 是数据端口
  • 0x1F1 是错误端口,可以返回错误信息,每一位都是一个错误信息,包括(0、AMNF未找到地址标记。1、TKZNF未找到零磁道。2、ABRT中止命令。3、MCR变更请求。4、IDNF未找到ID。5、MC 发生了变化。6、UNC不可纠正的数据错误。7、BBK检测到坏块。)
  • 0x1F2 是扇区数量端口
  • 0x1F3 是LBA低地址
  • 0x1F4 是LBA中地址
  • 0x1F5 是LBA高地址
  • 0x1F6 0-3位,在CHS寻址中表示柱头位,在LBA寻址中,表示LBA地址的24-27位。4位DRV,表示选择主盘或者从盘。5位、永远为1。6位、如果为0则为CHS寻址,如果为1则为LBA寻址。7位、永远为1。
  • 0x1F7 是状态寄存器端口 ,0位ERR,如果为1则表示出错了。3位Data ,如果为1表示硬盘已经把数据准备好了。6位DRDY,表示硬盘检测正常,可以执行命令。7位BSY,如果为1表示硬盘正繁忙,此寄存器中的其他位都无效。

主通道写时有一些yu寄存器有了不同的用途

  • 0x1F1 是参数端口,用于传递写硬盘时的参数
  • 0x1F7 是指令端口,我们主要用到了这么几个指令。0xEC,硬盘识别。0x20,读扇区。0x30,写扇区。

二、加载Loader

哈哈哈哈,上面说的是加载内核,现在又成了加载loader,没办法,加载内核之前就得加载Loader,Loader的作用有

  1. 加载内核:loader 负责将操作系统内核从存储设备(如硬盘、闪存)中读取到内存中,以便后续执行。
  2. 确认内核完整性:loader 在加载内核之前通常会对内核进行校验,以确保内核文件的完整性和正确性,避免因为损坏或错误的内核文件导致系统启动失败。
  3. 设置环境:loader 在加载内核前会设置好适当的执行环境,包括初始化硬件设备、建立内存映射关系等,为内核的正常执行做好准备工作。
  4. 启动内核:加载完内核后,loader 会将控制权转交给内核的起始地址,启动内核的执行,让操作系统开始运行。

由于MBR是占据了硬盘的第0扇区(以逻辑LBA方式,扇区从0开始编号,若是以物理CHS方式,扇区则从1开始编号),所以我们的loader就放在第1扇区,可以看第二章的内存布局,现在有两块内存可用,0x500~0x7BFF0x7E00~9FBFF,那我们就放在 0x600 的地方吧。下面我们接着改MBR

2.1、修改Mbr使其可以加载Loader

这里我们添加一点宏定义

; os/src/boot/boot.inc
LOADER_BASE_ADDR equ 0x600
LOADER_START_SECTOR equ 0x1

然后改写mbr

; os/src/boot/mbr.s
; 设置开始的地址,并且初始化寄存器
%include "boot.inc" 
SECTION MBR vstart=0x7c00         
    mov ax,cs      
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov fs,ax
    mov sp,0x7c00
    mov ax,0xb800
    mov gs,ax

; 利用0x06号功能实现清理屏幕
; AL = 0x06 功能号
; AL 上卷的行数(如果为0,表示全部)
; BH 上卷行属性
; (CL,CH) = 窗口左上角的(X,Y)位置,这里是 (0,0)
; (DL,DH) = 窗口右下角的(X,Y)位置,这里是 (80,25)
    mov    ah, 0x06
    mov    al, 0x00
    mov    bh, 0x7
    mov    bl, 0x00
    mov    cx, 0           
    mov    dx, 0x184f
    int    0x10             ; int 0x10

    mov byte [gs:0x00],'M'  ; 字符为M的ascii值
    mov byte [gs:0x01],0x0F	; 11100001b 即背景色为黑,字体为白,不闪烁 
    mov byte [gs:0x02],'B'  ;
    mov byte [gs:0x03],0x0F	; 
    mov byte [gs:0x04],'R'  ;
    mov byte [gs:0x05],0x0F	;

    mov eax,LOADER_START_SECTOR	 ; Loader起始扇区 
    mov bx, LOADER_BASE_ADDR     ; Loader起始内存地址
    mov cx, 1			         ; 待写入扇区数
    call rd_disk_m_16		     ; 执行读取硬盘程序
  
    jmp LOADER_BASE_ADDR         ; 跳转到Loader执行
       

rd_disk_m_16:	   
				       ; eax=LBA扇区号
				       ; ebx=Loader内存
				       ; ecx=扇区数量
    mov esi,eax	       ; 备份eax
    mov di,cx		   ; 备份cx

    mov dx,0x1f2       ; 设置要写入端口,即读取端口数
    mov al,cl          ; 设置要读取扇区数
    out dx,al          ; 设置
    mov eax,esi	       ; 恢复eax

    mov dx,0x1f3       ; 设置要写入端口,即LBA低地址              
    out dx,al          

    mov cl,8           ; ax右移八位   
    shr eax,cl
    mov dx,0x1f4       ; 设置要写入端口,即LBA中地址 
    out dx,al

    shr eax,cl         ; ax右移八位   
    mov dx,0x1f5       ; 设置要写入端口,即LBA高地址 
    out dx,al

    shr eax,cl         ; ax右移八位
    and al,0x0f	       ; 保留低4位,设置高4位为 0000
    or al,0xe0	       ; 保留低4位,设置高4位为 1110
    mov dx,0x1f6
    out dx,al

    mov dx,0x1f7       ;
    mov al,0x20        ; 读扇区指令               
    out dx,al


.not_ready:            ; 未准备好
    nop                ; 不执行任何指令,占用一个机器周期
    in al,dx           ; 查看读取状态
    and al,0x88        ; 与 10001000 做与运算
    cmp al,0x08        ; 比较第三位和第七位
    jnz .not_ready
    mov ax, di         ; 要读的扇区数
    mov dx, 256        ; 乘以256,即要读多少次
    mul dx
    mov cx, ax	       ; 将要读的次数传给cx
    mov dx, 0x1f0      ; 要读的端口号

.go_on_read:
    in ax,dx           ; 向ax中读,一次读两个字节
    mov [bx],ax        ; 将ax中数据给bx地址的内存
    add bx,2		   ; bx中内存地址加2
    loop .go_on_read   ; 循环cx次
    ret
         
; 将510个字节中剩余的空间填充为0
; $ 是当前地址
; $$ 是本节开头地址,也就是0x7c00
times 510-($-$$) db 0
db 0x55,0xaa

2.2、写一个小Loader

; os/src/boot/loader.s
%include "boot.inc" 
section loader vstart=LOADER_BASE_ADDR 
.begin_loader:
    mov byte [gs:0x00],'L'  ; 字符为M的ascii值
    mov byte [gs:0x01],0x0F	; 11100001b 即背景色为黑,字体为白,不闪烁 
    mov byte [gs:0x02],'O'  ;
    mov byte [gs:0x03],0x0F	; 
    mov byte [gs:0x04],'A'  ;
    mov byte [gs:0x05],0x0F	;
    mov byte [gs:0x06],'D'  ;
    mov byte [gs:0x07],0x0F	;
    mov byte [gs:0x08],'E'  ;
    mov byte [gs:0x09],0x0F	;
    mov byte [gs:0x0A],'R'  ;
    mov byte [gs:0x0B],0x0F	;

; 程序在此处卡住
jmp $

这里loader的作用还是输出一些内容作为指示

2.3、执行

执行前需要把脚本更新一下

# os/run.sh
# 编译mbr
nasm -I src/boot/ -o bin/mbr.bin src/boot/mbr.s 
nasm -I src/boot/ -o bin/loader.bin src/boot/loader.s 

# 复制mbr二进制程序到硬盘
dd if=bin/mbr.bin of=/home/lyj/bochs/bin/hd60M.img bs=512 count=1 seek=0 conv=notrunc
dd if=bin/loader.bin of=/home/lyj/bochs/bin/hd60M.img bs=512 count=2 seek=1 conv=notrunc

# 启动仿真
/home/lyj/bochs/bin/bochs -f /home/lyj/bochs/bin/bochsrc.disk 

执行!

image-20240309225254582

结束语

第三章也结束了,这一章我们讲了如何加载一个Loader,以及如何读写硬盘,下一章,我们就要开始讲一些有关于保护模式的东西了,先将这个Loader完善一下。

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LyaJpunov

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值