动手编写操作系统(3):系统引导过程——BIOS与MBR(下)
2022/1/28 23:34:28
本文主要是介绍动手编写操作系统(3):系统引导过程——BIOS与MBR(下),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
上一节,我们已经初步认识了系统开机引导过程,并编写了一个简单的MBR引导程序(仅样例,不带分区表)。下面,我们将在实模式下继续认识计算机的IO接口、硬盘操作等知识,并真正实现一个内核加载器。
(本系列所有文章均参考郑刚所著《操作系统真象还原》,真诚感谢前辈的指导。)
I/O 接口
CPU通过I/O接口与外部设备进行通信。I/O接口作为一个“层”,CPU与硬件的交互提供兼容服务,是连接CPU与外部设备的逻辑控制部件。I/O接口具有硬件和软件部分,硬件部分负责底层的硬件连接以及为软件提供缓冲等硬件基础,高级的硬件接口允许同时与多个设备进行连接;软件部分通过程序实现数据的交换。通过IO接口控制编程,我们可以控制接口的功能,这通常由端口读写指令in/out
实现。
in ax, dx ; ax: read data, dx: port out dx, al ; dx: port, al: write data
IO 接口的功能
- 设置数据缓冲,解决CPU与外设速率不匹配问题
- 设置信号电平转换电路,将CPU的TTL信号与外设的电平(模拟/数字信号)进行转换
- 数据格式转换:提供A/D、D/A转换、串并行转换与接口带宽转换等
- 设置时序控制电路,以同步CPU与外部设备
- 提供地址译码,使CPU能够选中某个端口,时期访问数据总线
总线
- 为提供多个设备之间的连接,我们使用总线连接这些设备,成为这些设备的公共通道
- 总线上, 同时只有一对设备进行通信,其他设备处于高阻态
- 如果总线空闲,一个设备需要发送信息,则驱动总线发送,其他设备收到符合的地址,则开始通信
- 计算机内部存在一条高速总线,连接CPU与南桥(输出输出控制中心),称为内部总线(系统总线)
- 南桥负责连接外部设备,内部集成多个IO接口
- 为了连接可拓展设备,IO接口设置了总线通信标准,符合一类总线标准的设备可连接在一套总线上
- 常见的总线标准
PCIE
USB
SATA
- 常见的总线标准
- 串行总线与并行总线
- 并行总线通过链路一次收发多b数据,但需要同步各线时序
- 并行总线一次收发1b数据,频率较高
- 串行总线逐渐取代并行总线
显示适配器
为了给用户提供视觉上的交互,计算机需要依靠显示器来输出图像、文字信息。而显示器显示的性能、功能要求较高,CPU无法直接驱动显示器,驱动显示器的工作就交给了专门的显示适配器,也就是我们常说的显卡。
为了接收、储存CPU想要输出的信息,显卡提供了显存这一接口。我们只要操作显存,就可以让显卡驱动屏幕,输出对应信息。在实模式下,显卡相关内存分布如下:
起始 | 结束 | 大小 | 用途 |
---|---|---|---|
0xC0000 | 0xC7FFF | 32KB | 显示适配器BIOS |
0xB8000 | 0xBFFFF | 32KB | 用于显示适配器文本模式 |
0xB0000 | 0xB7FFF | 32KB | 用于黑白显示适配器 |
0xA0000 | 0xAFFFF | 64KB | 用于彩色显示适配器 |
- 除了BIOS空间,其他的位置均指向显存。只要修改寄存器的值,就会影响屏幕的输出。
下面,我们通过修改之前的打印欢迎信息的程序,来试验一下效果。程序使用一个循环,将数据字符串复制到显存对应位置,并设置颜色属性
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 ; auxiliary section regisster mov gs, ax ;mov ecx, 0 ; initialize ecx ; Clear Screen use 0x06 sub-func in interrupt 0x10(Video Service) ; function description: scroll up the window ; INPUTS: ; AH function_index = 0x06 ; AL = rows to scroll up, 0: clear ; BH = scroll up color property ; (CL, CH) = Top Left Corner of window ; (DL, DH) = Lower Right Corner of window ; No return mov ax, 0x0600 ; also can mov ah, 6; mov ax, 0 mov bx, 0x0700 ; BH: Light Gray on Black mov cx, 0 ; Top Left (0, 0) mov dx, 0x184f ; Lower Right: (80, 25) ((79, 14)) ; in VGA Text Module, only 80c in one line, max 25 lines int 0x10 ; print string with gpu ; mov si, message mov cx, 0 loop: mov si, message add si, cx mov al, byte [si] mov bx, cx add bx, bx mov byte [gs:bx], al add bx, 1 mov byte [gs:bx], 0x0a ; color: light green add cx, 1 cmp cl, byte [len] jb loop ; end jmp $ ; data message db "Hello, world. This is a MBR." len db $ - message ; times 510-($-$$) db 0 db 510 - ($-$$) dup (0) db 0x55, 0xaa
程序同样打印出了正确内容,并且设置了我们想要设置的颜色。不过,如果我们手动操作显存,就无法使用控制字符(回车、换行等),如果使用,则需要另外处理。
硬盘
- 硬盘中的物理扇区使用CHS (Cylinder Head Sector)定位方法进行定位的
- Cylinder: 柱面,确定半径的所有盘片的同一圆柱面,读取同一柱面数据不需要移动磁头
- Head: 磁头,每个盘面需要有一个磁头用于读写数据
- Sector: 扇区,分割开的扇形区域
- 为了方便定位,将所有磁盘中扇区从0开始编号,分配一个逻辑地址LBA (Logical Block Address),称为逻辑块地址
- 早期的LBA28采用28位寻址,在扇区大小为512B时,最大支持空间128GB;后期的LBA48支持128PB
端口寄存器
硬盘控制器的主要端口寄存器如下表所示:
IO端口号(Primary) | 读操作时功能 | 写操作时功能 |
---|---|---|
0x1f0 | Data | Data |
0x1f1 | Error | Features |
0x1f2 | Sector Count | Sector Count |
0x1f3 | LBA low | LBA low |
0x1f4 | LBA Mid | LBA mid |
0x1f5 | LBA High | LBA high |
0x1f6 | Device | device |
0x1f7 | Status | Command |
0x3f6 | Alternate Status | Device Control |
- 以上为Primary(主盘)端口号,PATA从盘端口号 -0x80
- 其中,最后一个端口
0x3f6
为Control Block register
,其余均为Command Block register
- 部分寄存器具有多重功能,一个功能占几位
- 仅Data寄存器为16b位宽,其余均为8位
部分主要寄存器的功能为:
- Data:传输数据
- Error:在硬盘读错误时存放错误信息,此时Sector Count存放尚未读取的扇区数
- Feature:存放部分命令需要的额外参数
- Sector Count:存放需要读取/写入的扇区数。完成一个扇区的读写后,会将此值-1
- LBA: 存储LBA寻址地址,其余4位在Device寄存器中
- Device:杂项寄存器,低4b存放LBA的24-27位,第4位指定通道上的主/从盘,0为主;,第6位设置是否启用LBA
- Status
- 0:Error位,为1时表示出错
- 3:Data Request 位,为1时表示数据准备完成,可以供主机读取
- 6:DRDY,硬盘就绪,硬盘诊断中表示硬盘检测正常
- 7:BSY,硬盘正忙
硬盘操作命令
主要命令
- Identify: 0xEC, 硬盘识别
- Read Sector: 0x20, 读扇区
- Write Sector: 0x30, 写扇区
命令顺序
- 没有完整顺序,只有command寄存器必须最后置位,写入后即开始运行
- 约定使用顺序如下:
- 选择通道(Channel),像通道的Sector Count寄存器中写入待操作的扇区数
- 指定LBA地址低24位(3个LBA寄存器)
- 向Device寄存器中写入LBA高4位,将第6位LBA使能位置位,设置第4位操作的硬盘
- 向Command寄存器写入命令
- 读取Status,查看是否完成
- 如果是读取命令,则读出数据,否则完成
常用数据传送方式
- 无条件传送:数据源一定准备随时读取,如寄存器,内存等
- 查询传送:也称为
PIO (Programming I/O Model)
,程序IO,读取前需要检测状态,数据源设备在一定状态下才能读取,如硬盘等 - 中断传送:也称为中断驱动IO,弥补了查询传送需要不断查询的缺陷,数据源准备好数据,触发中断通知主机读取,效率较高
- 直接存储器读取(DMA):将数据源数据输出到内存,CPU直接向内存读取。效率更高(不需要中断),但需要硬件支持(DMA控制器)
- I/O处理机传送:通过单独的IO处理设备处理传输
使用硬盘
读取Loader
按照上面的过程,我们就可以操作硬盘啦!下面,我们就将使用这个新技能,将硬盘中的Loader读取到内存里,并跳转执行。下面直接贴代码:
%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 ; auxiliary section regisster mov gs, ax mov ecx, 0 ; initialize ecx ; Clear Screen use 0x06 sub-func in interrupt 0x10(Video Service) ; function description: scroll up the window ; INPUTS: ; AH function_index = 0x06 ; AL = rows to scroll up, 0: clear ; BH = scroll up color property ; (CL, CH) = Top Left Corner of window ; (DL, DH) = Lower Right Corner of window ; No return mov ax, 0x0600 ; also can mov ah, 6; mov ax, 0 mov bx, 0x0700 ; BH: Light Gray on Black mov cx, 0 ; Top Left (0, 0) mov dx, 0x184f ; Lower Right: (80, 25) ((79, 14)) ; in VGA Text Module, only 80c in one line, max 25 lines int 0x10 mov bx, 0 mov si, message mov cx, [len_message] call my_print mov eax, LOADER_START_SECTOR ; Start Sector of Loader mov bx, LOADER_BASE_ADDR ; Loader base address mov cx, 1 ; Sector count call rd_disk_m_16 ; jmp $ ; for debug jmp LOADER_BASE_ADDR my_print: ; print string with gpu ; param: bx: offset on the screen ; param si: string address ; param cx: length ; mov cx, dx loc_0x37: mov al, byte [si] mov byte [gs:bx], al add bx, 1 mov byte [gs:bx], 0x0a ; color add si, 1 add bx, 1 loop loc_0x37 retn ; my_print endp rd_disk_m_16: ; param eax = LBA Sector number ; bx = destination address ; cx = sector count mov esi, eax ; backup eax mov dx, 0x1f2 ; sector count mov al, cl out dx, al mov eax, esi mov dx, 0x1f3 ; set LBA address low 24b loc_72: out dx, al add dx, 1 shr eax, 8 cmp dx, 0x1f5 jbe loc_72 and al, 0x0f ; set LBA address high 4b or al, 0xe0 ; set device mode out dx, al add dx, 1 mov al, 0x20 ; read command out dx, al .not_ready: nop nop in al, dx and al, 0x88 ; 4: ready; 7: busy cmp al, 0x08 jnz .not_ready and cx, 0xf shl cx, 8 ; words to read, *512 / 2 mov dx, 0x1f0 ; data port .go_on_read: in ax, dx mov [bx], ax add bx, 2 loop .go_on_read retn ; data message db "Hello, world. This is a MBR." len_message db $ - message ; times 510-($-$$) db 0 db 510 - ($-$$) dup (0) db 0x55, 0xaa
下面是主要结构以及改动的说明:
- 增加
%include
预处理指令,将boot.inc
中设置的两个参数宏定义引用进文件中 - 对
my_print
进行改进,使之成为函数,采用寄存器传递参数 - 增加
rd_disk_m_16
读取硬盘函数,并调用此函数读取硬盘中loader,载入内存
我们再来详细看一下rd_disk_m_16
函数:
- 向
0x1f2
端口输出想要读取的扇区数,也就是配置参数- 直接通过配置好的寄存器参数输出即可
- 向三个LBA地址端口写入需要读取的扇区号
- 这里使用
shr
逻辑右移命令,每次将低八位输出,可以构造循环
- 这里使用
- 向Device寄存器写入LBA高四位以及其他配置信息
- 还需要置位LBA使能位,和另外两个恒为1的位
- 向
0x1f7
Command寄存器写入读命令0x20
- 检测硬盘状态,直到硬盘就绪
- 通过读取 Status 状态寄存器信息,可以得知硬盘状态是否就绪
- 我们只关心第4位Ready和第7位Busy,使用mask
0x88
过滤
- 循环从Data寄存器中读出数据
- 读取一个Sector共512B,由于Data寄存器长度为16b,故每次读出Word (2B)
- 循环数为扇区数左移8位
测试Loader
下面同样用一个简单的输出测试Loader是否被正确加载
%include "boot.inc" section loader vstart=LOADER_BASE_ADDR mov bx, 0x100 mov si ,message mov cx, [len] call my_print jmp $ my_print: ; vesion 3.0 ; print string with gpu ; param: bx: (bh, bl)=(row, col) offset on the screen ; param si: string address ; param cx: length ; mov cx, dx mov ax, 0xa0 mul bh add ax, bl mov bx, ax loc_0x37: mov al, byte [si] mov byte [gs:bx], al add bx, 1 mov byte [gs:bx], 0x0a ; color add si, 1 add bx, 1 loop loc_0x37 retn ; my_print endp ; data message db "Loader ready." len dw $ - message
- 使用更新版的my_print函数,可以指定输出的起始坐标
同样地,它需要被写入磁盘文件disk.img
中,我们将它放在第二个扇区里。下面是一键启动的配置文件:
#!/bin/zsh nasm -o mbr.bin mbr-gpu.S nasm -o loader.bin loader.S dd if=mbr.bin of=disk.img bs=512 count=1 conv=notrunc dd if=loader.bin of=disk.img bs=513 count=1 seek=1 conv=notrunc bochs -q
测试结果
接下来通过一键启动脚本,我们让虚拟机自主运行,MBR和Loader分别输出了预期的内容,本节的内容结束啦~
这篇关于动手编写操作系统(3):系统引导过程——BIOS与MBR(下)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-12如何创建可引导的 ESXi USB 安装介质 (macOS, Linux, Windows)
- 2024-11-08linux的 vi编辑器中搜索关键字有哪些常用的命令和技巧?-icode9专业技术文章分享
- 2024-11-08在 Linux 的 vi 或 vim 编辑器中什么命令可以直接跳到文件的结尾?-icode9专业技术文章分享
- 2024-10-22原生鸿蒙操作系统HarmonyOS NEXT(HarmonyOS 5)正式发布
- 2024-10-18操作系统入门教程:新手必看的基本操作指南
- 2024-10-18初学者必看:操作系统入门全攻略
- 2024-10-17操作系统入门教程:轻松掌握操作系统基础知识
- 2024-09-11Linux部署Scrapy学习:入门级指南
- 2024-09-11Linux部署Scrapy:入门级指南
- 2024-08-21【Linux】分区向左扩容的方法