Operating Systems : simple Operating System from scratch (6) — How to print log

吳建興
9 min readMay 22, 2021

--

Hello!

上一篇我們複習了一下怎麼從 Virtual Memory 轉換到 Physical Memory ,之後我們要開始想辦法要顯示日誌 ( log ) 了。能夠顯示 log 後, debug 的速度能大大的加快,覺的可疑的地方就將值給印出來就好了。這一篇來紀錄下,要怎麼從零開始顯示簡單的訊息。

在螢幕上顯示簡易的色條

這一篇有講述了簡單的原理,那便是我們從 FrameBuffer 的基底位址開始,每 4 bytes 代表一個 pixel。

所以其實我們只要一個個 pixel 慢慢塗色,很簡單的就可以顯示出世界上所有的文字了!但是要自己手刻,把一個個 pixel 塗上去,也未免太過疲累了。

幸好有大神準備好了文字檔,這樣我們就不須要自己一個個字刻出來了!

如何顯示一個字元 -- putchar

那我們有了文字檔之後,該怎麼顯示一個字元呢?讓我們以字元 '0' 為例子。只要我們將想塗色的部份設為 1 , 不塗色的部份設為 0 ,我們就可以用一個位元陣列 ( bit array ) 來表示一個字元。

以 0 為例,0 在 ascii 中會是第 48 個字元 ( 0-indexed ),所以在 font.h 的二維陣列 : "font_ascii",第 48 個元素會是

unsigned char font_ascii[256][16]={
.
.
.{0x00,0x18,0x24,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x24,0x18,0x00,0x00}, //48 '0'
.
.
.
.
}

那這個陣列的值是怎麼得來的呢?可以看下面簡單的圖示,就可以清楚的知道這個陣列的意義。

每一個字元的寬為 8 bits, 高為 16 bits ,所以想儲存一個字元,就需要 8 * 16 = 128 bits = 16 bytes。

好了,我們現在有一個用以表示字元的陣列了,我們就可以開始著手繪製出這個 '0'

/*
input :
fb --> FrameBuffer 的基底位址
Xsize --> 解析度中的寬度,在我們的例子為 1024,因為我們的解析度是 1024 * 768
x --> x 座標,範圍在 0 ~ 1023 之間
y --> y 座標,範圍在 0 ~ 767 之間
FRcolor --> 想為字元塗上的顏色
BKcolor --> 想為背景塗上的顏色
font --> ASCII 碼, 表示想顯示哪一個字元
output : N/A
*/
void putchar(unsigned int * fb,int Xsize,int x,int y,unsigned int FRcolor,unsigned int BKcolor,unsigned char font)
{
int i = 0,j = 0;
unsigned int * addr = NULL;
unsigned char * fontp = NULL;
int testval = 0;
fontp = font_ascii[font];
for(i = 0; i< 16;i++)
{
// 每個 *fontp 為 1 bytes, 8 bits
// 在這邊會去測試這 8 個 bit
// 1 --> 塗上字元顏色
// 0 --> 塗上背景顏色
addr = fb + Xsize * ( y + i ) + x;
testval = 0x100;
for(j = 0;j < 8;j ++)
{
testval = testval >> 1;
if(*fontp & testval)
*addr = FRcolor;
else
*addr = BKcolor;
// 每個 *addr 為 4 bytes
// 因為每個 pixel 為 4 bytes
addr++;
}
fontp++;
}
}

好的,接下來就可以來試用看看這個簡單的 function 了!
完整的程式碼請參考 這裡

       /*         初始化螢幕顯示所需要的資訊       */
/* 螢幕解析度 */
Pos.XResolution = 1024;
Pos.YResolution = 768;
/* 初始化顯示字元的位置 */
Pos.XPosition = 0;
Pos.YPosition = 0;
/* XCharSize -- 字元的高度 */
/* YCharSize -- 字元的寬度 */
Pos.XCharSize = 8;
Pos.YCharSize = 16;
/* 初始化 FrameBuffer 的起始 "虛擬位址" */
Pos.FB_addr = (int *)0xffff800003000000;
/* FrameBuffer 的長度 ... 解析度 * 4 bytes ( 一個 pixel 用 4 bytes 表示 ) 並對齊一個 page 的大小 */
Pos.FB_length = (Pos.XResolution * Pos.YResolution * 4 + PAGE_4K_SIZE - 1) & PAGE_4K_MASK;
/* 顯示 '0' */
putchar(Pos.FB_addr, Pos.XResolution, 0, 0, WHITE, BLACK, 48);
while(1)
;

