官术网_书友最值得收藏!

9 GDT與IDT的初始化(harib02i)

鼠標指針顯示出來了,我們想做的第一件事就是去移動它,但鼠標指針卻一動不動。那是當然,因為我們還沒有做出這個功能。……嗯,無論如何想讓它動起來。

要怎么樣才能讓它動呢?……(思考中)……有辦法了!首先要將GDT和IDT初始化。不過在此之前,必須說明一下什么是GDT和IDT。

GDT也好,IDT也好,它們都是與CPU有關的設定。為了讓操作系統(tǒng)能夠使用32位模式,需要對CPU做各種設定。不過,asmhead.nas里寫的程序有點偷工減料,只是隨意進行了一些設定。如果這樣原封不動的話,就無法做出使用鼠標指針所需要的設定,所以我們要好好重新設置一下。

那為什么要在asmhead.nas里偷工減料呢?最開始就規(guī)規(guī)矩矩地設定好不行嗎?……嗯,這個問題一下子就戳到痛處了。這里因為筆者希望盡可能地不用匯編語言,而用C語言來寫,這樣大家更容易理解。所以,asmhead.nas里盡可能少寫,只做了運行bootpack.c所必需的一些設定。這次為了使用這個文件,必須再進行設定。如果大家有足夠能力用匯編語言編寫程序,就不用模仿筆者了,從一開始規(guī)規(guī)矩矩地做好設定更好。

從現(xiàn)在開始,學習內容的難度要增加不小。以后要講分段呀,中斷什么的,都很難懂,很多程序員都是在這些地方受挫的。從難度上考慮,應該在20天以后講而不是第5天。但如果現(xiàn)在不講,幾乎所有的裝置都不能控制,做起來也沒什么意思。筆者不想讓大家做沒有意思的操作系統(tǒng)。

所以請大家堅持著讀下去,先懂個大概,然后再回過頭來仔細咀嚼。在一天半以后,內容的難度會回到以前的水平,所以這段時間大家就打起精神加油吧!

■■■■■

先來講一下分段英文是segmentation。。回想一下僅用匯編語言編程時,有一個指令叫做ORG。如果不用ORG指令明確聲明程序要讀入的內存地址,就不能寫出正確的程序來。如果寫著ORG 0x1234,但程序卻沒讀入內存的0x1234號,可就不好辦了。

發(fā)生這種情況是非常麻煩的。最近的操作系統(tǒng)能同時運行多個程序,這一點也不稀奇。這種時候,如果內存的使用范圍重疊了怎么辦?這可是一件大事。必須讓某個程序放棄執(zhí)行,同時報出一個“因為內存地址沖突,不能執(zhí)行”的錯誤信息。但是,這種錯誤大家見過嗎?沒有。所以,肯定有某種方法能解決這個問題。這個方法就是分段。

所謂分段,打個比方說,就是按照自己喜歡的方式,將合計4GBGB(Giga Byte,吉字節(jié)),指1024MB,可不是Game Boy的省略喲(笑)。的內存分成很多塊(block),每一塊的起始地址都看作0來處理。這很方便,有了這個功能,任何程序都可以先寫上一句ORG 0。像這樣分割出來的塊,就稱為段(segment)。順便說一句,如果不用分段而用分頁英文是paging。“分段”的基本思想是將4GB的內存分割;而分頁的思想是有多少個任務就要分多少頁,還要對內存進行排序。不過解說到這個程度,大家估計都不懂。以后有機會再詳細說明。(paging),也能解決問題。不過我們目前還不討論分頁,可以暫時不考慮它。

需要注意的一點是,我們用16位的時候曾經(jīng)講解過的段寄存器。這里的分段,使用的就是這個段寄存器。但是16位的時候,如果計算地址,只要將地址乘以16就可以了。但現(xiàn)在已經(jīng)是32位了,不能再這么用了。如果寫成“MOV AL, [DS:EBX]”, CPU會往EBX里加上某個值來計算地址,這個值不是DS的16倍,而是DS所表示的段的起始地址。即使省略段寄存器(segment register)的地址,也會自動認為是指定了DS。這個規(guī)則不管是16位模式還是32位模式,都是一樣的。

■■■■■

按這種分段方法,為了表示一個段,需要有以下信息。

? 段的大小是多少

? 段的起始地址在哪里

? 段的管理屬性(禁止寫入,禁止執(zhí)行,系統(tǒng)專用等)

CPU用8個字節(jié)(=64位)的數(shù)據(jù)來表示這些信息。但是,用于指定段的寄存器只有16位。或許有人會猜想在32位模式下,段寄存器會擴展到64位,但事實上段寄存器仍然是16位。

那該怎么辦才好呢?可以模仿圖像調色板的做法。也就是說,先有一個段號英文是segment selector,也有譯作“段選擇符”的。,存放在段寄存器里。然后預先設定好段號與段的對應關系。

