第7天
PIC初始化之后,再寫中斷處理函數,然后把中斷處理函數的入口地址注冊在IDT中。現在重點是中斷處理函數如何編寫。
PIC中還有一個寄存器OCW,如果鍵盤發生中斷,需要向PIC發送0X60+IRQ號碼,執行這行代碼之后PIC才會繼續監視IRQ1的中斷是否發生。OCW設置完畢后,CPU再從端口中讀入鍵盤數據,計算機的硬件規定,鍵盤數據總共8位,從0x0060端口讀入。附上中斷處理程序
void inthandler21(int *esp)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
unsigned char data, s[4];
io_out8(PIC0_OCW2, 0x61);
data = io_in8(PORT_KEYDAT);
sprintf(s, "%02X", data);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 23, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
return;
}
像上面的程序那樣在中斷處理程序中既要讀取端口數據又要顯示到屏幕上,作大量的圖像處理工作,如果這個時候正好有一個中斷進來,計算機就不能處理,只能不作任何反應。為了不影響中斷處理,把inthandle21中斷處理程序中的圖像處理部份拿出來,inthandle21中斷處理程序只做一個工作:讀取端口數據,把數據放入緩存區,把圖像處理工作放在主程序中處理。主程序一直查詢緩存區是否有數據,如果有數據就把數據顯示到屏幕上。接下來重點就是怎么寫緩存區的程序了。
鍵盤的緩沖區程序其實就是大學學過的隊列,也就是能保證先進先出的數據結構。當然我們在寫操作系統當然無法用鏈表,只能用數組實現一個隊列。
先定義一個隊列的結構體
struct FIFO8
{
unsigned char *buf;
int p;//下一個數據定入地址
int q;//下一個數據讀出地址
int size;//隊列長度
int free;//表示隊列有沒有數據的字節數
int flags;//標記隊列有沒有溢出,最低位是1表示溢出
};
第一步:初始化
void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
{
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)
{
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)
{
int data;
if (fifo->free == fifo->size)
{
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;
}
鍵盤的中斷碼是IRQ1,而鼠標則要晚得多,是IRQ12。如果要讓鼠標操作有效必須發行指令,讓下面兩個裝置有效,一個是鼠標控制電路,一個是鼠標本身。首先要激活鼠標控制電路,鼠標控制電路包含在鍵盤控制電路里。然后發送激活鼠標的指令,其實發送這個指令實際上就是CPU發送數據到鼠標控制器,也就是鍵盤控制器。
鼠標的中斷處理程序和鼠標差不多,也就是從端口中讀取數據放入隊列中,甚到CPU讀取數據的端口號都是一樣的。然后在主程序中從鼠標隊列中取出數據進行處理。鍵盤的處理非常簡單,從端口讀進來的數據就是鍵盤的掃描碼。那么從端口讀進來的鼠標數據表示什么意思呢?
第八天
鼠標一開始設置完成會自動發生一次中斷,這次中斷發送到CPU的數據為0xfa,只是表示鼠標已經設置完成,將會向CPU發送數據。我們每次對鼠標操作都會引起鼠標向CPU發送三次中斷,每次中斷發送一個字節,一共三個字節,我們要把這三個字節湊到一起處理才是有意義的。
鼠標一次性接收3字節數據,其中第一個字節表示鼠標的動作,第一個字節的高4位取值范圍是03,如果出現其它值,說明鼠標出現錯誤。第一個字節的低4位取值范圍8F,如果出現其他值也說明鼠標發生錯誤,如果第0位為1說明鼠標左鍵按下,如果第1位為1說明右鍵被按下。第二個字節為鼠標水平方向移動的多少,正值為右,負值為左。第三個字節為鼠標豎直方向移動多少,正值向上,負值向下。
目前已經能處理鼠標操作傳入的3次中斷數據,現在就是簡單得在畫面上顯示出來。首先把原來鼠標圖像所在的位置給畫成背景色,然在計算新的鼠標位置,然后在新的鼠標位置上畫一下鼠標圖像,然后就感覺鼠標移動了。
接下來插入講解一下如何從CPU實模式跳入保護模式。我們首先把顯卡模式設置好,然后把一些需要BIOS做的工作給做好。將下來就開始跳入保護模式了。關掉CPU級別中斷,往ox60號端口寫入0xdf,可以讓CPU使用超過1M的內存容量。然后設置CR0寄存器,把CR0寄存器讀出來,然后把最高位和最低位設置為0,再放入CR0,CPU就跳入保護模式了,進入保護模式后馬上要執行JMP指令,才能使接下來的指令正常執行。接下來只要把特定的程序復制到內存中就行了。也就是IDT,GDT,主程序,棧及其他這個所放的位置在內存中放好就行了。
保護模式和實模式的的區別就在于計算內存地址時,是使用段寄存器的值直接指定地址值的一部份呢,還是通過GDT 使用段寄存器的值指定并非實際存在的地址號碼。
第九天
鼠標處理告一段落,這一天主要做內存管理,內存管理的第一步是檢測計算機內存容量有多大。
先檢測CPU有沒有緩存,如果CPU是486以上就有緩存,以下就沒有緩存也就不需要關閉緩存。檢測方法就是看往CPU標志寄存器第18位寫入是否有效。然后對CR0寄存器的某些位設置為0,才能關閉掉緩存。關掉緩存之后,開始檢測內存容量,檢測的方法也很簡單,先把內存地址從小到大,每次讀一個內存內容,然后把內存內容取否,寫入內存,再讀一次這個內存,如果新的內容跟預計取反的內容一樣說明這個內存地址是有效的。為了提高效率我們沒有一個字節一個字節檢測,而是每次檢測4KB,所以這4KB內存中最一4個字節是有效的,那就認為這4KB都是有效內存。內存大小知道了之后,就可以進行內存管理了。
內存管理的基礎是內存分配和內存釋放。內存是否使用可以在內存中做一個位圖來表示,比如內存有16MB,我們以4KB為一個分配單位,那么一共需要4096位標記,也就是512字節。在這512字節中,如第1位是0,就表示0~0xfff這個內存地址未使用。如果以這種方式管理內存,從代碼上來講比較簡單。但是也有不足,那就是分配最小內存地址不靈活,上面的例子中以4KB為一個單位分配,如果只需要1B,那么這樣分配就太浪費了。如果位圖以1字節為單位的話,那么位圖就需要16777216個標記位,也就是2MB個標記位,太浪費了。
還有一種就是這本書使用的內存管理方式,類似于從XXX號地址開始的YYY字節的空間是空著的。可以創建這樣一種數據結構
struct FREEINFO
{
unsigned int addr, size;
}
struct MEMMAN
{
int frees;
struct FREEINFO free[1000];
}
struct MEMMAN memman;
memman.frees = 1;
memman.free[0].addr = 0x400000;
memman.free[0].size = 0x07c00000;
如果需要100KB的空間,查看memman中free的情況,找出100MB以上的可用空間即可
for(int i = 0; i < memman.frees; i++)
{
if(memman.free[i].size >= 100 * 1024)
{
"找到可用空間';
"從地址memman.free[i].addr開始的100KB空間;
}
}
用這種方法可以實現內存的管理。