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