1. 程式人生 > >利用C語言繪製作業系統影象介面

利用C語言繪製作業系統影象介面

有了C語言這一利器後,不多多拿來用,那就太對不起前面的一系列努力了。那麼怎麼表現C語言的強大功能呢,如果還只是一味的在介面上輸出幾行字元,那太沒意思,考慮到,我們的目標是做出像windows那樣具備舒心的影象使用者介面那樣的系統,所以在這一節,我們由字元模式切換入畫面模式,初步體驗下,那些絢麗多彩的影象介面是如何發展而成的。

要想由字元模式轉入圖形模式,我們需要操作硬體,特別是向顯示卡傳送命令,讓其進入圖形顯示模式,就如同前面我們所做的,要操作硬體,一般需要使用BIOS呼叫,以下幾行就是開啟VGA顯示卡色彩功能的程式碼:

mov  al, 0x13h
mov  ah, 0x00
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彩色模式

我們採用的是0x13模式,其中320*200*8 中,最後的數值8表示的是色彩值得位數,也就是我們可以用8位數值表示色彩,總共可以顯示256種色彩。

系統視訊記憶體的地址是0x000a0000,當我們執行上面幾句程式碼後,望視訊記憶體地址寫入資料,那麼螢幕就會出現相應的變化了。

我們先看看核心的彙編程式碼部分(kernel.asm):

%include "pm.inc"

org   0x9000

jmp   LABEL_BEGIN

[SECTION .gdt]
 ;                                  段基址          段界限                屬性
