Day 4 C 語言與畫面顯示的練習

這天很多在介紹 C,尤其是 pointer,作者用組語的角度去看 C,很有趣。這邊只寫些簡單的筆記。

組語跟 C 一起使用時,只有 EAXECXEDX 三個 register 可以使用,其他 register 只能 read 不能 write,因為它們存著 C 語言程式的相關資料。

C 語言中,普通數值跟表示 memory address 的數值被當作兩種不同的東西。

根據 C 語言規定,組語執行 RET 指令時, EAX 中的值就被看作是 function return value。

調色盤設定顏色

在 8 bit 彩色模式下,顏色以 8 個 bit 表示,也就是 0~255。

每個數字表示什麼顏色,是由 developer 來決定的,不像 RGB 的 #ffffff 固定代表某個顏色。使用 0~255 表示顏色前,developer 要先幫這些數字指定好對應的顏色,例如 25 對應 #ffffff 等等。這種方式稱為「調色盤」。

設定調色盤的步驟如下:

  1. 先 block interrupt
  2. 依據設定調色盤的方式,對 IO device 的某些 port 寫入資料
  3. 恢復對 interrupt 的處理

向 IO Device read/write

先來看怎麼對 IO device 讀寫。

CPU 與 IO device 相連,CPU 要能控制 IO device 當然有向 device 發送訊號與從 device 接收訊號的指令。

向 device 發送訊號的指令是 OUT,反之從 device 接收訊號的指令是 IN。就像 memory 用 memory address 區分不同位置,device 以 device port 區分不同 device。組語實作從 device read 與 write 到 device 的 function 們:

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
36
; read 8 bit
_io_in8: ; int io_in8(int port);
MOV EDX,[ESP+4] ; port (port number 只有 16 bit)
MOV EAX,0 ; 把 return value 清成 0
IN AL,DX ; read 8 bit (AL)
RET ; EAX 的值是 return value

_io_in16: ; int io_in16(int port);
MOV EDX,[ESP+4] ; port
MOV EAX,0
IN AX,DX ; read 16 bit (AX)
RET

_io_in32: ; int io_in32(int port);
MOV EDX,[ESP+4] ; port
IN EAX,DX ; read 32 bit (EAX)
RET

; write 8 bit
_io_out8: ; void io_out8(int port, int data);
MOV EDX,[ESP+4] ; port
MOV AL,[ESP+8] ; data
OUT DX,AL
RET

_io_out16: ; void io_out16(int port, int data);
MOV EDX,[ESP+4] ; port
MOV EAX,[ESP+8] ; data
OUT DX,AX
RET

_io_out32: ; void io_out32(int port, int data);
MOV EDX,[ESP+4] ; port
MOV EAX,[ESP+8] ; data
OUT DX,EAX
RET

我們要設定調色板,就是找出對應 device 設定調色板的指令,然後照著做~

CLI 與 STI

CLI 是將 interrupt flag clear 為 0 的指令, STI 則是將 interrupt flag set 為 1 的指令。

interrupt flag 為 0 時,CPU 遇到 interrupt 會忽略它、不處理,flag 為 1 時 CPU 就會處理 interrupt。

EFLAGS register

EFLAGS 是由 FLAGS 16 bit 的 register 擴展而來的 32 bit register。

FLAGS 儲存 carry flag(進位 flag)與 interrupt flag 等 flag,不同 bit 代表不同 flag(有 1 個 bit 表示一個 flag 也有 2 個 bit),如下所示:

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
NT IOPL IOPL OF DF IF TF SF ZF AF PF CF

IOPL 是第 12 跟 13 bit 放在一起處理。

EFLAGS 沒有 MOV 指令,只能使用 PUSHFDPOPFD 來讀寫。

PUSHFD 是 push flags double-word 的縮寫,以 double word 的長度(32 bit)將 flag 的值 push 進 stack,等同 PUSH EFLAGSPOPFD 是 pop flags double-word,將 double word 長度的值從 stack pop 出來到 flag,等同 POP EFLAGS

如果想把 EFLAGS 內的資料放到 EAX 裡,不能用 MOV,而要先 PUSHFDPOP EAX。反過來想把 EAX 的資料放進 EFLAGS 裡則是 PUSH EAXPOPFD。操作 EFLAGS 的組語 code 如下:

1
2
3
4
5
6
7
8
9
10
_io_load_eflags:	; int io_load_eflags(void);
PUSHFD ; PUSH EFLAGS
POP EAX
RET

_io_store_eflags: ; void io_store_eflags(int eflags);
MOV EAX,[ESP+4]
PUSH EAX
POPFD ; POP EFLAGS
RET

設定調色盤要先執行 CLI,為了在設定完後恢復 interrupt flag 的值,要先把原本的值記下來。我們可以直接把整個 eflags 記下來、執行 CLI、設定調色盤,最後直接把整個 eflags 的值再存回去,達到恢復 interrupt flag 的效果。

在螢幕上畫圖

螢幕上每個像素都對應到 VRAM 中的一個 address。向 VRAM 指定值,便能指定螢幕上像素的顏色。