從零實現一個操作系統-day6

我的博客startcraft

昨天寫的內核在屏幕上沒有我們輸出的東西,今天就來想辦法顯示點什麼

文字的顯示

要顯示東西就涉及到顯卡了,顯卡有兩種模式,文本模式和圖形模式,現在基本都是圖形模式用得多,但是我們這個就用文本模式了,畢竟不涉及ui啥的

文本的顯示規則

顯卡通電後就自動初始化了80\ * 25分辨率的文本模式,即一屏25行,一行80個字符
之前說過內存地址空間不是全部映射到主存的,有一部分映射到外部設備,0xB8000~0xBFFFF就是映射到顯卡文本模式的顯存的地址空間

從0xB8000開始,每兩個字節對應屏幕上的一個字符,從第一行開始,字符在內存中的存儲形式叫內碼,第一個字符對應字符的ASCII碼,第二個控制字符的顏色等等信息,每一位都有不同的含義,如下圖: (圖片來自教程)


除了顯存之外,顯卡還有一些控制單元,這些單元沒有映射在cpu尋址的4GB空間裏,需要使用in / out命令來讀寫,這部分寄存器的訪問規則是:

  • 通過0x3D4端口來設置寄存器索引,就是要訪問哪一個寄存器
  • 通過0x3D5端口來設置寄存器的值

端口讀寫函數

這些要用in / out命令訪問的寄存器用c的代碼顯然是不行的,只能用匯編實現,這裏選擇在c裏面內嵌彙編代碼參考彙編語言和C語言混合編程和內聯彙編

libs/common.c

#include "common.h"
//往端口寫一個字節
        inline void outb (uint16_t port, uint8_t value)
{
	asm volatile ("outb %1,%0"::"dN"(port), "a"(value));
}

//從端口讀一個字節
inline uint8_t inb (uint16_t port)
{
	uint8_t ret;
	asm volatile ("inb %1,%0":"=a"(ret):"dN"(port));
	return ret;
}

//從端口讀一個字
inline uint16_t inw (uint16_t port)
{
	uint16_t ret;
	asm volatile ("inw %1,%0":"=a"(ret):"dN"(port));
	return ret;
}

include/common.h

#ifndef INCLUDE_COMMON_H_
#define INCLUDE_COMMON_H_
#include "types.h"
//往端口寫一個字節
void outb (uint16_t port, uint8_t value);

//從端口讀一個字節
uint8_t inb (uint16_t port);

//從端口讀一個字
uint16_t intw (uint16_t port);

#endif

頭文件前面的那個ifndef和define是爲了防止在不同的文件中include了同一個頭文件會出現的重複定義錯誤
函數前面的inline是建議編譯器把函數當成內聯函數來編譯,即在函數的調用處進行代碼展開,而不是傳統函數調用


顏色定義和屏幕操作函數

是顏色定義的枚舉和一些屏幕控制函數的聲明

include/console.h

#ifndef INCLUDE_CONSOLE_H_
#define INCLUDE_CONSOLE_H_

#include "types.h"

typedef
enum real_color {
	rc_black = 0,
	rc_blue = 1,
	rc_green = 2,
	rc_cyan = 3,
	rc_red = 4,
	rc_magenta = 5,
	rc_brown = 6,
	rc_light_grey = 7,
	rc_dark_grey = 8,
	rc_light_blue = 9,
	rc_light_green = 10,
	rc_light_cyan = 11,
	rc_light_red = 12,
	rc_light_magenta = 13,
	rc_light_brown = 14, // yellow
	rc_white = 15
} real_color_t;

// 清屏操作
void console_clear();

// 屏幕輸出一個字符帶顏色
void console_putc_color(char c, real_color_t back, real_color_t fore);

// 屏幕打印一個以 \0 結尾的字符串默認黑底白字
void console_write(char *cstr);

// 屏幕打印一個以 \0 結尾的字符串帶顏色
void console_write_color(char *cstr, real_color_t back, real_color_t fore);

// 屏幕輸出一個十六進制的整型數
void console_write_hex(uint32_t n, real_color_t back, real_color_t fore);

// 屏幕輸出一個十進制的整型數
void console_write_dec(uint32_t n, real_color_t back, real_color_t fore);

#endif // INCLUDE_CONSOLE_H_

drivers/console.c

定義一些變量,static修飾代表只在該文件內生效

static uint16_t *video_memory = (uint16_t *)0xB8000; //顯存的起始地址,每兩個字節表示一個字符所以是16位
//屏幕光標的座標
static uint8_t cursor_x = 0;
static uint8_t cursor_y = 0;

輸入光標的移動

