本章是操作系统从 0 到 1 系列的第五篇文章,暂时喘口气,来讲解下怎么在 32 位模式下向屏幕打印字符串,方便我们打印各种信息,增加成就感。
Concepts you may want to Google beforehand: 32-bit protected mode, VGA, video memory
# 背景
当系统进入到 32 位模式后,我们可以使用 32 位寄存器、更大的内存空间、保护模式、虚拟内存等高级功能,但同样的,我们也失去了通过中断直接调用 BIOS 的能力,所以前几章中使用的 print
函数就无法继续使用了(如果你细心的话,也可以注意到,在上一章末尾的 main
程序中,在进入 32 位模式后,我们就没有打印字符了,只是通过 switch_to_pm
函数后的打印来确定程序是否出错)。
这一点还是很难受的,所以本章我们就来写一个 32 位模式下的打印函数。
# 原理
在 32 位下打印信息需要用到 VGA Text Mode,一种极其古老的显示模式,打印的原理简单来说就是用 32KB
的内存来控制一个 25行 * 80列
的字符的屏幕终端,回忆一下第三章中出现的图:
看红框标出来的,答案就是利用 0xB8000 ~ 0xBFFFF
这 32KB
的空间,可以通过访问并修改这一段内存的值来控制屏幕的显示。
屏幕上有 25 * 80 = 2000
个字符,每个字符由 2 byte
控制,这样一屏幕就占用 4000 bytes
,所以 32 KB 最大容纳大约 8 屏的内容。为了简单起见,只控制第一屏幕的数据,超出部分就不予显示,也不支持上下翻屏等功能。
# 字符显示
一个字符由 2 byte
来控制,高 1 byte
控制字符颜色(前景色 / 背景色 / 闪烁等),低 1 byte
为字符对应的 Ascii
码,更加具体可以参考 Wiki 百科,这里给出其中的一个图。
3 个 bit 可以显示 8 种颜色
颜色 | bit 值 |
---|---|
黑 | 0 |
蓝 | 1 |
绿 | 2 |
青 | 3 |
红 | 4 |
粉 | 5 |
粽 | 6 |
白 | 7 |
其他控制位是类似的,就不再赘述了。
# 实现
由于是直接读写内存,实现的思想其实就是之前的 print
的思想,只不过会更简单一些,我们可以利用 ax
来暂存要打印的字符 (2 bytes),用 ebx
存储需要打印的字符串地址,用 edx
指向 VGA 的地址。代码非常简单,自己试试实现吧!
32bit-print.asm
[bits 32] ; using 32-bit protected mode | |
; this is how constants are defined | |
VIDEO_MEMORY equ 0xb8000 | |
WHITE_ON_BLACK equ 0x0f ; the color byte for each character | |
print_string_pm: | |
pusha | |
mov edx, VIDEO_MEMORY | |
print_string_pm_loop: | |
mov al, [ebx] ; [ebx] is the address of our character | |
mov ah, WHITE_ON_BLACK ; control char color | |
cmp al, 0 ; check if end of string | |
je print_string_pm_done | |
mov [edx], ax ; store character + attribute in video memory | |
add ebx, 1 ; next char | |
add edx, 2 ; next video memory position | |
jmp print_string_pm_loop | |
print_string_pm_done: | |
popa | |
ret |
「一定要尝试自己将文字颜色换一换试试哟!」
然后我们可以对上一章中的 main
代码中加入一下 print_string_pm
函数来试试吧
32bit-main.asm
[org 0x7c00] ; bootloader offset | |
mov bp, 0x9000 ; set the stack | |
mov sp, bp | |
mov bx, MSG_REAL_MODE | |
call print ; This will be written after the BIOS messages | |
call switch_to_pm | |
mov bx, MSG_ERROR | |
call print | |
jmp $ ; this will actually never be executed | |
%include "../02-mbr/boot-print.asm" ; must be the first included | |
%include "../03-loader/32bit-gdt.asm" | |
%include "32bit-print.asm" | |
%include "../03-loader/32bit-switch.asm" | |
[bits 32] | |
BEGIN_PM: ; after the switch we will get here | |
mov ebx, MSG_PROT_MODE | |
call print_string_pm ; Note that this will be written at the top left corner | |
jmp $ | |
MSG_REAL_MODE db "Started in 16-bit real mode", 0 | |
MSG_ERROR db "Loaded 32-bit protected mode ERROR", 0 | |
MSG_PROT_MODE db "Loaded 32-bit protected mode", 0 | |
; bootsector | |
times 510-($-$$) db 0 | |
dw 0xaa55 |
编译并运行后,我们就可以看到打印出来的文字啦!
「我们之前说过,VGA Text Mode 的内存是对应终端的字符的,由于我们是在 0xb8000
开始处写的,所以打印的字符也会从终端最开始处开始,而且可以明显看出,VGA 下的打印出字体的白色会更凉一点,与 BIOS 的打印不同。」
# 参考
- https://en.wikipedia.org/wiki/VGA_text_mode
- https://wiki.osdev.org/Text_mode
- https://wiki.osdev.org/Printing_To_Screen