手写简易操作系统(二)--启动计算机

前情提要

在上一部分的内容中,我们已经做好了模拟仿真的整个准备工作,这一节我们就先把计算机启动起来。

一、Bochs配置

1.1、配置启动盘

创建硬盘的工具是bximage,这个工具在我们的Bochs目录下,使用命令行创建硬盘

第一个选择 1,即创建新的硬盘或者软盘

第二个选择 hd,即创建硬盘

第三个选择 flat,即创建flat格式的硬盘,还有一些诸如sparse, growing, vpc or vmware4格式的硬盘。

第四个选择 512,即一个扇区有512字节

第五个选择 60,即硬盘的大小为60MB

第六个选择 hd60M.img,这个是硬盘的名字

1.2、配置启动文件

在bin目录下新建一个启动文件 bochsrc.disk

# 设置内存为32MB
megs: 32

# 设置对应机器的BIOS
romimage: file=/home/lyj/bochs/share/bochs/BIOS-bochs-latest 
# 设置对应机器的VGA BIOS
vgaromimage: file=/home/lyj/bochs/share/bochs/VGABIOS-lgpl-latest 

# 选择启动盘符,默认为从软盘启动,但是这里设置为硬盘,软盘太古老了
boot: disk 

# 设置日志文件的输出
log: bochs_out.log

# 关闭鼠标功能,打开键盘功能,并且设置键盘的映射
mouse: enabled=0 
keyboard: keymap=/home/lyj/bochs/share/bochs/keymaps/x11-pc-us.map 

# 硬盘设置
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 
ata0-master: type=disk, path="/home/lyj/bochs/bin/hd60M.img", mode=flat

其中的大部分设置都做了详细的注释,这里我们对硬盘部分做出一些更详细的解释

  • enabled=1:表示 ATA0 控制器被启用。这意味着 Bochs 将会模拟一个 ATA0 控制器,并允许虚拟机使用这个控制器来访问硬盘或光驱设备。

  • ioaddr1=0x1f0:表示 ATA0 控制器的 I/O 端口地址。在计算机体系结构中,I/O 端口用于与外部设备进行数据交换。这里指定了 ATA0 控制器的主要 I/O 端口地址为 0x1f0。

  • ioaddr2=0x3f0:表示 ATA0 控制器的备用(secondary)I/O 端口地址。有些设备可能需要额外的 I/O 端口进行通信,这里指定了 ATA0 控制器的备用 I/O 端口地址为 0x3f0。

  • irq=14:表示 ATA0 控制器的中断请求线(IRQ)。中断请求线用于处理硬件设备发出的中断信号,这里指定了 ATA0 控制器的中断请求线为 14。

ata0可以挂载两个硬盘,其中一个是主盘一个是从盘。这部分内容我们会在讲解硬盘时详细说。

1.3、启动Bochs

./bochs -f bochsrc.disk

在bochs的命令行中先输入 6,即开始仿真

在输入 c,即开始执行

然后我们就看到了下面的画面

系统提示没有可引导的启动设备,也就是我们设置的硬盘是一个空的硬盘,里面并没有程序可以执行,所以直接报错退出了。接下来我们让整个程序先能跑起来。

二、MBR主引导记录

好了,我们要开始写代码了,在写代码之前先普及一点小知识,x86架构的处理器,由于历史原因,一开始是先进入实模式,再进入保护模式的。实模式下,只有20根地址线,因此只能访问到2的20次方Byte的数据,也就是1MB。这1MB又被分为了几个部分,我们可以看一下

这一部分如果我们学过《汇编语言》或者《微机原理》之类的课程是比较好理解的

2.1、BIOS启动

开机的一瞬间,BIOS被启动,BIOS中的代码就存在于 0xF0000~0xFFFFF 这个地址了,但实际上这个地址不是内存,是的,你没听错,不是内存,是一块ROM。

为了统一管理计算机中的端口,内存,ROM等等设备,计算机将这些东西统一了,就像访问内存那样去访问这些资源。

所以在开机的一瞬间,CPU的cs:ip寄存器被强制初始化为0xF000:0xFFF0,这样就可以直接执行BIOS中的程序。BIOS开始检测内存,显卡等外设信息,当外设准备好后,开始在 0x000~0x3FF 处建立中断向量表,这些中断就是BIOS提供的基础功能,可以帮助我们操作系统做一些基础操作。

做完这些,BIOS的使命就要结束了,最后的一点任务,校验启动盘中位于0盘0道1扇区(也就是LBA地址为0的扇区)的内容,如果此扇区末尾的两个字节分别是魔数 0x550xaa,BIOS便认为此扇区中确实存在可执行的程序,并把这个扇区的内容加载到物理地址 0x7c00 的内存中,一个扇区的大小我们在上一节中设置硬盘扇区大小中设置过了,是512B。

最后BIOS通过跳转指令 jmp 0:0x7c00 跳转到 0x7c00 的地址开始执行用户的程序,这里用户的程序也就是我们的操作系统了。

问:为什么是魔数 0x550xaa

