現在想要在螢幕上顯示點什麼東西,需要向VRAM中數據,雖然C語言可以寫記憶體,但我們還是先用匯編來完成這個工作吧。現在在naskfunc.c中新增點東西。
_write_mem8: ; void write_mem8(int addr, int data);
MOV ECX, [ESP + 4] ; [ESP + 4]中存放的是地址,蔣其讀入ECX
MOV AL, [ESP + 8] ; [ESP + 8]中存放的是數據,將其讀入AL
MOV [ECX], AL
RET
在第二天的部落格中有一張CPU記憶體部分暫存器的表,SP表示16位元的棧指針暫存器,而此處ESP表示32位元的棧指針暫存器。函數的參數會按地址從低到高的順序存放在棧中,可以通過ESP取出參數。
這段程式碼的含義就是把data數據存入記憶體的addr地址中。另外,要與C語言聯合使用的話,能自由使用的暫存器就只有EAX、ECX和EDX,因此不能隨意更改程式碼中的暫存器。
naskfunc.c中還新增了一個INSTRSET指令,由於書中有介紹就不多說了。
彙編部分完成了,就開始寫C語言程式碼了。
void io_hlt(void);
void write_mem8(int addr, int data);
void HariMain(void)
{
int i;
for (i = 0xa0000; i <= 0xaffff; i++)
{
write_mem8(i, 15); /* MOV BYTE [i], 15 */
}
while (1)
{
io_hlt();
}
}
不會C語言語法的讀者就先去學學C語言吧,相信我,那會事半功倍的。
這段程式碼是將地址0xa0000~0xaffff的記憶體全部寫入15,然後一直死回圈。
來執行看看效果吧。
一片白色,正是我們想要的效果。
改成條紋圖案的操作倒是很簡單,只需要改一行程式碼:
for (i = 0xa0000; i <= 0xaffff; i++)
{
write_mem8(i, i & 0x0f); /* MOV BYTE [i], 15 */
}
這裏用到了與運算,如之前所說,不介紹C語言的基礎語法,相信大家也能看懂。
這段程式碼就是把調色板的16種顏色全部顯示出來,並循環往復,我們設定的螢幕寬度是320畫素,是16的整數倍,所以相同的顏色會在同一列出現,形成條紋。
最後的執行效果如下:
這圖看久了眼睛都覺得有點花。
指針作爲C語言的核心,自然是重中之重。什麼?你沒學過指針?那你學的什麼C語言。什麼?感覺指針太難了,學不會?那我只能說C語言不適合你,寫操作系統也不適合你,去學Java、Python或Php吧。
來看看使用指針後的程式碼吧。
int i; /* int爲32位元 */
char *p; /* BYTE型地址 */
for (i = 0xa0000; i <= 0xaffff; i++)
{
p = (char*) i;
*p = i & 0x0f; /* 代替了write_mem8(i, i & 0x0f); */
}
p = (char*) i;這句程式碼中對i進行了型別轉換,雖然在這裏字元指針和整型數據都佔4位元組,但編譯器它不聽啊,你不按規矩來,他就報警告,雖然警告沒什麼影響,但眼不見心不煩嘛,0 error 0 warning聽起來多舒服。
執行一下看結果。
對程式碼做點小小的修改:
p = (char *) 0xa0000; /* 給地址變數賦值 */
for (i = 0; i <= 0xffff; i++)
{
*(p + i) = i & 0x0f;
}
圖我就不放了,反正效果看上面的就行了。
對程式碼做點小小的修改:
p = (char *) 0xa0000; /* 給地址變數賦值 */
for (i = 0; i <= 0xffff; i++)
{
p[i] = i & 0x0f;
}
這和陣列操作差不多。
圖也不放了,偷懶。
既然都能顯示顏色了,那自定義要顯示的顏色?當然可以!但是我們只能設定最多256種顏色,至於爲什麼只能設定256位,還記得我們在asmhead.nas中設定的8位元彩色模式吧。這個8位元彩色模式,是由程式設計師隨意指定0~255的數位所對應的顏色的。1個數字對應一種顏色,這種方式被稱爲調色板。
來看看程式碼吧。
void init_palette(void)
{
static unsigned char table_rgb[16 * 3] =
{
0x00, 0x00, 0x00, /* 0:黑 */
0xff, 0x00, 0x00, /* 1:亮紅 */
0x00, 0xff, 0x00, /* 2:亮綠 */
0xff, 0xff, 0x00, /* 3:亮黃 */
0x00, 0x00, 0xff, /* 4:亮藍 */
0xff, 0x00, 0xff, /* 5:亮紫 */
0x00, 0xff, 0xff, /* 6:淺亮藍 */
0xff, 0xff, 0xff, /* 7:白 */
0xc6, 0xc6, 0xc6, /* 8:亮灰 */
0x84, 0x00, 0x00, /* 9:暗紅 */
0x00, 0x84, 0x00, /* 10:暗綠 */
0x84, 0x84, 0x00, /* 11:暗黃 */
0x00, 0x00, 0x84, /* 12:暗藍 */
0x84, 0x00, 0x84, /* 13:暗紫 */
0x00, 0x84, 0x84, /* 14:淺暗藍 */
0x84, 0x84, 0x84 /* 15:暗灰 */
};
set_palette(0, 15, table_rgb);
}
這個函數的作用就是定義顏色數據,然後將數據傳入下一個函數中進行處理。
這裏我們得先講講一些彙編程式碼,因爲下面 下麪的C語言程式需要用到彙編語言編寫的函數。作者搞事啊,一下子蹦出這麼多函數,我就挑幾個來講講好了。
_io_in8: ; int io_in8(int port)
MOV EDX, [ESP + 4] ; port
MOV EAX, 0
IN AL, DX
RET
_io_out8: ; void io_out8(int port, int data);
MOV EDX, [ESP + 4] ; port
MOV AL, [ESP + 8] ; data
OUT DX, AL
RET
_io_in8用於從埠讀入一個位元組,_io_out8用於向埠輸出一個位元組。其中,IN和OUT都是彙編中的I/O操作指令,IN用於從埠讀取數據,OUT用於向埠寫入數據。
如:IN AL, 21H表示從21H埠讀取一個位元組數據到AL。
IN AX, 21H表示從21H埠讀取兩個位元組數據到AX(21H埠的數據放入AL,22埠的數據放入AH)
OUT 21H, AL表示將AL的值寫入21H埠
OUT 21H, AX表示將AX的值寫入埠地址21H開始的兩個位元組(AL寫入21H埠,AH寫入22H埠)
注:當I/O地址大於FFH時,地址需放入DX中,而且數據傳輸只能通過使用EAX,AX或AL。
_io_cli: ; void io_cli(void);
CLI
RET
_io_sti: ; void io_sti(void);
STI
RET
CLI指令用於禁止中斷髮生,STI指令用於允許中斷髮生。所以這兩函數的功能也顯而易見。
_io_load_eflags: ; int io_load_eflags(void);
PUSHFD ; 將標誌暫存器數據壓入棧中
POP EAX
RET
_io_store_eflags: ; void io_store_eflags(int eflags);
MOV EAX, [ESP + 4]
PUSH EAX
POPFD ; 將數據寫入標誌暫存器中
RET
這兩函數的主要內容都已經在註釋寫好了,而且關於棧操作書上也寫得很清楚,我就不多說了。接下來繼續介紹C語言檔案的函數。
void set_palette(int start, int end, unsigned char *rgb)
{
int i, eflags;
eflags = io_load_eflags(); /* 記錄中斷許可標誌的值 */
io_cli(); /* 將中斷許可標誌置爲0,禁止中斷 */
io_out8(0x03c8, start);
for (i = start; i <= end; i++)
{
io_out8(0x03c9, rgb[0] / 4);
io_out8(0x03c9, rgb[1] / 4);
io_out8(0x03c9, rgb[2] / 4);
rgb += 3;
}
io_store_eflags(eflags); /* 復原中斷許可標誌 */
}
首先用io_load_eflags儲存狀態暫存器的值,然後用io_cli將中斷標誌位置0(中斷標誌位在狀態暫存器上),禁止中斷,最後復原狀態暫存器的值,被修改的中斷標誌就會被修改回來。
程式碼中分別對0x3C8和0x3C9埠進行了操作,這兩個埠的操作步驟如下:
程式的結果就是把調色板的數位0對應黑色,1對應紅色,……,15對應暗灰。其中有個奇怪的地方就是,io_out8(0x03c9, rgb[0] / 4);這段程式碼中,當rgb[0]=0xff時,程式碼中爲rgb[0]或rgb[0] / 4的效果是一樣的;但當rgb[0]=0x84時,效果也不一樣了。而且,當rgb[0]=0xff時,程式碼爲io_out8(0x03c9, rgb[0] / 4);和程式碼爲io_out8(0x03c9, 0xcf);的效果不一樣,也不知道是爲什麼。如果有人知道請務必在下方評論告訴我。
主程式並沒有什麼變化。讓我們看看修改後的效果。
可以看到從左到右依次是黑、紅、綠、黃……,和我們設定的一樣。
繪製矩陣是完成靠C語言程式邏輯實現的,而且並沒有什麼難度,相信讀者花一點時間也能琢磨出一個實現繪製矩陣的演算法。
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
int x, y;
for (y = y0; y <= y1; y++)
{
for (x = x0; x <= x1; x++)
{
vram[y * xsize + x] = c;
}
}
}
如下圖所示:
首先從(x0, y0)點開始填充畫素點,直到(x1, y0),然後換行繼續填充,直到(x1, y1)。很簡單的程式邏輯。
說來慚愧,之前竟然把調色板的工作方式給忘了,所以面對COL8_000000這個定義是數位時還想了一會兒是怎麼回事。因爲調色板的工作方式,一個數字代表一個顏色,只要往VRAM中寫入數位,螢幕上會顯示數位相應的顏色。
主函數中新增了繪製矩陣的程式碼,沒什麼好講的,就直接看效果吧。
也不知道五彩斑斕的黑的顏色怎麼定義,我想畫出來。
大家期待的介面它來了!
先看看程式碼。
void HariMain(void)
{
char *vram; /* BYTE型地址 */
int xsize, ysize;
init_palette(); /* 設定調色板 */
vram = (char *) 0xa0000; /* 給地址變數賦值 */
xsize = 320;
ysize = 200;
boxfill8(vram, xsize, COL8_008484, 0, 0, xsize - 1, ysize - 29);
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 28, xsize - 1, ysize - 28);
boxfill8(vram, xsize, COL8_FFFFFF, 0, ysize - 27, xsize - 1, ysize - 27);
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 26, xsize - 1, ysize - 1);
boxfill8(vram, xsize, COL8_FFFFFF, 3, ysize - 24, 59, ysize - 24);
boxfill8(vram, xsize, COL8_FFFFFF, 2, ysize - 24, 2, ysize - 4);
boxfill8(vram, xsize, COL8_848484, 3, ysize - 4, 59, ysize - 4);
boxfill8(vram, xsize, COL8_848484, 59, ysize - 23, 59, ysize - 5);
boxfill8(vram, xsize, COL8_000000, 2, ysize - 3, 59, ysize - 3);
boxfill8(vram, xsize, COL8_000000, 60, ysize - 24, 60, ysize - 3);
boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 24, xsize - 4, ysize - 24);
boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 23, xsize - 47, ysize - 4);
boxfill8(vram, xsize, COL8_FFFFFF, xsize - 47, ysize - 3, xsize - 4, ysize - 3);
boxfill8(vram, xsize, COL8_FFFFFF, xsize - 3, ysize - 24, xsize - 3, ysize - 3);
while (1)
{
io_hlt();
}
}
老實說,程式碼沒什麼好講的。我就是來水字數的。下面 下麪是介面效果。
這介面應該能和85年的Windows介面有的一拼。
今天的內容又結束了。本以爲出完第3天的部落格之後要等半個月才能 纔能寫出這篇來,結果只花了3天時間。其中,2天學第7章的內容,1天時間寫這篇部落格。。。雖然部落格簡陋,內容也不多,也是花了我3小時才寫出來的。願與你繼續看第5天的部落格,雖然也沒寫,先留個坑再說,什麼時候學完了第8章什麼時候寫。我寫這麼多幹什麼,又沒人看。