本文記錄了在JOS(或在任意OS)上實現圖形界面的方法與一些圖形庫的實現。
本文中支持的新特性:java
相關:VBE VESA MMIO 點陣字庫
Github : https://github.com/He11oLiu/MOSgit
Video Electronics Standards Association
(視頻電子標準協會,簡稱「VESA」)是制定計算機和小型工做站視頻設備標準的國際組織,1989年由NEC及其餘8家顯卡製造商贊助成立。創立VESA的原始目的是要制定分辨率爲800x600
的SVGA視頻顯示標準。其後,VESA公告一系列的我的電腦視頻周邊功能的相關標準。github
參考博客CSDN博客編程
VBE
功能調用ruby
INT 10H
AL=4FH
:支持該功能AL!=4FH
:不支持該功能AH=00H
:調用成功AH=01H
:調用失敗AH=02H
:當前硬件配置不支持該功能AH=03H
:當前的顯示模式不支持該功能此部分參考VESA編程——GUI離咱們並不遙遠,原做者博客已關閉。markdown
輸入: AX = 4F00h 返回VBE控制器信息 ES:DI = 指向存放VbeInfoBlock結構體的緩衝區指針 輸出: AX = VBE返回狀態
這個函數返回一個VbeInfoBlock結構體,該結構體定義以下:數據結構
// Vbe Info Block
typedef struct {
unsigned char vbe_signature;
unsigned short vbe_version;
unsigned long oem_string_ptr;
unsigned char capabilities;
unsigned long video_mode_ptr;
unsigned short total_memory;
unsigned short oem_software_rev;
unsigned long oem_vendor_name_ptr;
unsigned long oem_product_name_ptr;
unsigned long oem_product_rev_ptr;
unsigned char reserved[222];
unsigned char oem_data[256];
} VbeInfoBlock;
vbe_signature
是VBE標識,應該填充的是」VESA」vbe_version
是VBE版本,若是是0300h則表示3.0版本oem_string_ptr
是指向oem字符串的指針,該指針是一個16位的selector:offset
形式的指針,在實模式下能夠直接使用。video_mode_ptr
是指向視頻模式列表的指針,與oem_string_ptr
類型同樣total_memory
是64kb內存塊的個數oem_vendor_name_ptr
是指向廠商名字符串的指針oem_product_name_ptr
是指向產品名字符串的指針輸入: AX = 0x4F01 返回VBE模式信息 CX = 模式號 ES:DI = 指向VBE特定模式信息塊的指針 輸出: AX = VBE返回值
這個函數返回一個ModeInfoBlock結構體,其中重要的部分以下:tcp
mode_attributes
字段,這個字段描述了圖形模式的一些重要屬性。其中最重要的是第4位和第7位。第4位爲1表示圖形模式(Graphics mode),爲0表示文本模式(Text mode)。第7位爲1表示線性幀緩衝模式(Linear frame buffer mode),爲0表示非線性幀緩衝模式。咱們主要要檢查這兩個位。xresolution
,表示該視頻模式的X分辨率。yresolution
,表示該視頻模式的Y分辨率。bits_per_pixel
,表示該視頻模式每一個像素所佔的位數。phys_base_ptr
,這是一個很是重要的字段,它給出了平坦內存幀緩衝區的物理地址,你能夠理解爲顯存的首地址。若是每一個像素佔32位的話,屏幕左上角第一個點所佔的緩衝區就是phys_base_ptr所指的第一個4個字節。按照先行後列的順序,每一個像素點所佔緩衝區依次緊密排列。咱們要想在屏幕上畫出像素點,就得操做以phys_base_ptr爲起始的物理內存空間。輸入:
AX = 4F02h 設置VBE模式
BX = 須要設置的模式
D0 - D8 = 模式號
D9 - D10 = 保留(必須爲0)
D11 = 0 使用當前缺省刷新率
= 1 使用用戶指定的CRTC值爲刷新率 D12 - D13 = 爲VBE/AF保留(必須爲0)
D14 = 0 使用窗口幀緩衝區模式
= 1 使用線性/平坦幀緩衝區模式 D15 = 0 清除顯示內存
= 1 不清除顯示內存 ES:DI = 指向CRTCInfoBlock結構體的指針
輸出:
AX = VBE返回狀態
JOS
實現Qemu須要添加-vga std
ide
QEMUOPTS = -drive file=$(OBJDIR)/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::$(GDBPORT) -vga std
在boot
中實模式中獲取VBE
,設置VBE
函數
sti
call getvideomode
call setvideomode
cli
先獲取VBE
模式,填充di
,而後切換模式,設置VBE
模式。
根據上面查的VESA
資料,調用函數,實現這兩個功能。
getvideomode:
mov $0x4f01, %ax # get mode
mov $0x105, %cx # mode 0x105
mov $0x8000, %di # mode info block address
int $0x10 # VBE int
ret
setvideomode:
movw $0x4f02, %ax # set mode
movw $0x105, %bx
movw $0x8000, %di
int $0x10 # VBE int
movl 40(%di), %eax # get memory address
movl %eax, info_vram
movw 18(%di), %ax # get x resolution
movw %ax, info_scrnx
movw 20(%di), %ax # get y resolution
movw %ax, info_scrny
ret
這裏設計了一個結構體來存放從boot
傳來的東西。
struct boot_info
{
short scrnx, scrny;
char *vram;
};
init
的時候,設計一個獲取boot_info
的模塊
static void get_boot_info(void)
{
struct boot_info *info = (struct boot_info *)(KADDR(0x0ff0));
// Init Graph info
graph.scrnx = info->scrnx;
graph.scrny = info->scrny;
graph.vram = info->vram;
}
這個地方我選擇初始化memory layout
以後,開啓真正的頁表的時候才獲取信息。因此這裏要用KADDR
進行物理地址到KVA
的轉化。
設計一個全局用於保存圖像相關信息的結構體
struct graph_info
{
short scrnx,scrny;
char *vram;
};
extern struct graph_info graph;
利用MMIO
映射一片顯存
void graph_init()
{
int i;
// Init Graph MMIO
graph.vram =
(char *)mmio_map_region((physaddr_t)graph.vram,
graph.scrnx * graph.scrny);
cprintf("====Graph mode on====\n");
cprintf(" scrnx = %d\n",graph.scrnx);
cprintf(" scrny = %d\n",graph.scrny);
cprintf("MMIO VRAM = %#x\n",graph.vram);
cprintf("=====================\n");
// Draw Screen
for (i = 0; i < graph.scrnx * graph.scrny; i++)
*(graph.vram + i) = 0x34;
}
上面基本已經實現了圖像顯示基本平臺。如今補充一些經常使用的圖像庫。
#define PIXEL(x, y) *(graph.vram + x + (y * graph.scrnx))
int draw_screen(uint8_t color)
{
int i;
for (i = 0; i < graph.scrnx * graph.scrny; i++)
*(graph.vram + i) = color;
return 0;
}
int draw_pixel(short x, short y, uint8_t color)
{
if ((x >= graph.scrnx) || (y >= graph.scrny))
return -1;
*(graph.vram + x + (y * graph.scrnx)) = color;
return 0;
}
int draw_rect(short x, short y, short l, short w, uint8_t color)
{
int i, j;
w = (y + w) > graph.scrny ? graph.scrny : (y + w);
l = (x + l) > graph.scrnx ? graph.scrnx : (x + l);
for (j = y; j < w; j++)
for (i = x; i < l; i++)
*(graph.vram + i + j * graph.scrnx) = color;
return 0;
}
這部分也是老生常談了,板子上各類系統都實現過點陣字庫。
int draw_ascii(short x, short y, char *str, uint8_t color)
{
char *font;
int i, j, k = 0;
for (k = 0; str[k] != 0; k++)
{
font = (char *)(ascii_8_16 + (str[k] - 0x20) * 16);
for (i = 0; i < 16; i++)
for (j = 0; j < 8; j++)
if ((font[i] << j) & 0x80)
PIXEL((x + j), (y + i)) = color;
x += 8;
}
return k;
}
int draw_cn(short x, short y, char *str, uint8_t color)
{
uint16_t font;
int i, j, k;
int offset;
for (k = 0; str[k] != 0; k += 2)
{
offset = ((char)(str[k] - 0xa0 - 1) * 94 +
((char)(str[k + 1] - 0xa0) - 1)) *
32;
for (i = 0; i < 16; i++)
{
font = cn_lib[offset + i * 2] << 8 |
cn_lib[offset + i * 2 + 1];
for (j = 0; j < 16; j++)
if ((font << j) & 0x8000)
PIXEL((x + j), (y + i)) = color;
}
x += 16;
}
return 0;
}
直接把以前單片機的點陣字庫拿過來,不過單片機當時開發環境是win
,找的字庫尋址模式是GB2312
的。這裏把這個文件的編碼改成GB2312
來正確編碼中文便可。實現效果見文章頭。
實際上,直接對顯存寫是很不負責任的行爲。很早以前在寫java
的界面的時候,就接觸了雙緩衝技術,其實與顯示有關的思想都是差很少的,咱們應該提供一個framebuffer
。當完成一個frame
後,再將這個frame
update到顯存中。
uint8_t *framebuffer;
void init_framebuffer(){
if((framebuffer = (uint8_t *) kmalloc((size_t)(graph.scrnx*graph.scrny)))== NULL)
panic("Not enough memory for framebuffer!");
}
void update_screen(){
memcpy(graph.vram,framebuffer,graph.scrnx*graph.scrny);
}
通過實現kmalloc
與kfree
,已經能夠分配這個緩衝區,並直接向緩衝區寫入,最後再進行update
#define PIXEL(x, y) *(framebuffer + x + (y * graph.scrnx))
int draw_xx()
{
xxx;
update_screen();
}
至此,基本的GUI
底層接口已基本實現,後面的就是各類數據結構的設計,窗口樹設計之類。這裏暫不打算繼續深究,轉而研究其他內核的東西。