答:BIOS中设置好的,也是大家公认的,如果你自己写BIOS的话,也可以设置为别的,但是这就不通用了。

问:为什么是 0x7c00 的地址?

答:这也是一个写死了的地址,王八的屁股,规定。

2.2、编写MBR,并编译

这里我们还是用到了BIOS中自带的中断。代码如下所示

assembly 复制代码
; os/src/boot/mbr.s
; 设置开始的地址,并且初始化寄存器
SECTION MBR vstart=0x7c00         
    mov ax,cs      
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov fs,ax
    mov sp,0x7c00

; 利用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

; 获取光标位置 
; AL = 0x03 功能号
; bh 寄存器存储的是待获取光标的页号
    mov    ah, 3
    mov    bh, 0
    int    0x10

; 打印字符串
; BP 字符串地址,BP需要通过AX访问,不能直接给BP内存数据
; AL = 0x13 功能号
; AL = 0x01 显示字符串,光标跟随移动
; CX = 0x03 字符串长度
; BH = 0x00 要显示的页号
; BL = 0x02 字符属性,属性黑底绿字
    mov ax, message
    mov bp, ax
    mov ax, 0x1301
    mov cx, 0x03
    mov bx, 0x02
    int 0x10	

; 循环等待
    jmp $            
; 要显示的字符串
    message db "MBR"
; 将510个字节中剩余的空间填充为0
; $ 是当前地址
; $$ 是本节开头地址,也就是0x7c00
    times 510-($-$$) db 0
    db 0x55,0xaa

其实还是很简单的,就像是调用函数,然后我们往那些指定的寄存器中添加数据就可以了。

编译一下

shell 复制代码
nasm -o bin/mbr.bin src/boot/mbr.s

查看一下确实是512字节

然后我们将这个文件直接通过dd指令存放到我们之前生成的虚拟硬盘中的0盘0道1扇区

dd if=./bin/mbr.bin of=~/bochs/bin/hd60M.img bs=512 count=1 conv=notrunc

然后运行Bochs

~/bochs/bin/bochs -f ~/bochs/bin/bochsrc.disk

可以看到,我们成功的运行起来了

2.3、改进MBR,直接操作显卡

我们将输出字符串直接通过操作显存来完成。通过上表我们可以得知,显存是从0xB8000~0xBFFFF,范围是32KB,一屏可以显示2000个字符,显示器上的每个字符占2字节大小,故每屏字符实际占用4000字节。这样,我们的32KB的显存可以容纳8屏的数据。

屏幕上每个字符的低字节是字符的ASCII码,高字节是字符属性元信息。

其中各个位的搭配为

我们将程序修改为

assembly 复制代码
; os/src/boot/mbr.s
; 设置开始的地址,并且初始化寄存器
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	; 00001111b 即背景色为黑,字体为白,不闪烁 
    mov byte [gs:0x02],'B'  ;
    mov byte [gs:0x03],0x0F	; 
    mov byte [gs:0x04],'R'  ;
    mov byte [gs:0x05],0x0F	;

; 循环等待
    jmp $            
; 要显示的字符串
    message db "MBR"
; 将510个字节中剩余的空间填充为0
; $ 是当前地址
; $$ 是本节开头地址,也就是0x7c00
    times 510-($-$$) db 0
    db 0x55,0xaa

之前的编译,写入硬盘,启动程序,这里也可以简单点写个脚本

shell 复制代码
# os/run.sh
# 编译mbr
nasm -o bin/mbr.bin src/boot/mbr.s 

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

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

直接启动脚本

结束语

好了第二章已经结束了,这一章我们生成了需要的硬盘以及写了一个简单的MBR引导程序,但是我们的内核还在硬盘中(实际上还没有写),下一章我们将在MBR中实现一个内核加载器出来。

相关推荐
修修修也1 天前
【Linux】进程间通信
linux·运维·服务器·操作系统·进程通信
Pandaconda3 天前
【操作系统】每日 3 题(十八)
linux·服务器·开发语言·数据结构·笔记·后端·操作系统
vincent_woo3 天前
再学安卓 - 系统环境安装
操作系统
Raymond运维3 天前
第一章 Linux安装 -- 安装Debian 12操作系统(四)
linux·运维·服务器·操作系统·debian
小蜗的房子4 天前
一篇文章让你了解Linux中的用户和组权限
linux·运维·服务器·后端·学习·操作系统·基础
简鹿办公4 天前
Windows 怎么关机?这五种方法你需要了解一下
操作系统
星海幻影4 天前
linux基础-完结(详讲补充)
linux·服务器·网络·安全·操作系统
小林up5 天前
【MIT-OS6.S081笔记1】Chapter1阅读摘要:Operating system interfaces
笔记·操作系统
linhhanpy5 天前
自制操作系统(九、操作系统完整实现)
c语言·开发语言·汇编·c++·操作系统·自制操作系统
tt5555555555556 天前
操作系统学习笔记-5.1-IO设备
服务器·笔记·嵌入式硬件·学习·操作系统