LABEL_GDT:          Descriptor        0,            0,                   0  
LABEL_DESC_CODE32:  Descriptor        0,      SegCode32Len - 1
, DA_C + DA_32 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW LABEL_DESC_VRAM: Descriptor 0, 0ffffffffh, DA_DRW LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32 GdtLen equ $ - LABEL_GDT GdtPtr dw GdtLen - 1 dd 0 SelectorCode32 equ LABEL_DESC_CODE32 - 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] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h mov al, 0x13 mov ah, 0 int 0x10 xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_SEG_CODE32 mov word [LABEL_DESC_CODE32 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al mov byte [LABEL_DESC_CODE32 + 7], ah ;set stack for C language 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 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT mov dword [GdtPtr + 2], eax lgdt [GdtPtr] cli ;關中斷 in al, 92h or al, 00000010b out 92h, al mov eax, cr0 or eax , 1 mov cr0, eax jmp dword SelectorCode32: 0 [SECTION .s32] [BITS 32] LABEL_SEG_CODE32: ;initialize stack for c code mov ax, SelectorStack mov ss, ax mov esp, TopOfStack mov ax, SelectorVram mov ds, ax C_CODE_ENTRY: %include "write_vga.asm" io_hlt: ;void io_hlt(void); HLT RET SegCode32Len equ $ - LABEL_SEG_CODE32 [SECTION .gs] ALIGN 32 [BITS 32] LABEL_STACK: times 512 db 0 TopOfStack equ $ - LABEL_STACK

解釋下上面程式碼,我們設定了一個描述符,LABEL_DESC_VRAM, 這個描述符對應的記憶體起始地址是0,長度是0xffffffff,也就是我們把整個4G記憶體當做一段可讀可寫的記憶體,有了這個設定後,我們在C語言裡就可以隨意讀寫記憶體的任何地方。

LABEL_DESC_STACK 這個描述符用來設定一段可讀可寫的記憶體,它的起始地址是LABEL_STACK, 可以看到,程式通過語句:times 512 db 0
初始化了512位元組的記憶體。C語言的執行,特別是函式呼叫時,是需要一個堆疊來傳遞引數的,所以,要執行C語言,我們首先需要為其配置一個堆疊,該描述符所對應的這512自己記憶體就是給C語言使用的,由於堆疊只有512位元組,在後面我們使用C語言寫的程式碼中,函式的區域性變數大小不能超過512位元組,例如下面的程式碼可能就要出錯了:

void fun() {
  char buf[513];
}

語句%include write_vga.asm”, 表明,我們要開發的C程式碼檔案叫write_vga.c, 我們寫完C程式碼後,會使用上一節的步驟將它編譯成彙編,然後include到我們當前的彙編檔案裡,統一編譯成可執行核心。

最後一小塊程式碼:
io_hlt: ;void io_hlt(void);
HLT
RET
作用是進入死迴圈,HLT指令會讓系統進入休眠狀態。

匯入C語言

硬體,堆疊等基層設施通過彙編準備就緒後,我們可以使用C語言開發圖形功能了。顯示器的每一個畫素對應一個點,一個點可以顯示256種不同的顏色,因此,只要我們給每個點設定成相應的顏色,那麼最終就可以繪製出特定的影象。

我們看看如何用C語言寫入視訊記憶體從而操作螢幕影象,write_ram.c:

void CMain(void) {
    int i;
    char*p = 0;

    for (i = 0xa0000; i <= 0xaffff; i++) {
        p = i;
        *p = i & 0x0f;  
    }

    for(;;) {
       io_hlt();
    }

}

程式碼中,我們將指標P指向地址0xa0000, 這個地址正好就是vga視訊記憶體地址,vga視訊記憶體地址從0xa0000開始,直到0xaffff結束,總共64k.接著語句:
*p = i & 0x0f 將一個數值寫入視訊記憶體,這個值可以是0-256中任意一個數值,我們程式碼裡是將i的最後4位作為畫素顏色寫入視訊記憶體,這個值是任意的,大家可以隨意設定。

在Ubuntu中寫出上面程式碼後,通過命令編譯成二進位制檔案:
gcc -m32 -fno-asynchronous-unwind-tables -s -c -o write_vga.asm write_vga.c

於是在目錄下會生成write_vga.o二進位制檔案,接著使用objconv進行反彙編:
./objconv -fnasm write_vga.asm write_vga.o
反彙編後代碼如下:

; Disassembly of file: write_vga.o
; Tue Sep 13 10:30:14 2016
; Mode: 32 bits
; Syntax: YASM/NASM
; Instruction set: 80386


global CMain: function

extern io_hlt                                           ; near


SECTION .text   align=1 execute                         ; section number 1, code

CMain:  ; Function begin
        push    ebp                                     ; 0000 _ 55
        mov     ebp, esp                                ; 0001 _ 89. E5
        sub     esp, 24                                 ; 0003 _ 83. EC, 18
        mov     dword [ebp-0CH], 0                      ; 0006 _ C7. 45, F4, 00000000
        mov     dword [ebp-10H], 655360                 ; 000D _ C7. 45, F0, 000A0000
        jmp     ?_002                                   ; 0014 _ EB, 17

?_001:  mov     eax, dword [ebp-10H]                    ; 0016 _ 8B. 45, F0
        mov     dword [ebp-0CH], eax                    ; 0019 _ 89. 45, F4
        mov     eax, dword [ebp-10H]                    ; 001C _ 8B. 45, F0
        and     eax, 0FH                                ; 001F _ 83. E0, 0F
        mov     edx, eax                                ; 0022 _ 89. C2
        mov     eax, dword [ebp-0CH]                    ; 0024 _ 8B. 45, F4
        mov     byte [eax], dl                          ; 0027 _ 88. 10
        add     dword [ebp-10H], 1                      ; 0029 _ 83. 45, F0, 01
?_002:  cmp     dword [ebp-10H], 720895                 ; 002D _ 81. 7D, F0, 000AFFFF
        jle     ?_001                                   ; 0034 _ 7E, E0
?_003:  call    io_hlt                                  ; 0036 _ E8, FFFFFFFC(rel)
        jmp     ?_003                                   ; 003B _ EB, F9
; CMain End of function


SECTION .data   align=1 noexecute                       ; section number 2, data


SECTION .bss    align=1 noexecute                       ; section number 3, bss

在上面程式碼中去掉以section 開始的指令,這些指令會影響我們把當前彙編結合入核心kerne.asm.
同時去掉開頭的兩句:

global CMain: function
extern io_hlt

因為我們要把兩個彙編檔案結合成一個,所以這兩句宣告是多餘的。做完這些後,再用nasm編譯kernel.asm:
nasm -o kernel.bat kernel.asm
於是本地目錄下,核心檔案就編譯好了。

接著執行java工程,生成虛擬軟盤,執行結果如下:
這裡寫圖片描述

大家注意看,kernel.bat寫入了兩個扇區,也就是說,我們核心的大小已經超過了512位元組。此時我們需要修改一下核心載入器,讓核心載入器一次讀入兩個扇區才能把核心完全載入入記憶體,開啟boot.asm,將readFloppy中的:
mov ah, 0x02
mov al, 1
改成:
mov al, 2
也就是一次讀取兩個扇區的內容,修改後再次編譯boot.asm:
nasm -o boot.bat boot.asm

最後再次執行java程式,此時生成的虛擬軟盤中,才會包含完整的核心檔案。啟動虛擬機器,載入虛擬軟盤後,執行情況如下:
這裡寫圖片描述

大家可以看到,螢幕顯示出了條紋狀影象。