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

前情提要

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

一、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中实现一个内核加载器出来。

相关推荐
qq_437896435 小时前
动态内存分配算法对比:最先适应、最优适应、最坏适应与邻近适应
操作系统
别说我什么都不会5 小时前
鸿蒙轻内核M核源码分析系列十一 (2)信号量Semaphore
操作系统·harmonyos
别说我什么都不会12 小时前
鸿蒙轻内核M核源码分析系列十 软件定时器Swtmr
操作系统·harmonyos
别说我什么都不会1 天前
鸿蒙轻内核M核源码分析系列九 互斥锁Mutex
操作系统·harmonyos
别说我什么都不会2 天前
鸿蒙轻内核M核源码分析系列七 动态内存Dynamic Memory
操作系统·harmonyos
别说我什么都不会2 天前
鸿蒙轻内核M核源码分析系列六 任务及任务调度(3)任务调度模块
操作系统·harmonyos
徐徐同学2 天前
【操作系统】操作系统概述
操作系统·计算机系统
守望时空333 天前
Linux内核升级指南
linux·操作系统
塞尔维亚大汉3 天前
OpenHarmony(鸿蒙南向开发)——小型系统内核(LiteOS-A)【用户态内存调测】
操作系统·harmonyos
别说我什么都不会3 天前
鸿蒙轻内核M核源码分析系列五 时间管理
操作系统·harmonyos