VRAMに直書きする
自分の前回の投稿(自作OS(1): ブートローダ)では、BIOSの割り込み0x10
のテレタイプモードでテキストを表示したが今回はBIOSを使用せずにVRAMに直接書き込むことでテキストを表示させる
※とりあえず今回はリアルモードでの話
NASMを使用します
VRAMの領域は0xb800:0x0000
2byteが色を含めた1文字分に相当する
具体的には、
- 1byte目: キャラクタコード
- 2byte目: 0-3bit 文字色, 4-8bit 背景色
といった具合
また、80文字x25行なので、1行は160byteとなる
基本的に知っておくことはこれだけで、適切なオフセットを計算して2byteを書き込むということを繰り返す操作となる
コード
各種初期化
まず、stosw
命令はアキュムレータレジスタ(AX)をエクストラセグメント(ES)のディスティネーションインデックス(DI)に書き込むので、ESにはVRAMのセグメントアドレス0xb800
を入れておく
ソースインデックス(SI)には目的のテキストのアドレスを入れておく
[bits 16]
[org 0x7c00]
start:
;; initialize segment registers
xor ax, ax
mov ds, ax
mov ss, ax
;; initialize stack
mov sp, 0x7c00
mov ax, 0xb800 ; VRAM segment
mov es, ax ; segment reg for stosw
mov si, msg
jmp println
メインの処理
汎用レジスタの使い方は以下のような感じ
- アキュムレータレジスタ(AX): ALは文字、AHは色情報
- カウンターレジスタ(CX): 行内オフセット
- ベースレジスタ(BX): 行オフセット (160 byte/line), 25行あるので2byte使いました
大まかな処理の流れ
- SIレジスタから
lodsb
でALに次の文字を格納 - 終端なら1行分インクリメントして、とりあえず
hang
ヘ - AHに文字色と背景色の情報を格納
- BXにオフセット値を乗せる
- オフセットアドレス DI = CX + BX
- ES:DIにAXの内容を書き込む
- CXを1文字分(2byte)インクリメントして次の文字へ
println:
xor cx, cx ; cx = 0
print_char:
lodsb ; load a char to AL
or al, al ; if \0
jnz print
add word [line], 160 ; 1 line = 80 chars x 2 bytes
jmp hang
print:
mov ah, 0x0f ; char:white, back:black
;; set offset
mov bx, word [line]
xor di, di ; di = 0 (beginning of VRAM)
add di, cx ; x-offset
add di, bx ; y-offset
;; write
stosw ; stor ax to ES:DI, and di -= 2
add cx, 2 ; char/attr = 2byte
jmp print_char
hang:
hlt
jmp hang
line dw 0 ; = line num x ( 80 x 2 )
msg db "Hello World", 0
times 510-($-$$) db 0
dw 0xaa55