【操作系统真象还原】04编写MBR分区(二)和显卡对话
前言
通过BIOS提供的中断,我们的MBR程序在屏幕上输出了绿油油的 Hi from MBR!
。但只有在 实模式
下,我们才可以使用BIOS中断,而我们要编写的操作系统是运行在32位 保护模式
下的程序。既然不能调用BIOS中断了,那么我们就直接和显卡对话吧。
外部硬件与CPU
忙碌的CPU要和大量的硬件打交道,处理大量由硬件传递的数据。不同的硬件传递的数据是不一样的、响应速度是不一样的,而CPU的时间又是那么的宝贵,不能被浪费,所以硬件工程师们指定了一系列的IO接口,作为CPU与硬件之间的中间人。首先,IO接口用有缓冲区,在一定程度上缓解了CPU与硬件设备响应速度不匹配;其次,IO接口还拥有处理数据格式的功能,减少CPU的工作量。
还有些需要CPU频繁向其传输数据的硬件(如:显卡),直接将自身的内存空间,映射到计算机内存的某块空间上,只要在特定的地址上填写数据,就相当于在该硬件的内存上填写了数据。
和显卡对话
显存分布
现在,我们知道显卡将自己部分的内存空间映射到可计算机的内存中。想要和显卡对话,我们就要知道这块映射空间在哪块内存中(其实显卡也有自己的IO接口,不过我们不涉及)。
因为我们想要在屏幕上显示文字,所以我们选择将数据发送到起始地址为 b8000
的适用于 文本模式
的显存。
数据格式
在 文本模式
下,表示一个字符需要两个字节。第一个字节表示 字符ASCII码
,第二个字节表示 字符属性
。
因为可表示颜色有限,所以有一份整理好的字符属性表可供大家参考。
改进MBR
知道了怎么和显卡对话,我们现在就可以来改进之前编写的 MBR
了。
初始化段寄存器 gs
因为实模式下CPU的寻址方式为 段基地址:偏移地址
,即 物理地址 = 段基地址 * 16 + 偏移地址
。而文字模式显存起始地址为 b8000
,所以我们将 gs
寄存器的值初始化为 0xb800
,即:
mov ax, 0xb800mov gs, ax
编写 putchar
函数
将 di
寄存器初始化为 0
,通过 gs:di
寻址。所以 putchar
函数:
; al: ASCII码; bl: 字符属性
putchar:
mov [gs:di], al ; 放置ASCII码
inc di ; di++
mov [gs:di], bl ; 放置字符属性
inc di
ret ; return
编写 print_message
函数
我们将要打印的字符串 Hi from MBR!
,通过 message db "Hi from MBR!" db 0
的方式储存。其中 message
就相当于一个 char *
。0
代表字符串的结束符。
所以我们可以通过 si
寄存器保存 message
,即 字符串的起始地址
,通过 inc si
移动指针,通过 [si]
对指针解引用得到字符,通过 putchar
函数输出字符,通过判断字符是否为 0
从而判断是否到达字符串末尾。所以 print_message
函数:
print_message: .init:
mov ax, message
mov si, ax ; 让 ds:si 指向 message 的开头
mov bl, 0x07 ; 字符属性:黑底白字
.print:
mov al, [si] ; 读取字符串
or al, 0 ; al | 0
jz .end ; 若为0,则输出完毕
call putchar ; 不为0,则输出 al
inc si ; si++
jmp .print ; 循环
.end:
jmp $
MBR总览
; 主引导程序 mbr.sSECTION MBR vstart=0x7c00
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov fs, ax
mov ax, 0xb800
mov gs, ax
mov sp, 0x7c00 ; 初始化栈指针
xor di, di ; di = 0
; 目前 0x7c00 以下暂时是安全的区域,就把它当作找来用
; push ax; ax的内容压栈保存,(sp)=(sp)-2
; 清屏: 利用 0x06 号功能, 上卷全部行, 则可清屏
; -----------------------------------------------------------
; INT 0x10 功能号: 0x06 功能描述: 上卷窗口
; -----------------------------------------------------------
; 输入:
; ah: 功能号
; al: 上卷的行数
; bh: 上卷行属性
; (cl, ch): 窗口左上角的 (X, Y) 位置
; (dl, dh): 窗口右上角的 (X, Y) 位置
; 无返回值
mov ax, 0x0600
mov bx, 0x0700
mov cx, 0 ; 左上角: (0, 0)
mov dx, 0x184f ; 右下角: (80, 25)
; VGA文本模式中, 一行只能容纳 80 个字符, 共 25 行
; 下标从 0 开始, 所以 0x18=24,0x4f=79
int 0x10
print_message:
.init:
mov ax, message
mov si, ax ; 让 ds:si 指向 message 的开头
mov bl, 0x07 ; 字符属性:黑底白字
.print:
mov al, [si] ; 读取字符串
or al, 0 ; al | 0
jz .end ; 若为0,则输出完毕
call putchar ; 不为0,则输出 al
inc si ; si++
jmp .print ; 循环
.end:
jmp $
; al: ASCII码
; bl: 字符属性
putchar:
mov [gs:di], al ; 放置ASCII码
inc di ; di++
mov [gs:di], bl ; 放置字符属性
inc di
ret ; return
message db "Hi from MBR"
db 0
times 510-($-$$) db 0 ; 以 0 填补空间至 510 字节处
db 0x55, 0xaa ; 魔数 0x55 0xaa
## bochs 模拟
最后编译并写入镜像,运行bochs:
可以看见,我们成功输出了字符串 Hi from MBR!
,但光标的位置并不对劲。下一篇博客,我们来做一个属于自己的光标,并编写一个函数来监听键盘输入,并将键盘的输入打印到屏幕上!敬请期待(手动狗头)。
以上是 【操作系统真象还原】04编写MBR分区(二)和显卡对话 的全部内容, 来源链接: utcz.com/z/519954.html