x86汇编如何操作显卡让显示器打印字符

1
2
mov ax, 0b800h
mov ds, ax

Screenshot_20220103_140129.png

8086CPU是一个16位的处理器,它有8个16位通用寄存器,每个寄存器都有自己的名字,而计算机经常处理单字节的数据,一个字节是8位,如果每次都用16位寄存器处理有些浪费,所以AX、BX、CX、DX这四个寄存器可以分别拆成2个8位寄存器来使用。

Screenshot_20220103_140709.png

8086一共有20根地址线,所以寻址能力是2^20,也就是1M,8086可以找到0x00000-0xFFFFF之间的所有地址。0x00000-0x9FFFF分配给内存,一共是640KB。0xCFFFF-0xFFFFF分配给BIOS。0xAFFFF-0xBFFFF分配给了包括文字模式、图像模式在内的显示部分

Screenshot_20220103_141038.png

显卡是支持文字模式和图形模式,其中文字模式还支持黑白和彩色两种。

图像模式包括VGA等占用0xA0000-0xAFFFF之间的64K空间,黑白模式占用0xB0000-0xB7FFF之间的32K空间。彩色文字模式占用0xB8000-0xBFFFF之间的32K空间。这些地址空间实际上以映射显卡的显存

Screenshot_20220103_141835.png

假如显存空间有4GB,而映射的地址空间可能只有256MB,那么首先把4GB开始的256MB映射到CPU的寻址空间,如果不够用,就切换到下一个256MB….

Screenshot_20220103_142056.png

文字模式会把显示器的整个区域分割为25行,每行80个字符,也就是整个屏幕可以显示2000个字符

如果为了显示字符,每次都操作像素太痛苦了,所以把每个可以显示的字符的像素先定义好,然后发送给显卡一个字符的代号,显卡就知道该怎么显示了,这个代号就是ASCII码表。

比如我们想显示字符’A’,它的编码是0x41,不能只是单纯的把0x41发送给显卡,还需要指定一个属性字节,这个字节必须紧跟在字符’A’后面发送

Screenshot_20220103_142639.png

如果我们想显示一个黑底白字的字符’A’,就要发送0x4107到显存去,而计算机默认就是显示黑底白字。但是发送的时候得空一个字节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
mov ax, 0b800h
mov ds, ax

// 偏移地址
mov byte [0x00], '2'
mov byte [0x02], '0'
mov byte [0x04], '2'
mov byte [0x06], '2'
mov byte [0x08], ','
mov byte [0x0a], 'H'
mov byte [0x0c], 'a'
mov byte [0x0e], 'p'
mov byte [0x10], 'p'
mov byte [0x12], 'y'
mov byte [0x14], ' '
mov byte [0x16], 'n'
mov byte [0x18], 'e'
mov byte [0x1a], 'w'
mov byte [0x1c], ' '
mov byte [0x1e], 'Y'
mov byte [0x20], 'e'
mov byte [0x22], 'a'
mov byte [0x24], 'r'
mov byte [0x26], '!'

CPU执行指令就是单纯的顺序执行,所以程序的指令都是放在一起的,占用一端连续的地址空间,称为代码段,数据也是一样的道理,所以也会有一个数据段

Screenshot_20220103_143551.png

CS寄存器指向代码段的起始地址,DS寄存器指向数据段的起始地址,指令寄存器IP里面存储的并不是绝对的物理地址,而是相对于CS寄存器的偏移,也就是说CPU访问指令是通过段地址+偏移地址的方式来进行的,CS指定代码段基准地址,IP指定代码段的偏移地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
; 设定数据段寄存器DS, 值为0b800h
mov ax, 0b800h
mov ds, ax

; 通过mov指令,把数据传送到显存地址
mov byte [0x00], '2'
mov byte [0x02], '0'
mov byte [0x04], '2'
mov byte [0x06], '2'
mov byte [0x08], ','
mov byte [0x0a], 'H'
mov byte [0x0c], 'a'
mov byte [0x0e], 'p'
mov byte [0x10], 'p'
mov byte [0x12], 'y'
mov byte [0x14], ' '
mov byte [0x16], 'n'
mov byte [0x18], 'e'
mov byte [0x1a], 'w'
mov byte [0x1c], ' '
mov byte [0x1e], 'Y'
mov byte [0x20], 'e'
mov byte [0x22], 'a'
mov byte [0x24], 'r'
mov byte [0x26], '!'

; 标定当前位置,死循环
jmp $

; $$表示程序的起始位置,$表示jmp所在的位置
; $-$$就是从开头到jmp的位置一共多少个字节
; 填充0
times 510-($-$$) db 0
; 结束标志位
db 0x55, 0xaa