接下來可以看一下效果:

雖然很小,但總還算是把一個字元給印出來了。。。

現在只需要給我們 ASCII 字碼,就可以印出相對應的字元,所以接下來我們就可以開始實做功能齊全的格式化字串 ( format string ) 了,下面僅做介紹性的簡單介紹。

實做格式化字串 --- color_printk

/*
FRcolor : 字元的顏色
BKcolor : 背景的顏色
fmt : 格式化字串 e.g. "abc : %d, %c"
... : 附在格式化字串後面可變長度參數 ( Variable Arguments )。 e.g. : printf("abc : %d, %c", 123, 'a'), "abc : %d, %c" 便是 "格式化字串",而 123, 'a' 就是可變長度參數。
*/
int color_printk(unsigned int FRcolor,unsigned int BKcolor,const char * fmt,...)
{
int i = 0;
int count = 0;
int line = 0;
va_list args;
va_start(args, fmt);
/*
將格式化字串 ( format string ) 以及可變長度參數 ( variable arguments ),組成一個完整的字串。
e.g. "abc : %d, %c", 123, 'a'
--> "abc : 123, a"
*/
i = vsprintf(buf,fmt, args);
va_end(args);/*
我們有了完整的,以 ASCII 表示的字串後,就可以一個個用 putchar 將它們給顯示出來了!
*/
for(count = 0;count < i || line;count++)
{
//// add \n \b \t
if(line > 0)
{
count--;
goto Label_tab;
}
if((unsigned char)*(buf + count) == '\n')
{
Pos.YPosition++;
Pos.XPosition = 0;
}
.
.
.
.

實做格式化字串 — — vsprintf

這個 function 會遍尋格式化字串,只要當中存在著 '%' 這個特殊的字元,就會針對這個特殊字元後的規格,來做相對應的處理。

e.g. % 後面是 d --> 有號整數
e.g. % 後面是 u --> 無號整數
e.g. % 後面是 10u --> 用 10 的字元的寬度來顯示這個無號整數

int vsprintf(char * buf,const char *fmt, va_list args)
{
// 把 fmt ( format string ) 跟 args 湊在一起,組成 buf
char * str,*s;
int flags;
int field_width;
int precision;
int len,i;
int qualifier; /* 'h', 'l', 'L' or 'Z' for integer fields */ for(str = buf; *fmt; fmt++)
{
if(*fmt != '%')
{
*str++ = *fmt;
continue;
}
flags = 0;
repeat:
fmt++;
switch(*fmt)
{
case '-':flags |= LEFT;
goto repeat;
case '+':flags |= PLUS;
goto repeat;
.
.
.
.
.

Hello World!

那現在我們來顯示 Hello World 吧!

        /*          初始化螢幕顯示所需要的資訊        */
/* 螢幕解析度 */
Pos.XResolution = 1024;
Pos.YResolution = 768;
/* 初始化顯示字元的位置 */
Pos.XPosition = 0;
Pos.YPosition = 0;
/* XCharSize -- 字元的高度 */
/* YCharSize -- 字元的寬度 */
Pos.XCharSize = 8;
Pos.YCharSize = 16;
/* 初始化 FrameBuffer 的起始 "虛擬位址" */
Pos.FB_addr = (int *)0xffff800003000000;
/* FrameBuffer 的長度 ... 解析度 * 4 bytes ( 一個 pixel 用 4 bytes 表示 ) 並對齊一個 page 的大小 */
Pos.FB_length = (Pos.XResolution * Pos.YResolution * 4 + PAGE_4K_SIZE - 1) & PAGE_4K_MASK;
color_printk(WHITE, BLACK,"Hello World %s!\n", "fdgkhdkgh");

可以看到,我們的格式化字串也有正常的運作!

到此為止,我們成功在作業系統還沒完全地運行起來的情況下,也能顯示 log。這幫助我們在接下來的例外處理中,可以在我們出現某些 bug 的時候,顯示相對應的訊息。

結語

雖然介紹的很簡略,不過還是粗淺的理解了要如何顯示簡單的字串。接下來會開始看要如何進行例外處理,方便在接下來的過程中進行除錯。

Reference

  1. 一個64位操作系統的設計與實現
  2. draw.io

--

--

吳建興
吳建興

Written by 吳建興

I want to be a good programmer.(´・ω・`)

No responses yet