調色板中,色號可以使用0~255的數(shù)。段號可以用0~8191的數(shù)。因為段寄存器是16位,所以本來應該能夠處理0~65535范圍的數(shù),但由于CPU設計上的原因,段寄存器的低3位不能使用。因此能夠使用的段號只有13位,能夠處理的就只有位于0~8191的區(qū)域了。

段號怎么設定呢?這是對于CPU的設定,不需要像調色板那樣使用io_out(由于不是外部設備,當然沒必要)。但因為能夠使用0~8191的范圍,即可以定義8192個段,所以設定這么多段就需要8192×8=65536字節(jié)(64KB)。大家可能會想,CPU沒那么大存儲能力,不可能存儲那么多數(shù)據(jù),是不是要寫入到內存中去呀。不錯,正是這樣。這64KB(實際上也可以比這少)的數(shù)據(jù)就稱為GDT。

GDT是“global(segment)descriptor table”的縮寫,意思是全局段號記錄表。將這些數(shù)據(jù)整齊地排列在內存的某個地方,然后將內存的起始地址和有效設定個數(shù)放在CPU內被稱作GDTRglobal (segment) descriptor table register的縮寫。的特殊寄存器中,設定就完成了。

■■■■■

另外,IDT是“interrupt descriptor table”的縮寫,直譯過來就是“中斷記錄表”。當CPU遇到外部狀況變化,或者是內部偶然發(fā)生某些錯誤時,會臨時切換過去處理這種突發(fā)事件。這就是中斷功能。

我們拿電腦的鍵盤來舉個例子。以CPU的速度來看,鍵盤特別慢,只是偶爾動一動。就算是重復按同一個鍵,一秒鐘也很難輸入50個字符。而CPU在1/50秒的時間內,能執(zhí)行200萬條指令(CPU主頻100MHz時)。CPU每執(zhí)行200萬條指令,查詢一次鍵盤的狀況就已經(jīng)足夠了。如果查詢得太慢,用戶輸入一個字符時電腦就會半天沒反應。

要是設備只有鍵盤,用“查詢”這種處理方法還好。但事實上還有鼠標、軟驅、硬盤、光驅、網(wǎng)卡、聲卡等很多需要定期查看狀態(tài)的設備。其中,網(wǎng)卡還需要CPU快速響應。響應不及時的話,數(shù)據(jù)就可能接受失敗,而不得不再傳送一次。如果因為害怕處理不及時而靠查詢的方法輪流查看各個設備狀態(tài)的話,CPU就會窮于應付,不能完成正常的處理。

正是為解決以上問題,才有了中斷機制。各個設備有變化時就產(chǎn)生中斷,中斷發(fā)生后,CPU暫時停止正在處理的任務,并做好接下來能夠繼續(xù)處理的準備,轉而執(zhí)行中斷程序。中斷程序執(zhí)行完以后,再調用事先設定好的函數(shù),返回處理中的任務。正是得益于中斷機制,CPU可以不用一直查詢鍵盤,鼠標,網(wǎng)卡等設備的狀態(tài),將精力集中在處理任務上。

講了這么長,其實總結來說就是:要使用鼠標,就必須要使用中斷。所以,我們必須設定IDT。IDT記錄了0~255的中斷號碼與調用函數(shù)的對應關系,比如說發(fā)生了123號中斷,就調用〇×函數(shù),其設定方法與GDT很相似(或許是因為使用同樣的方法能簡化CPU的電路)。

如果段的設定還沒順利完成就設定IDT的話,會比較麻煩,所以必須先進行GDT的設定。

■■■■■

雖然說明很長,但程序并沒那么長。

本次的*bootpack.c節(jié)選

struct SEGMENT_DESCRIPTOR{
short limit_low, base_low;
    char base_mid, access_right;
    char limit_high, base_high;
};

struct GATE_DESCRIPTOR {
    short offset_low, selector;

    char dw_count, access_right;
    short offset_high;
};

void init_gdtidt(void)
{
    struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000;
    struct GATE_DESCRIPTOR     *idt = (struct GATE_DESCRIPTOR     *) 0x0026f800;
    int i;

    /* GDT的初始化 */
    for (i = 0; i < 8192; i++) {
        set_segmdesc(gdt + i, 0, 0, 0);
    }
    set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
    set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
    load_gdtr(0xffff, 0x00270000);

    /* IDT的初始化 */
    for (i = 0; i < 256; i++) {
        set_gatedesc(idt + i, 0, 0, 0);
    }
    load_idtr(0x7ff, 0x0026f800);

    return;
}

void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
    if (limit > 0xfffff) {
        ar |= 0x8000; /* G_bit = 1 */
        limit /= 0x1000;
    }
    sd->limit_low     = limit & 0xffff;
    sd->base_low      = base & 0xffff;
    sd->base_mid      = (base >> 16) & 0xff;
    sd->access_right = ar & 0xff;
    sd->limit_high   = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
    sd->base_high     = (base >> 24) & 0xff;
    return;
}

