08.C語言繪製系統介面
簡介
上一節我們實現了從真實模式到保護模式下字元複製到1M記憶體空間外的顯示。直觀感受從真實模式到保護模式地址定址的變化。
目標
顯示器基本都有字元模式和圖形顯示模式,用C語言實現色彩斑斕的圖形顯示。引入C語言開發作業系統需要對C語言函式引數傳遞機制有基本認識。
1、為了能使用C語言,我們需要設定棧空間。C語言函式體中的變數就是使用棧管理的,預設情況下C語言編譯後生成的程式碼是能在該作業系統下執行的可執行檔案,在連結中連結器會插入相關的描述資訊。但我們要開發的是系統核心,如果將核心編譯成可執行的檔案,那麼就不能直接將核心載入到記憶體直接執行。所以需要想新的辦法。
2、用反彙編結合C語言和組合語言
gcc 編譯器給我們提供了相關的編譯選項,我們把C語言編譯成2進位制檔案後使用反彙編器得到nasm 彙編器能編譯的彙編檔案。
用以下命令編譯C程式碼模組,以便後面反彙編:
gcc -m32 -c -fno-asynchronous-unwind-tables os.c -o os.o
-m32:編譯的指令是32位指令集
-c:編譯出2進位制機器指令
反彙編工具objconv安裝 https://github.com/vertis/objconv.git
下載後進入objconv目錄,編譯該工具,執行下面的命令:
g++ -o objconv -O2 src/*cpp
, -O2中的圓圈是大寫字母O
用objconv 反彙編C語言生成的目標檔案os.o,命令如下:
objconv -fnasm os.o -o os.s
,目錄下便有一個反彙編檔案os.s
修改os.s 彙編檔案,刪除多餘的 SECTION 等不合適的彙編偽指令
在kernel.s 彙編檔案中使用%include "os.s"
匯入os.s彙編檔案
至此,我們便可使用nasm 正常編譯該檔案生成2進位制核心檔案
3.kernel.s 檔案內容如下:
;全域性描述符結構 8位元組 ; byte7 byte6 byte5 byte4 byte3 byte2 byte1 byte0 ; byte6低四位和 byte1 byte0 表示段偏移上限 ; byte7 byte4 byte3 byte2 表示段基址 ;定義全域性描述符資料結構 ;3 表示有3個引數分別用 %1、%2、%3引用引數 ;%1:段基址 %2:段偏移上限 %3:段屬性 %macro GDescriptor 3 dw %2 & 0xffff dw %1 & 0xffff db (%1>>16) & 0xff dw ((%2>>8) & 0x0f00) | (%3 & 0xf0ff) db (%1>>24) & 0xff %endmacro DA_32 EQU 0x4000 ; 32 位段 DA_CODE EQU 0x98 ; 只執行程式碼段屬性值 DA_RW EQU 0x92 ; 可讀寫資料段屬性值 org 0x9000 jmp entry [SECTION .gdt] ;定義全域性描述符 段基址 段偏移上限 段屬性 LABEL_GDT: GDescriptor 0, 0, 0 LABEL_DESC_CODE: GDescriptor 0, SegCodeLen-1, DA_CODE+DA_32 LABEL_DESC_VIDEO: GDescriptor 0xb8000, 0xffff, DA_RW LABEL_DESC_STACK: GDescriptor 0, STACK_TOP-1, DA_32+DA_RW LABEL_DESC_VRAM: GDescriptor 0, 0xffffffff, DA_RW ;gdt 表大小 GdtLen equ $-LABEL_GDT ;gdt表偏移上限和基地址 GdtPtr dw GdtLen-1 dd 0 ;cpu開機進入真實模式時使用的段暫存器 cs,ds,es,ss 和偏移地址組成記憶體地址,記憶體地址=段暫存器 * 16 + 偏移地址 ;保護模式中段暫存器儲存的是gdt 描述表中各個描述符的偏移,也叫選擇子 SelectorCode32 EQU LABEL_DESC_CODE-LABEL_GDT SelectorVideo EQU LABEL_DESC_VIDEO-LABEL_GDT SelectorStack EQU LABEL_DESC_STACK-LABEL_GDT SelectorVRAM EQU LABEL_DESC_VRAM-LABEL_GDT [SECTION .s16] [BITS 16] entry: mov ax,cs mov ds,ax mov es,ax mov ss,ax mov sp,0x100 ;設定螢幕色彩模式 mov al,0x13 mov ah,0 int 0x10 ;設定LABEL_DESC_CODE描述符段基址 mov eax,0 mov ax,cs shl eax,4 add eax,SEG_CODE32 mov word [LABEL_DESC_CODE+2],ax shr eax,16 mov [LABEL_DESC_CODE+4],al mov [LABEL_DESC_CODE+7],ah ;設定棧空間 xor eax,eax mov ax,cs shl eax,4 add eax,LABEL_STACK mov word [LABEL_DESC_STACK+2],ax shr eax,16 mov byte [LABEL_DESC_STACK+4],al mov byte [LABEL_DESC_STACK+7],ah mov eax,0 mov ax,ds shl eax,4 add eax,LABEL_GDT mov dword [GdtPtr+2],eax ;設定GDTR 暫存器 lgdt [GdtPtr] cli ;關閉可可遮蔽中斷,如鍵盤中斷 in al,0x92 or al,0x02 out 0x92,al mov eax,cr0 or eax,1 mov cr0,eax jmp dword SelectorCode32:0 [SECTION .s32] [BITS 32] SEG_CODE32: mov ax,SelectorStack mov ss,ax mov esp,STACK_TOP mov ax,SelectorVRAM mov ds,ax call _showChar ;呼叫C語言設定圖形功能 global _io_hlt ;申明該函式能被外界呼叫,主要給C語言呼叫, void io_hlt(); _io_hlt: hlt ret %include "os.s" ;匯入C語言編寫的功能模組 ;32位模式程式碼長度 SegCodeLen EQU $-SEG_CODE32 [SECTION .gs] ALIGN 32 [BITS 32] LABEL_STACK: times 512 db 0 STACK_TOP EQU $ - LABEL_STACK
4.os.c 檔案內容如下:
//申明需要調用匯編實現的hlt 功能
extern void io_hlt();
void showChar(){
int i;
char *p = (char *)0xa0000;
for (i = 0; i <= 0xffff; i++) {
*p = i & 0x0f;
p++;
}
for(; ;){
io_hlt();
}
}
執行:gcc -m32 -c -fno-asynchronous-unwind-tables os.c -o os.o
編譯該os.c檔案為32位指令集2進位制檔案os.o
執行:./objconv -fnasm os.o -o os.s
反彙編成nasm 能編譯的彙編檔案os.s ,刪除多餘的彙編偽指令後如下:
_showChar:; Function begin
push ebp ; 0000 _ 55
mov ebp, esp ; 0001 _ 89. E5
sub esp, 8 ; 0003 _ 83. EC, 08
mov eax, 655360 ; 0006 _ B8, 000A0000
mov dword [ebp-8H], eax ; 000B _ 89. 45, F8
mov dword [ebp-4H], 0 ; 000E _ C7. 45, FC, 00000000
?_001: cmp dword [ebp-4H], 65535 ; 0015 _ 81. 7D, FC, 0000FFFF
; Note: Immediate operand could be made smaller by sign extension
jg ?_002 ; 001C _ 0F 8F, 00000024
mov eax, dword [ebp-4H] ; 0022 _ 8B. 45, FC
and eax, 0FH ; 0025 _ 83. E0, 0F
mov cl, al ; 0028 _ 88. C1
mov eax, dword [ebp-8H] ; 002A _ 8B. 45, F8
mov byte [eax], cl ; 002D _ 88. 08
mov eax, dword [ebp-8H] ; 002F _ 8B. 45, F8
add eax, 1 ; 0032 _ 83. C0, 01
mov dword [ebp-8H], eax ; 0035 _ 89. 45, F8
mov eax, dword [ebp-4H] ; 0038 _ 8B. 45, FC
add eax, 1 ; 003B _ 83. C0, 01
mov dword [ebp-4H], eax ; 003E _ 89. 45, FC
; Note: Immediate operand could be made smaller by sign extension
jmp ?_001 ; 0041 _ E9, FFFFFFCF
?_002:
; Note: Immediate operand could be made smaller by sign extension
jmp ?_003 ; 0046 _ E9, 00000000
?_003: call _io_hlt ; 004B _ E8, FFFFFFB0(rel)
; Note: Immediate operand could be made smaller by sign extension
jmp ?_003 ; 0050 _ E9, FFFFFFF6
; _showChar End of function
5.執行:nasm kernel.s -o kernel.bat
生成kernel.bat 核心檔案
此時我們只差使用C語言把核心載入器和核心檔案寫入虛擬軟盤,我們生成的kernel.bat 檔案已經超過了512位元組,我們需要修改核心載入器boot.s和C語言寫入模組功能,讓核心檔案能完整寫入虛擬軟盤。
main.c 檔案修改如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "floppy.h"
int main(int argc,char **argv){
FILE *fp = initFloppy("floppy.img");
if(fp == NULL) {
printf("初始化磁碟失敗");
exit(0);
}
FILE *src = fopen("boot.bat", "r");
if(src == NULL) {
printf("檔案開啟失敗");
exit(0);
}
char buf[512];
memset(buf, 0, 512);
fread(buf, 512, 1, src);
buf[510] = 0x55;
buf[511] = 0xaa;
writeFloppy(0, 0, 1, fp, buf);
fclose(src);
src = fopen("kernel.bat", "r");
if(src == NULL) {
printf("檔案開啟失敗");
exit(0);
}
//可正確處理16個扇區的資料內容
for(int i=0;!feof(src);i++){
memset(buf, 0, 512);
fread(buf, 512, 1, src);
writeFloppy(1, 0, 2+i, fp, buf);
printf("第%d次讀取kernel.bat\n",i+1);
}
fclose(src);
fclose(fp);
return 1;
}
boot.s 修改扇區連續讀取數如下:
mov al,3 ;表示連續讀取扇區數
使用nasm 生成boot.bat 核心載入器2進位制檔案
6.gcc 編譯main.c 並執行,在目錄下可生成floppy.img 虛擬磁碟檔案,使用虛擬機器載入該虛擬軟盤檔案效果如下:
至此,我們使用C語言實現圖形介面成功完成!
總結
1、kernel.s 檔案中我們添加了棧描述符LABEL_DESC_STACK,棧選擇子SelectorStack,並在程式碼中設定段基址、段界限、段屬性:
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_STACK
mov word [LABEL_DESC_STACK+2],ax
shr eax,16
mov byte [LABEL_DESC_STACK+4],al
mov byte [LABEL_DESC_STACK+7],ah
2、設定圖形模式段和選擇子為簡單起見把4G的記憶體空間都這是為可讀寫
LABEL_DESC_VRAM: GDescriptor 0, 0xffffffff, DA_RW
SelectorVRAM EQU LABEL_DESC_VRAM-LABEL_GDT
圖形模式下顯示基址為:0xa0000
3、使用如下彙編程式碼呼叫BIOS功能設定顯示器為圖形模式:
mov al,0x13
mov ah,0
int 0x10
其中al 的值決定了要設定顯示卡的色彩模式,下面是一些常用的模式設定:
1)0x03, 16色字元模式
2) 0x12, VGA圖形模式, 640 * 480 * 4位彩色模式,獨特的4面儲存模式
3) 0x13, VGA圖形模式, 320 * 200 * 8位彩色模式,調色盤模式
4) 0x6a, 擴充套件VGA圖形模式, 800 * 600 * 4彩色模式
4、32位模式程式碼中設定棧選擇子:
mov ax,SelectorStack
mov ss,ax
mov esp,STACK_TOP
mov ax,SelectorVRAM
mov ds,ax
call _showChar ;呼叫C語言設定圖形功能