《30天自制操作系統》讀書筆記(5) GDT&IDT


  • 梳理項目結構

項目做到現在, 前頭的好多東西都忘了, 還是通過Makefile重新理解一下整個項目是如何編譯的:

 

現在我們擁有這么9個文件:

  • ipl10.nas    InitialProgramLoader, 占用了軟盤的第一個扇區並符合啟動盤的規范, 默認被載入地址是0x7c00 到 0x7e00, 負責將10個柱面讀入到0x8200到0x34fff (10個柱面共10*2*18 = 360 個扇區但是第一個沒有被讀入);
  • asmhead.nas     包含一些暫時未知的設定;
  • naskfun.nas     包含供C語言程序使用的匯編函數;
  • bootpack.h     各種常量定義, 函數定義;
  • hankaku.txt     字庫文件;
  • fifo.c         一個完整的循環隊列的實現, 用於中斷的緩沖;
  • graphic.c     提供繪圖函數, 繪制系統界面和指針, 打印字符;
  • dsctbl.c     GDT(全局符號描述符表) 和 IDT(中斷描述符表) 的設定;
  • int.c         Interrupt Service Routines 的處理.

系統的內存分配:

  • 0x7c00~0x7e00     ipl10.nas
  • 0x26f800~0x26ffff     IDT
  • 0x270000~0x27ffff     GDT
  • 0x280000~0x2fffff    bootpack.h

關於GDT IDT設定, 盡管書中已經盡量簡化, 但是有相當多的細節需要注意, 並非一篇博文能容下的, 因此我決定不記錄整個過程, 而是歸納其中需要注意的大的知識點, 畢竟這只是筆記…

 

   GDT (Global Descriptor Table) is a data structure in order to define the characteristics of the various memory areas used during program execution,

including the base address, the size and access privileges like executability and writability.

   GDT 全稱是全局段描述符表, 用來提供程序執行是需要的關於內存的各種信息, 表的條目成為GDT Entry, 大小為8字節, 包含了: 段的大小; 段的起始地址; 段的管理屬性等信息.其結構如下:

 C語言表示如下:

struct SEGMENT_DESCRIPTOR
{
    short limit_low, base_low;
    char base_mid, access_right;
    char limit_high, base_high;
} __attribute__((packed)); /* 作者在代碼中省略了這個壓縮內存的指令, 應該是由nask隱式地執行了.*/ 

 

 亦可以用位域來表示:

struct desc_struct {
    union {
        struct {
            unsigned int a;
            unsigned int b;
        };
        struct {
            u16 limit0; // u16 unsigned int
            u16 base0;
            unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;
            unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
        };
    };
} __attribute__((packed)); // 這樣段的屬性顯得更加清晰. 

 

可以看到limit 被分成了limit_low 和 limit_high, 基址base 被分成了low, mid 和 high, 這導致了這個結構的賦值非常麻煩, 造成這樣的原因是為了與286之前的系統兼容.

  You noticed that I didn't gave a real structure for GDT[], didn't you? That's on purpose. The actual structure of descriptors is a little messy for backwards compatibility with the 286's GDT. Base address are split on 3 different fields and you cannot encode any limit you want. Plus, here and there, you have flags that you need to set up properly if you want things to work.

 

這也導致了對SEGMENT_DESCRIPTOR結構填充的麻煩, 填充SEGMENT_DSCRIPTOR的代碼如下(我看得也很懵…):

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;
} 

 

// 看到這些突然覺得腦子不夠用, 先了解以下就好了, 我覺得這些繁復的規則在真正制作系統的過程中也只是只用一次的技能, 可以通過查資料解決.

一個GDT 至少應該有以下的條目:

  • The null descriptor which is never referenced by the processor. Certain emulators, like Bochs, will complain about limit exceptions if you do not have one present. Some use this descriptor to store a pointer to the GDT itself (to use with the LGDT instruction). The null descriptor is 8 bytes wide and the pointer is 6 bytes wide so it might just be the perfect place for this. // 一個空的描述符
  • A code segment descriptor (for your kernel, it should have type=0x9A) //代碼段
  • A data segment descriptor (you can't write to a code segment, so add this with type=0x92) // 數據段
  • A TSS segment descriptor (trust me, keep a place for at least one) .//任務狀態段

    但是作者在初始化所有段之后, 只填充了code段和data段:

set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW);
set_segmdesc(gdt + 2, LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);
//代碼段正是bootpack.hrb

 

  • GDTR是一個48位寄存器, 用來告知CPU GDT在內存中的位置, 使用LGDT指令可將地址寫入到GDTR中, 代碼如下:
