1. 程式人生 > >30天自制作業系統學習-第7天

30天自制作業系統學習-第7天

1 獲取按鍵編碼

如何讓使用者輸入的鍵盤按鍵轉換為對於的字元,只需使用匯編呼叫bios中斷即可實現,我們在naskfuc.nas中編寫好的大量in out介面嘗試呼叫,修改後的int.c中inthandler函式:

#define PORT_KEYDAT		0x0060

void inthandler21(int *esp)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	unsigned char data, s[4];
	io_out8(PIC0_OCW2, 0x61);	/* 通知IRQ-01已經處理完畢 */
	data = io_in8(PORT_KEYDAT);

	sprintf(s, "%02X", data);
	boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);

	return;
}

我們往鍵盤上按下一個鍵的時候,鍵盤外設會向CPU傳送一個外中斷,並將按鍵的區位碼傳送,我們使用io_in8介面接收返回的緩衝區資料,執行:

按下一個按鍵,比如a:

我們暫時讓按鍵對應的編碼輸出了,以後我們再將鍵盤編碼轉換為對應的字元。

2 製作FIFO(First In First Out)緩衝區

我們在bookpack.h標頭檔案中宣告一個名為KEYBUF的緩衝區,用於存放更多的字元編碼:

/* int.c */
struct KEYBUF {
	unsigned char data[32];
	int next;
};

修改一下int.c中的inthandler函式,使其能不斷的讀取鍵盤碼,其中next為陣列指標:

struct KEYBUF keybuf;

void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	/* 通知IRQ-01已經處理完畢 */
	data = io_in8(PORT_KEYDAT);
	if (keybuf.next < 32) {
		keybuf.data[keybuf.next] = data;
		keybuf.next++;
	}
	return;
}

 bookpack.c中主函式HariMain尾部新增讀取緩衝區鍵盤碼程式碼:

/* bootpack主函式 */

#include "bootpack.h"
#include <stdio.h>

extern struct KEYBUF keybuf;

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	char s[40], mcursor[256];
	int mx, my, i, j;

	init_gdtidt();
	init_pic();
	io_sti(); /* IDT/PIC的初始化結束後解除CPU的中斷禁止 */

	io_out8(PIC0_IMR, 0xf9); /* PIC1允許鍵盤(11111001) */
	io_out8(PIC1_IMR, 0xef); /* 允許滑鼠(11101111) */

	init_palette();
	init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
	mx = (binfo->scrnx - 16) / 2; /*以成為畫面中央的座標計算 */
	my = (binfo->scrny - 28 - 16) / 2;
	init_mouse_cursor8(mcursor, COL8_008484);
	putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
	sprintf(s, "(%d, %d)", mx, my);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);

	for (;;) {
		io_cli();
		if (keybuf.next == 0) {
			io_stihlt();
		} else {//如果資料不為0,讀取第一個資料
			i = keybuf.data[0];
			keybuf.next--;
			for (j = 0; j < keybuf.next; j++) {
				keybuf.data[j] = keybuf.data[j + 1];
			}
			io_sti();
			sprintf(s, "%02X", i);
			boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
			putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
		}
	}
}

這樣當鍵盤連續輸入的時候,會不斷的重新整理顯示的按鍵編碼。

3 改進緩衝區的讀取

上面緩衝區的設計的弊端就是不能儲存大量的資料,每次只能讀取一個數據。並且每次讀取都需要進行一次一位操作,時間複雜度是O(N),我們嘗試設計一個O(1)複雜度的讀取操作,並且使它能讀取更多的資料,修改後bookpack.h中的KEYBUF結構體:

/* int.c */
struct KEYBUF {
	unsigned char data[32];
	int next_r, next_w, len;
};

其中定義的data存放資料,下面的三個指標next_r是資料讀取指標,next_w是資料寫入指標,len是緩衝區已有資料個數。

修改後int.c的inthandler函式對鍵盤編碼的寫入:

struct KEYBUF keybuf;