void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
    gd->offset_low   = offset & 0xffff;
    gd->selector      = selector;
    gd->dw_count      = (ar >> 8) & 0xff;
    gd->access_right = ar & 0xff;
    gd->offset_high  = (offset >> 16) & 0xffff;
    return;
}

SEGMENT_DESCRIPTOR中存放GDT的8字節(jié)的內容,它無非是以CPU的資料為基礎,寫成了結構體的形式。同樣,GATE_DESCRIPTOR中存放IDT的8字節(jié)的內容,也是以CPU的資料為基礎的。

變量gdt被賦值0x00270000,就是說要將0x270000~0x27ffff設為GDT。至于為什么用這個地址,其實那只是筆者隨便作出的決定,并沒有特殊的意義。從內存分布圖可以看出這一塊地方并沒有被使用。

變量idt也是一樣,IDT被設為了0x26f800~0x26ffff。順便說一下,0x280000~0x2fffff已經(jīng)有了bootpack.h。“哎?什么時候?我可沒聽說過這事哦!”大家可能會有這樣的疑問,其實是后面要講到的“asmhead.nas”幫我們做了這樣的處理。

■■■■■

現(xiàn)在繼續(xù)往下說明。

for (i = 0; i < 8192; i++) {
    set_segmdesc(gdt + i, 0, 0, 0);
}

請注意一下以上幾行代碼。gdt是0x270000, i從0開始,每次加1,直到8191。這樣一來,好像gdt+i最大也只能是0x271fff。但事實上并不是那樣。C語言中進行指針的加法運算時,內部還隱含著乘法運算。變量gdt已經(jīng)聲明為指針,指向SEGMENT_DESCRIPTOR這樣一個8字節(jié)的結構體,所以往gdt里加1,結果卻是地址增加了8。

因此這個for語句就完成了對所有8192個段的設定,將它們的上限(limit,指段的字節(jié)數(shù)-1)、基址(base)、訪問權限都設為0。

再往下還有這樣的語句:

set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);
set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);

以上語句是對段號為1和2的兩個段進行的設定。段號為1的段,上限值為0xffffffff即大小正好是4GB),地址是0,它表示的是CPU所能管理的全部內存本身。段的屬性設為0x4092,它的含義我們留待明天再說。下面來看看段號為2的段,它的大小是512KB,地址是0x280000。這正好是為bootpack.hrb而準備的。用這個段,就可以執(zhí)行bootpack.hrb。因為bootpack.hrb是以ORG 0為前提翻譯成的機器語言。

■■■■■

下一個語句是:

load_gdtr(0xffff, 0x00270000);

這是因為依照常規(guī),C語言里不能給GDTR賦值,所以要借助匯編語言的力量,僅此而已。

再往下都是關于IDT的記述,因為跟前面一樣,所以應該沒什么問題。

在set_segmdesc和set_gatedesc中,使用了新的運算符,下面來介紹一下。首先看看語句“ar |=0x8000; ”,它是“ar = ar |0x8000; ”的省略表現(xiàn)形式。同樣還有“l(fā)imit /= 0x1000; ”,它是“l(fā)imit =limit/0x1000; ”的省略表現(xiàn)形式。“|”是前面已經(jīng)出現(xiàn)的或(OR)運算符。“/”是除法運算符。

“>>”是右移位運算符。比如計算00101100>>3,就得到00000101。移位時,舍棄右邊溢出的位,而左邊不足的3位,要補3個0。

■■■■■

今天到這里就差不多了,訪問權屬性及IDT的詳細說明就留到明天吧。總之,使用本程序的操作系統(tǒng)是做成了。能不能正常運行啊?趕緊試一試吧。“make run ”……還好,能運行。這次只是簡單地做了初期設定,所以即使運行成功了,畫面上也什么都不顯示。

現(xiàn)在haribote.sys變成多少字節(jié)了呢?哦,光字體就有4KB,增加了不少,到7632字節(jié)了。今天就先到這里吧,大家明天見。

主站蜘蛛池模板: 金寨县| 连平县| 山阳县| 安徽省| 甘谷县| 盐边县| 肃宁县| 东兰县| 满洲里市| 蓬安县| 民县| 青神县| 望都县| 乐平市| 柳州市| 革吉县| 温泉县| 报价| 鸡东县| 和林格尔县| 东至县| 大庆市| 金华市| 武冈市| 湘乡市| 宁国市| 福建省| 新民市| 崇礼县| 新野县| 松江区| 南木林县| 洛南县| 太康县| 肇源县| 克拉玛依市| 银川市| 沾益县| 句容市| 石河子市| 原阳县|