_load_gdtr:        ; void load_gdtr(int limit, int addr);
        MOV    AX,[ESP+4]        ; limit
        MOV    [ESP+6],AX
        LGDT    [ESP+6]
        RET 

 

  • PIC

    http://wiki.osdev.org/PIC

    The 8259 Programmable Interrupt Controller (PIC) is one of the most important chips making up the x86 architecture. Without it, the x86 architecture would not be an interrupt driven architecture. The function of the 8259A is to manage hardware interrupts and send them to the appropriate system interrupt. This allows the system to respond to devices needs without loss of time (from polling the device, for instance).

    由於CPU的結構限制, 只能處理一個中斷, 所以PIC被設計來輔助CPU處理多個中斷, PIC的全稱是可編程中斷控制器, 結構如圖, 通過PIC可以控制15個中斷, 在現代操作系統中, 8259PIC似乎被APIC所取代.

  主觸發器連接着CPU的管腳, 從觸發器連接着主觸發器的IRQ 2.

  IRQ x 負責傳導中斷信號.

  • 初始化PIC(邊沿觸發模式什么的讓我想起了數字邏輯…):
  • PIC的觸發器:

    IMR 是中斷屏蔽寄存器, 用來屏蔽IRQ信號;

    ICW 1 和ICW 4 聲明了主板配線方式( = = 完全不懂哎);

    ICW 3 是主從設定, 表示觸發器的哪一位連着從觸發器; (一般是IRQ2 啦);

    ICW 2 決定IRQ以哪一個中斷號通知CPU, INT 0x0~0x19 不能被使用.

   總的代碼如下:

void init_pic(void)
    /* PIC初始化*/
{
    io_out8(PIC0_IMR, 0xff ); //禁止主PIC的所有中斷
    io_out8(PIC1_IMR, 0xff ); //禁止從PIC的所有中斷

    // 主PIC設定
    io_out8(PIC0_ICW1, 0x11 ); //邊沿觸發模式 (edge trigger mode)
    io_out8(PIC0_ICW2, 0x20 ); //IRQ0~7 由 INT 20~27 接收
    io_out8(PIC0_ICW3, 1 << 2); //PIC1從PIC由IRQ2 連接
    io_out8(PIC0_ICW4, 0x01 ); //無緩沖區模式
//從PIC設定 io_out8(PIC1_ICW1, 0x11 ); //邊沿觸發模式 (edge trigger mode) io_out8(PIC1_ICW2, 0x28 ); // IRQ0~7 由 INT 28~2f 接收 io_out8(PIC1_ICW3, 2 ); //PIC1由IRQ2 連接 io_out8(PIC1_ICW4, 0x01 ); //無緩沖區模式 io_out8(PIC0_IMR, 0xfb ); //11111011 PIC1 以外全部禁止 io_out8(PIC1_IMR, 0xff ); //11111111 禁止所有中斷 return; }

 

  • ISR

    http://wiki.osdev.org/ISR

    ISR (Interrupt Service Routines) 是中斷處理程序, 當發生中斷的時候, 這段程序會被調用.注意ISR的返回不能夠用RET指令, 而是用IRETD, 而IRETD指令在C語言里沒有相應的實現, 當然有許多方法來規避這個問題, 作者用的是在匯編實現的函數里在調用C語言實現的函數.

    寫法暫時略過.

  • IDT

    http://wiki.osdev.org/IDT

    The Interrupt Descriptor Table (IDT) is specific to the I386 architecture. It is the Protected mode counterpart to the Real Mode Interrupt Vector Table (IVT) telling where the Interrupt Service Routines (ISR) are located. It is similar to the Global Descriptor Table in structure. The IDT entries are called gates. It can contain Interrupt Gates, Task Gates and Trap Gates.

    IDT是一個保護模式下的中斷描述符表, 用來告訴CPU ISR的位置, 結構和GDT相似, IDT的Entry也被稱作門, 包括了中斷門, 陷阱門和任務門…此處用的應是中斷門.

    set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
    set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
    set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32); 

     

    其他函數的實現方法與GDT類似, 略過不表.

這次主要的是概念理解而不是代碼編寫, 界面也沒有發生顯著的變化, 因此不貼代碼和圖片了.

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM