1. 程式人生 > >08.C語言繪製系統介面

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語言設定圖形功能