写在前面:自制操作系统Gos 第二章第三篇:主要内容是如何操纵外设,如何操纵显示器
其实博主之前对和硬件打交道是非常恐惧的。一个是不了解,一个是外部设备种类繁多、原理各异,实在是没有太多的精力精通每一项了。其实,说白了就是没有一个统一的接口供我这种懒狗调用,兼容性太差。
但是,按照计算机哲学来看:任何不兼容的问题,其实都可以通过加一层来解决这个问题,这一层就是 —— IO接口。比如说,声卡就是驱动影响设备的;显卡就是驱动显示器的。所以,今天我们打交道的主要对象就是显卡了,通过显卡间接操作显示器。
注:
什么是IO接口呢?
IO接口时连接CPU和外部设备的逻辑控制部件,其分为硬件和软件两个部分。硬件部分所做的都是一些实质具体的工作,其功能时协调CPU和外设之间的种种不匹配,如双方由于不匹配,那IO接口就实现数据缓存以减少等待时间;数据格式不匹配,那IO接口就可以负责数据转换的功能。
当然,IO接口的作用还有很多,总结如下:
- 设置数据缓冲,解决CPU和外设速度不匹配的问题
- 设置信号电平转换电路。CPU时TTL电平,而外设大多时机电设备,需要进行转换
- 设置数据格式转换
- 设置时序控制电路来同步CPU和外部设备
- 提供地址译码
同一时刻,CPU只能和一个IO接口通信,当很多的IO接口同时想和CPU对话的时候。其实就导致了冲突的问题,那么如何解决呢?这个时候,我们再加一层去仲裁这个问题。而这一层就是大名鼎鼎的南桥。
CPU通过内部总线连接到南桥芯片的内部。同时,在南桥内部集成了一些IO接口。除了集成之外,还有一些供其他非必要设备支持的PCI接口。
IO接口在诞生之初,其就被设置为通过寄存器来和CPU交互。其内部也有专门的寄存器用于数据交互,称之为端口。IO接口是CPU和硬件的桥梁。一端是CPU,另一端是硬件。端口是IO接口开放给CPU的接口,一般的IO接口都有一组端口,而每个端口都有自己的用途。
刚刚也说了,端口的本质就是寄存器,其也有自己的数据宽度。所以我们需要合适的指令去访问,那么怎么访问呢?
#Intel版本的汇编语言格式
操作码 目的 源
# 从端口读取数据 --> in
in al,dx #dx☞端口号
# 写数据到端口 --> out
out dx,al #dx☞端口号
某些IO接口也叫适配器,适配器是驱动某一外部设备的功能模块。显卡也称为显示适配器,不够其本质就是IO接口,起了个高大上的名字罢了,不要害怕。
显卡呢,是PCI设备,其就是插在上面那个图中的插槽的。
注:
PCI总线时并行架构,并行数据需要保证数据发送之后要同时到达目的地。否则就是一团乱麻。为了解决这个问题,其实就不得不采用一次一位这样的发送方式,而这也被称为串口显卡。有人说这样可能会很慢,但是其实也不一定。记住:速率=单次传送数据*频率
。
为了显示图像,我们就需要显示器。但是无论是什么牌子的显示器,其都是由显卡来控制的。无论是哪种显卡,它提供给我们的可编程接口都是一样的:IO端口和显存。所以,同学们大家不要对硬件交互过于恐惧。
显存是什么:
显存是由显卡提供的,它是位于显卡内部的一块内存,所以称其为显存。
显卡的工作原理就是不断的读取显存,随后将其内容发送到显示器之中。所以,我们要做的工作就是往这块内存中不断写数据。写什么数据呢?
那么问题来了,我们知道要写什么了,那么怎么写到缓存里面呢?
我们先来看一些显存的地址分布:
起始 | 结束 | 大小 | 用途 |
---|---|---|---|
C0000 | C7FFF | 32KB | 显示适配器BIOS |
B8000 | BFFFF | 32KB | 用于文本模式显示适配器 |
B0000 | B7FFF | 32KB | 用于黑白显示适配器 |
A0000 | AFFFF | 64KB | 用于彩色显示适配器 |
注:
这里的地址是实模式哦
我们实现文本模式就可以了。也就是往上面0xB8000 ~0xBFFFF
这个地址范围写数据咯。
那么一个屏幕可以显示多少个文本呢?一本来说屏幕显示是行数*列数
。常用规格有80×25、40×25、80×43 等等。我们这里就默认的 80×25 就好了。
我们要注意一个问题:彩色字符是怎么实现的呢?
是的,即便在文本模式下面,也是可以打印彩色字符的。实现原理就是用两个字节表示一个字符,其中1个字节表示文本内容,一个字节表示文本属性。其内存布局如下:
图示说的很明白我这里就不赘述了。
下图显示了RGB三原色不同组合的效果:
R | G | B | 不高亮 | 高亮 |
---|---|---|---|---|
0 | 0 | 0 | 黑 | 灰 |
0 | 0 | 1 | 蓝 | 浅蓝 |
0 | 1 | 0 | 绿 | 浅绿 |
0 | 1 | 1 | 青 | 浅青 |
1 | 0 | 0 | 红 | 浅红 |
1 | 0 | 1 | 品红 | 浅品红 |
1 | 1 | 0 | 棕 | 黄 |
1 | 1 | 1 | 白 | 亮白 |
所以说,我们要打印出黑底亮白字的效果就需要往显存写入:0000 1111
,即0x0F
所以,我们现在已经了解所有原理的。我们可以上手写代码了:
; 显卡主引导程序
;
; LOADER_BASE_ADDR equ 0xA000
; LOADER_START_ADDR equ 0x2
SECTION MBR vstart=0x7c00
mov ax,cs ;初始化
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0x7c00
mov ax,0xb800 ;0xb800 段基址
mov gs,ax
; 清屏利用0x06号功能,上卷全部行,进行清屏
; int 0x10 功能号:0x60 功能描述:上卷窗口
; 输入:
; AH 功能号: 0x06
; AL = 上卷的行数(0代表全部)
; BH = 上卷的行属性
; (CL,CH) = 窗口左上角(x,y) 的位置
; (DL,DH) = 窗口右下角(x,y)的位置
; 无返回值!
mov ax,0600h
mov bx,0700h
mov cx,0 ;左上角(0,0)
mov dx,0x184f ;右下角(80,25)
;VAG文本模式中,一行只能容纳80个字符,总共25行
;下标从0开始,所以0x18=24,0x4f=79
int 10h
; 以下是操纵显示器打印字符
mov byte [gs:0x00],'h'
mov byte [gs:0x01],0x0F ;黑底亮白不闪烁
mov byte [gs:0x02],'e'
mov byte [gs:0x03],0x0F
mov byte [gs:0x04],'l'
mov byte [gs:0x05],0x0F
mov byte [gs:0x06],'l'
mov byte [gs:0x07],0x0F
mov byte [gs:0x08],'o'
mov byte [gs:0x09],0x0F
mov byte [gs:0x0a],','
mov byte [gs:0x0b],0x0F
mov byte [gs:0x0c],'G'
mov byte [gs:0x0d],0x0F
mov byte [gs:0x0e],'o'
mov byte [gs:0x0f],0x0F
mov byte [gs:0x10],'s'
mov byte [gs:0x11],0x0F
mov byte [gs:0x12],'!'
mov byte [gs:0x13],0x0F
jmp $
times 510-($-$$) db 0 ;总共512字节,需要有510个占位
db 0x55,0xaa
很简单的汇编代码,做的事情也都写到注释里面了,起始也就是对MBR的代码的更改了一下。我们直接来看一下效果:
[1] 操作系统真相还原
[2] 百度图片