void inthandler21(int *esp)
{
	unsigned char data;
	io_out8(PIC0_OCW2, 0x61);	/* IRQ-01向PIC通知完成受理 */
	data = io_in8(PORT_KEYDAT);
	if (keybuf.len < 32) {//小於緩衝區長度寫入資料
		keybuf.data[keybuf.next_w] = data;
		keybuf.len++;
		keybuf.next_w++;
		if (keybuf.next_w == 32) {//越界時重置
			keybuf.next_w = 0;
		}
	}
	return;
}

bookpack.c的HariMain主函式對資料的讀取:

for (;;) {
		io_cli();
		if (keybuf.len == 0) {
			io_stihlt();
		} else {//有資料時
			i = keybuf.data[keybuf.next_r];
			keybuf.len--;//讀取後更新資料規模
			keybuf.next_r++;//記錄讀取位置
			if (keybuf.next_r == 32) {//越界時重置
				keybuf.next_r = 0;
			}
			io_sti();
			sprintf(s, "%02X", i);
			boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
			putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
		}
	}

 這樣就不需要頻換的進行移位操作。

4 整理緩衝區

我們優化後的緩衝區只能讀取32個位元組,這樣後面用到更多讀寫操作時又需要頻換修改,我們將緩衝區的讀寫獨立為一個fifo.c函式。

修改後的結構體:

/* fifo.c */
struct FIFO8 {
	unsigned char *buf;
	int p, q, size, free, flags;/*p為下一個寫入地址,q為下一個讀出地址,free為當前已有資料數量,flags為溢位標誌*/
};

新增的fifo.c檔案:

/* FIFO庫 */

#include "bootpack.h"

#define FLAGS_OVERRUN		0x0001

void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
/* FIFO結構體初始化函式 */
{
	fifo->size = size;
	fifo->buf = buf;
	fifo->free = size; /* 緩衝區大小 */
	fifo->flags = 0;
	fifo->p = 0; /* 下一個資料寫入位置 */
	fifo->q = 0; /* 下一個資料讀出位置*/
	return;
}

int fifo8_put(struct FIFO8 *fifo, unsigned char data)
/* 向FIFO傳送資料並儲存 */
{
	if (fifo->free == 0) {
		/* 空餘沒有了,溢位 */
		fifo->flags |= FLAGS_OVERRUN;
		return -1;
	}
	fifo->buf[fifo->p] = data;
	fifo->p++;
	if (fifo->p == fifo->size) {//越界時重置
		fifo->p = 0;
	}
	fifo->free--;
	return 0;
}

int fifo8_get(struct FIFO8 *fifo)
/* 從FIFO取得一個數據 */
{
	int data;
	if (fifo->free == fifo->size) {
		/* 如果緩衝區為空,則返回-1 */
		return -1;
	}
	data = fifo->buf[fifo->q];
	fifo->q++;
	if (fifo->q == fifo->size) {//越界時重置
		fifo->q = 0;
	}
	fifo->free++;
	return data;
}

int fifo8_status(struct FIFO8 *fifo)
/* 報告一共儲存了多少資料 */
{
	return fifo->size - fifo->free;
}

此時在bookpack.c的HariMain主函式中:

/* bootpack主函式 */

#include "bootpack.h"
#include <stdio.h>

extern struct FIFO8 keyfifo;

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	char s[40], mcursor[256], keybuf[32];
	int mx, my, i;

	init_gdtidt();
	init_pic();
	io_sti(); /* IDT/PIC的初始化結束後解除CPU的中斷禁止 */

	fifo8_init(&keyfifo, 32, keybuf);//初始化一個32位元組的緩衝區
	io_out8(PIC0_IMR, 0xf9); /* 允許PIC1和鍵盤(11111001) */
	io_out8(PIC1_IMR, 0xef); /* 允許滑鼠(11101111) */

	init_palette();
	init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
	mx = (binfo->scrnx - 16) / 2; /* 以成為畫面中央的座標計算 */
	my = (binfo->scrny - 28 - 16) / 2;
	init_mouse_cursor8(mcursor, COL8_008484);
	putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
	sprintf(s, "(%d, %d)", mx, my);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);

	for (;;) {
		io_cli();
		if (fifo8_status(&keyfifo) == 0) {//檢查是否存在資料
			io_stihlt();
		} else {
			i = fifo8_get(&keyfifo);//存在資料時讀取一個位元組
			io_sti();
			sprintf(s, "%02X", i);
			boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
			putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
		}
	}
}

