本章是操作系统从 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 ~ 0xBFFFF32KB 的空间,可以通过访问并修改这一段内存的值来控制屏幕的显示。

屏幕上有 25 * 80 = 2000 个字符,每个字符由 2 byte 控制,这样一屏幕就占用 4000 bytes ,所以 32 KB 最大容纳大约 8 屏的内容。为了简单起见,只控制第一屏幕的数据,超出部分就不予显示,也不支持上下翻屏等功能。

# 字符显示

一个字符由 2 byte 来控制,高 1 byte 控制字符颜色(前景色 / 背景色 / 闪烁等),低 1 byte 为字符对应的 Ascii 码,更加具体可以参考 Wiki 百科,这里给出其中的一个图。

VGA Text Mode Char

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 Print

「我们之前说过,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

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Gality 微信支付

微信支付

Gality 支付宝

支付宝