//移動光標的位置
static void move_cursor ()
{
	uint16_t cursorLocation = cursor_y * 80 + cursor_x; //一行有80個字符
	/*控制光標位置的寄存器爲14號和15號寄存器,
	 * 分別存儲位置的高八位和低八位*/
	outb(0x3D4, 14); //要設置14號寄存器,即位置信息的高八位
	outb(0x3D5, cursorLocation >> 8);
	outb(0x3D4, 15); //要設置15號寄存器,即位置信息的低八位
	outb(0x3D5, cursorLocation);
}

清屏操作

清屏就是把所有字符用黑底白字的空格填充,然後把光標移動到最前面就行了

void console_clear()
{
	uint8_t attribute_byte = (0 << 4) | (15 & 0x0f); //黑底白字
	uint16_t blank = 0x20 | (attribute_byte << 8);
	int i;
	for (i = 0; i < 80 * 25; i++)
	{
		video_memory[i] = blank;
	}
	cursor_x = cursor_y = 0;
	move_cursor();
}

屏幕滾動

只需要將所有行內容往上移動一格,最後一行填充空格

//屏幕的滾動
static void scroll()
{
	int i;
	uint8_t attribute_byte = (0 << 4) | (15 & 0x0f); //黑底白字
	uint16_t blank = 0x20 | (attribute_byte << 8);
	//大於25行就該滾動屏幕了
	if (cursor_y >= 25)
	{
		//米一行向上移動一行
		for (i = 0; i < 24 * 80; i++)
		{
			video_memory[i] = video_memory[i + 80];
		}
		//最後一行填充空格
		for (i = 24 * 80; i < 25 * 80; i++)
			video_memory[i] = blank;
		//光標向上一行
		cursor_y--;
	}
}

顯示字符串

注意對換行符等等一些符號的處理

// 屏幕輸出一個字符帶顏色
void console_putc_color(char c, real_color_t back, real_color_t fore)
{
	//設置字體顏色
	uint8_t back_color = (uint8_t) back;
	uint8_t fore_color = (uint8_t) fore;
	uint8_t attribute_byte = (back_color << 4) | (fore_color & 0x0f);
	uint16_t attirbute = attribute_byte << 8;
	// 0x08 是退格鍵的 ASCII 碼
	// 0x09 是tab 鍵的 ASCII 碼
	if (c == 0x08 && cursor_x)
		cursor_x--;
	else if (c == 0x09)
		cursor_x = (cursor_x + 4) & ~(4 - 1); //即當前位置移動4下後的最高位的1所表示的值
	else if (c == '\r')
		cursor_x = 0;
	else if (c == '\n')
	{
		cursor_x = 0;
		cursor_y++;
	} else if (c >= ' ')
	{
		video_memory[cursor_y * 80 + cursor_x] = c | attirbute;
		cursor_x++;
	}
	//一行滿了換行
	if (cursor_x >= 80)
	{
		cursor_x = 0;
		cursor_y++;
	}
	// 如果需要的話滾動屏幕顯示
	scroll();
	// 移動硬件的輸入光標
	move_cursor();
}

// 屏幕打印一個以 \0 結尾的字符串默認黑底白字
void console_write(char *cstr)
{
	while (*cstr)
	{
		console_putc_color(*cstr++, rc_black, rc_white);
	}
}

// 屏幕打印一個以 \0 結尾的字符串帶顏色
void console_write_color(char *cstr, real_color_t back, real_color_t fore)
{
	while (*cstr)
	{
		console_putc_color(*cstr++, back, fore);
	}
}

輸出數字

做一下進制轉換當字符串輸出就行了

/ 屏幕輸出一個十六進制的整型數
void console_write_hex(uint32_t n, real_color_t back, real_color_t fore)
{
	uint8_t data[8];
	int16_t len = 0;
	while (n)
	{
		data[len++] = n % 16;
		n /= 16;
	}
	len--;
	console_putc_color('0', back, fore);
	console_putc_color('x', back, fore);
	while (len >= 0)
	{
		if (data[len] < 10)
			console_putc_color(data[len] + '0', back, fore);
		else
			console_putc_color(data[len] - 10 + 'A', back, fore);
		len--;
	}
}


// 屏幕輸出一個十進制的整型數
void console_write_dec(uint32_t n, real_color_t back, real_color_t fore)
{
	uint8_t data[10];
	int16_t len = 0;
	while (n)
	{
		data[len++] = n % 10;
		n /= 10;
	}
	len--;
	while (len >= 0)
	{
		console_putc_color(data[len] + '0', back, fore);
		len--;
	}
}

測試

修改一下init / entry.c

#include "console.h"
int kern_entry()
{
	console_clear();
	console_write_color("Hello, OS kernel!\n", rc_black, rc_green);
	console_write_dec(128, rc_black, rc_green);
	console_putc_color('\n', rc_black, rc_green);
	console_write_hex(128, rc_black, rc_green);
	return 0;
}
make
make qemu

最終的成果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章