我們使用make run命令執行,按下鍵盤上任意鍵:

沒有問題。

5 滑鼠的中斷處理

對滑鼠的中斷處理跟鍵盤大致差不多,對於滑鼠我們將緩衝區位元組大小修改為128位元組,修改後的bookpack.c:

/* bootpack主函式 */

#include "bootpack.h"
#include <stdio.h>

extern struct FIFO8 keyfifo, mousefifo;
void enable_mouse(void);
void init_keyboard(void);

void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	char s[40], mcursor[256], keybuf[32], mousebuf[128];
	int mx, my, i;

	init_gdtidt();
	init_pic();
	io_sti(); /* IDT/PICの因為初始化結束了,所以解除了CPU的中斷禁止 */
	fifo8_init(&keyfifo, 32, keybuf);/*鍵盤輸入緩衝區*/
	fifo8_init(&mousefifo, 128, mousebuf);/*滑鼠輸入緩衝區*/
	io_out8(PIC0_IMR, 0xf9); /* PIC1允許鍵盤(11111001) */
	io_out8(PIC1_IMR, 0xef);

	init_keyboard();

	init_palette();
	init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
	mx = (binfo->scrnx - 16) / 2; /* 以成為畫面中央的座標計算 */
	my = (binfo->scrny - 28 - 16) / 2;
	init_mouse_cursor8(mcursor, COL8_008484);
	putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
	sprintf(s, "(%d, %d)", mx, my);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);

	enable_mouse();

	for (;;) {
		io_cli();
		if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {//鍵盤滑鼠都沒有輸入
			io_stihlt();
		} else {
			if (fifo8_status(&keyfifo) != 0) {//鍵盤輸入
				i = fifo8_get(&keyfifo);
				io_sti();
				sprintf(s, "%02X", i);
				boxfill8(binfo->vram, binfo->scrnx, COL8_008484,  0, 16, 15, 31);
				putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
			} else if (fifo8_status(&mousefifo) != 0) {//滑鼠輸入
				i = fifo8_get(&mousefifo);
				io_sti();
				sprintf(s, "%02X", i);
				boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 47, 31);
				putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
			}
		}
	}
}

#define PORT_KEYDAT				0x0060
#define PORT_KEYSTA				0x0064
#define PORT_KEYCMD				0x0064
#define KEYSTA_SEND_NOTREADY	0x02
#define KEYCMD_WRITE_MODE		0x60
#define KBC_MODE				0x47

void wait_KBC_sendready(void)
{
	/* 等待鍵盤控制器可以傳送資料 */
	for (;;) {
		if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) {
			break;
		}
	}
	return;
}

void init_keyboard(void)
{
	/* 鍵盤控制器初始化 */
	wait_KBC_sendready();
	io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
	wait_KBC_sendready();
	io_out8(PORT_KEYDAT, KBC_MODE);
	return;
}

#define KEYCMD_SENDTO_MOUSE		0xd4
#define MOUSECMD_ENABLE			0xf4

void enable_mouse(void)
{
	/* 啟用滑鼠 */
	wait_KBC_sendready();
	io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
	wait_KBC_sendready();
	io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
	return; /* 順利的話,鍵盤控制其會返送ACK(0xfa) */
}

這樣鍵盤滑鼠的中斷我們都處理了,現在嘗試執行一下:

我們移動一下滑鼠:

今天的內容大致就這麼多。