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) */
}
這樣鍵盤滑鼠的中斷我們都處理了,現在嘗試執行一下:
我們移動一下滑鼠:
今天的內容大致就這麼多。