Bran的內核開發指南(4)
導讀:
現在我們將試着在屏幕上顯示點東西。爲此,我們需要一種管理屏幕滾動的方法。同時,能在屏幕上顯示不同的顏色也是一件美妙的事情。 幸運的是,VGA顯卡使這很容易實現:爲了在顯示器上顯示內容,顯卡給定了一個內存塊,我們只需向內存中寫入字符和屬性對。 VGA控制器會自動地把更新的內容畫到屏幕上。滾動屏幕是由我們的內核軟件來維護的。從技術上說,這是我們的第一個驅動程序,現在我們就開始編寫。
如上面所提到的,字符緩存只是在我們地址空間中的一塊內存。這塊緩存在0xB8000的物理內存位置上。 緩存的類型爲“short”,這意味着緩存中的每一項內容都是由16位組成的,而不是我們通常認爲的8位。 緩存中的每一個16位元素,都可以被分爲“高8位”和“低8位”。低8位代表需要顯示的字符。高8位通常定義了這個字符的前景色和背景色。
15 12 11 8 7 0
背景色 前景色 字符
16位中的高8位被稱爲“屬性位”,低8位被稱爲“字符位”。正如你在上面的表格中看到的,每一個16位元素中,屬性位又被分爲2個4位的塊:一個代表背景色,另一個代表前景色。 現在因爲只用4位來表示顏色的原因,最多隻可能有16種不同的顏色可供選擇,(使用公式:(位數 ^ 2) - 4^2 = 16 )。以下是16種顏色表。
值 顏色 值 顏色
0 黑 8 深灰
1 藍 9 淡藍
2 綠 10 淡綠
3 青綠 11 淡青綠
4 紅 12 淡紅
5 品紅 13 淡品紅
6 棕 14 淡棕
7 淡灰 15 白
最後,爲了能處理內存中特定的索引內容,我們需要使用有一個公式。 字符型的內存是一個簡單的“線性”(或平坦)的內存區域,但是顯示控制器使它看起來像一個80x25的16位矩陣。 在內存中,文字的每一行都是相等的;前後相互連接。 因此我們試着把屏幕變爲平行的線。完成這個過程的最好方法是用公式:
index = (y_value * width_of_screen) + x_value;
如果我們要控制(3,4)位置上的字符,使用這個公式,就得到 4 * 80 + 3 = 323。 也就是說,在屏幕(3,4)位置上操作,就等同於如下操作:
unsigned short *where = (unsigned short *)0xB8000 + 323;
*where = character | (attribute <<8);
以下內容是'scrn.c'文件,這個文件中包含了我們處理屏幕顯示時要用到的函數。 我們include了'system.h'文件,這樣我們就能使用outportb,memcpy,memset,memsets和strlen了。 我們使用的滾動屏幕功能是十分有趣的: 我們從第1行開始操作字符緩存(而不是第0行),然後把它複製到第0行上去,實際上就是把整個屏幕向上移動了一行。最後,我們用一行帶有屬性的空格寫滿最後一行。 這個文件中的putch函數可能是最複雜的一個了,同樣也是最大的一個。 因爲它需要處理換行("/n"),回車("/r")和退格("/b")。 如果你想要的話,你可以接着處理警告字符("/a" - ASCII 7), 處理時應該會發出一聲短beep。 如果你需要的話,我已經編寫了settextcolor函數來設置字符的顏色。
#include ="">
/* 這些內容定義了我們的文字指針,背景和前景顏色(屬性),和xy座標。 */
unsigned short *textmemptr;
int attrib = 0x0F;
int csr_x = 0, csr_y = 0;
/* 滾動屏幕 */
void scroll(void)
{
unsigned blank, temp;
/* 把空格定義爲空白字符...我們也要設置他的背景顏色 */
blank = 0x20 | (attrib <<8);
/* 25行是結尾,也就是說,我們要把它滾動上去 */
if(csr_y >= 25)
{
/* 把當前的字符塊向上移動一行 */
temp = csr_y - 25 + 1;
memcpy (textmemptr, textmemptr + temp * 80, (25 - temp) * 80 * 2);
/* 最後,我們把最後一行設置爲我們定義的空白字符。 */
memsetw (textmemptr + (25 - temp) * 80, blank, 80);
csr_y = 25 - 1;
}
}
/* 更新硬件光標: 在輸入的字符之後的那一行上顯示一個閃爍。 */
void move_csr(void)
{
unsigned temp;
/* 在線性的內存塊中找到索引的公式。表示爲:
* Index = [(y * width) + x] */
temp = csr_y * 80 + csr_x;
/* 向VGA控制器的CRT控制寄存器發送14和15標誌。
* 他們是索引字符的高位和低位,這個字符顯示在硬件光標的閃爍處。
* 想知道更多細節,你可以查看VGA規範的編程文檔。一個相當好的文檔在
* http://www.brackeen.com/home/vga */
outportb(0x3D4, 14);
outportb(0x3D5, temp >> 8);
outportb(0x3D4, 15);
outportb(0x3D5, temp);
}
/* 清空屏幕 */
void cls()
{
unsigned blank;
int i;
/* 同樣的,我們需要一個用來做空白的'short'顏色 */
blank = 0x20 | (attrib <<8);
/* 用帶有空白顏色的空白畫滿整個屏幕 */
for(i = 0; i <25; i++)
memsetw (textmemptr + i * 80, blank, 80);
/* 更新我們的虛擬光標,然後移動物理光標 */
csr_x = 0;
csr_y = 0;
move_csr();
}
/* 在屏幕上顯示單個字符 */
void putch(unsigned char c)
{
unsigned short *where;
unsigned att = attrib <<8;
/* 處理退格時,把光標向前一動一格 */
if(c == 0x08)
{
if(csr_x != 0) csr_x--;
}
/* 處理一個跳格時,增加光標的x值,但只移動到可以被8整除的位置上。 */
else if(c == 0x09)
{
csr_x = (csr_x + 8) &~(8 - 1);
}
/* 處理一個回車, 直接把光標放回到最前面 */
else if(c == '/r')
{
csr_x = 0;
}
/* 我們用DOS和BIOS的方法來處理新換行:我們把CR看作和換行一同出現。
* 我們把x放到最前面,然後增加y值 */
else if(c == '/n')
{
csr_x = 0;
csr_y++;
}
/* 任何比空格大的字符都是可以被打印出來的,包括空格本身。
* 用來從線性的內存快中找出索引的公式表示爲:
* Index = [(y * width) + x] */
else if(c >= ' ')
{
where = textmemptr + (csr_y * 80 + csr_x);
*where = c | att; /* 字符 AND 屬性: 顏色 */
csr_x++;
}
/* 如果光標到達了屏幕寬度的邊緣,我們就插入另一行 */
if(csr_x >= 80)
{
csr_x = 0;
csr_y++;
}
/* 如果需要的話,滾動屏幕,並移動光標 */
scroll();
move_csr();
}
/* 使用以上的方法,輸出一個字符串 */
void puts(unsigned char *text)
{
int i;
for (i = 0; i
現在我們將試着在屏幕上顯示點東西。爲此,我們需要一種管理屏幕滾動的方法。同時,能在屏幕上顯示不同的顏色也是一件美妙的事情。 幸運的是,VGA顯卡使這很容易實現:爲了在顯示器上顯示內容,顯卡給定了一個內存塊,我們只需向內存中寫入字符和屬性對。 VGA控制器會自動地把更新的內容畫到屏幕上。滾動屏幕是由我們的內核軟件來維護的。從技術上說,這是我們的第一個驅動程序,現在我們就開始編寫。
如上面所提到的,字符緩存只是在我們地址空間中的一塊內存。這塊緩存在0xB8000的物理內存位置上。 緩存的類型爲“short”,這意味着緩存中的每一項內容都是由16位組成的,而不是我們通常認爲的8位。 緩存中的每一個16位元素,都可以被分爲“高8位”和“低8位”。低8位代表需要顯示的字符。高8位通常定義了這個字符的前景色和背景色。
15 12 11 8 7 0
背景色 前景色 字符
16位中的高8位被稱爲“屬性位”,低8位被稱爲“字符位”。正如你在上面的表格中看到的,每一個16位元素中,屬性位又被分爲2個4位的塊:一個代表背景色,另一個代表前景色。 現在因爲只用4位來表示顏色的原因,最多隻可能有16種不同的顏色可供選擇,(使用公式:(位數 ^ 2) - 4^2 = 16 )。以下是16種顏色表。
值 顏色 值 顏色
0 黑 8 深灰
1 藍 9 淡藍
2 綠 10 淡綠
3 青綠 11 淡青綠
4 紅 12 淡紅
5 品紅 13 淡品紅
6 棕 14 淡棕
7 淡灰 15 白
最後,爲了能處理內存中特定的索引內容,我們需要使用有一個公式。 字符型的內存是一個簡單的“線性”(或平坦)的內存區域,但是顯示控制器使它看起來像一個80x25的16位矩陣。 在內存中,文字的每一行都是相等的;前後相互連接。 因此我們試着把屏幕變爲平行的線。完成這個過程的最好方法是用公式:
index = (y_value * width_of_screen) + x_value;
如果我們要控制(3,4)位置上的字符,使用這個公式,就得到 4 * 80 + 3 = 323。 也就是說,在屏幕(3,4)位置上操作,就等同於如下操作:
unsigned short *where = (unsigned short *)0xB8000 + 323;
*where = character | (attribute <<8);
以下內容是'scrn.c'文件,這個文件中包含了我們處理屏幕顯示時要用到的函數。 我們include了'system.h'文件,這樣我們就能使用outportb,memcpy,memset,memsets和strlen了。 我們使用的滾動屏幕功能是十分有趣的: 我們從第1行開始操作字符緩存(而不是第0行),然後把它複製到第0行上去,實際上就是把整個屏幕向上移動了一行。最後,我們用一行帶有屬性的空格寫滿最後一行。 這個文件中的putch函數可能是最複雜的一個了,同樣也是最大的一個。 因爲它需要處理換行("/n"),回車("/r")和退格("/b")。 如果你想要的話,你可以接着處理警告字符("/a" - ASCII 7), 處理時應該會發出一聲短beep。 如果你需要的話,我已經編寫了settextcolor函數來設置字符的顏色。
#include ="">
/* 這些內容定義了我們的文字指針,背景和前景顏色(屬性),和xy座標。 */
unsigned short *textmemptr;
int attrib = 0x0F;
int csr_x = 0, csr_y = 0;
/* 滾動屏幕 */
void scroll(void)
{
unsigned blank, temp;
/* 把空格定義爲空白字符...我們也要設置他的背景顏色 */
blank = 0x20 | (attrib <<8);
/* 25行是結尾,也就是說,我們要把它滾動上去 */
if(csr_y >= 25)
{
/* 把當前的字符塊向上移動一行 */
temp = csr_y - 25 + 1;
memcpy (textmemptr, textmemptr + temp * 80, (25 - temp) * 80 * 2);
/* 最後,我們把最後一行設置爲我們定義的空白字符。 */
memsetw (textmemptr + (25 - temp) * 80, blank, 80);
csr_y = 25 - 1;
}
}
/* 更新硬件光標: 在輸入的字符之後的那一行上顯示一個閃爍。 */
void move_csr(void)
{
unsigned temp;
/* 在線性的內存塊中找到索引的公式。表示爲:
* Index = [(y * width) + x] */
temp = csr_y * 80 + csr_x;
/* 向VGA控制器的CRT控制寄存器發送14和15標誌。
* 他們是索引字符的高位和低位,這個字符顯示在硬件光標的閃爍處。
* 想知道更多細節,你可以查看VGA規範的編程文檔。一個相當好的文檔在
* http://www.brackeen.com/home/vga */
outportb(0x3D4, 14);
outportb(0x3D5, temp >> 8);
outportb(0x3D4, 15);
outportb(0x3D5, temp);
}
/* 清空屏幕 */
void cls()
{
unsigned blank;
int i;
/* 同樣的,我們需要一個用來做空白的'short'顏色 */
blank = 0x20 | (attrib <<8);
/* 用帶有空白顏色的空白畫滿整個屏幕 */
for(i = 0; i <25; i++)
memsetw (textmemptr + i * 80, blank, 80);
/* 更新我們的虛擬光標,然後移動物理光標 */
csr_x = 0;
csr_y = 0;
move_csr();
}
/* 在屏幕上顯示單個字符 */
void putch(unsigned char c)
{
unsigned short *where;
unsigned att = attrib <<8;
/* 處理退格時,把光標向前一動一格 */
if(c == 0x08)
{
if(csr_x != 0) csr_x--;
}
/* 處理一個跳格時,增加光標的x值,但只移動到可以被8整除的位置上。 */
else if(c == 0x09)
{
csr_x = (csr_x + 8) &~(8 - 1);
}
/* 處理一個回車, 直接把光標放回到最前面 */
else if(c == '/r')
{
csr_x = 0;
}
/* 我們用DOS和BIOS的方法來處理新換行:我們把CR看作和換行一同出現。
* 我們把x放到最前面,然後增加y值 */
else if(c == '/n')
{
csr_x = 0;
csr_y++;
}
/* 任何比空格大的字符都是可以被打印出來的,包括空格本身。
* 用來從線性的內存快中找出索引的公式表示爲:
* Index = [(y * width) + x] */
else if(c >= ' ')
{
where = textmemptr + (csr_y * 80 + csr_x);
*where = c | att; /* 字符 AND 屬性: 顏色 */
csr_x++;
}
/* 如果光標到達了屏幕寬度的邊緣,我們就插入另一行 */
if(csr_x >= 80)
{
csr_x = 0;
csr_y++;
}
/* 如果需要的話,滾動屏幕,並移動光標 */
scroll();
move_csr();
}
/* 使用以上的方法,輸出一個字符串 */
void puts(unsigned char *text)
{
int i;
for (i = 0; i
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.