【自制操作系統(tǒng)】(六)接受CPU外部中斷--PIC簡(jiǎn)介

可編程中斷控制器PIC

x86體系架構(gòu)包含一個(gè)可編程的中斷控制器PIC(Programmable Interrupt Controller),用于收集外部中斷并將其發(fā)送給CPU。外部設(shè)備不能直接和CPU鏈接^ ^_

intel體系結(jié)構(gòu)的PIC通常包含兩種:8259,以及最新的APIC(Advanced PIC)。

8259芯片(重點(diǎn))

簡(jiǎn)介

8259芯片由IBM公司開發(fā),用于接受外部設(shè)備(如鍵盤)的中斷請(qǐng)求并將其發(fā)送給CPU。

起初,只有一個(gè)8259芯片,能向CPU提供8種中斷請(qǐng)求。后來,通過將另一塊8259芯片與其相連,則能提供總共15種中斷請(qǐng)求。

其結(jié)構(gòu)如下圖所示:

PIC與CPU交互

PIC作為可編程的硬件,我們可以通過CPU執(zhí)行相關(guān)指令來對(duì)其進(jìn)行操作。

每個(gè)8259芯片都有一個(gè)命令端口和數(shù)據(jù)端口,具體如下:

端口名稱 IO端口號(hào)
Master 命令 0x20
Master 數(shù)據(jù) 0x21
Slave 命令 0xA0
Slave 數(shù)據(jù) 0xA1

APIC(簡(jiǎn)介)

Advanced PIC,常見于現(xiàn)代多核心CPU。、其實(shí)從P5開始就已經(jīng)有APIC了,雖然當(dāng)時(shí)并為內(nèi)嵌進(jìn)CPU中。相對(duì)8259,APIC有很多優(yōu)勢(shì),將來有時(shí)間再做詳細(xì)介紹。

為什么不詳細(xì)介紹?

我們現(xiàn)在常見的PC,以及模擬器其實(shí)都是有APIC的。之所以不詳細(xì)介紹APIC是因?yàn)槭忻嫔虾芏鄬⒉僮飨到y(tǒng)的教材都只是在介紹8286,對(duì)APIC設(shè)計(jì)不多,考慮到學(xué)習(xí)曲線,故只做簡(jiǎn)單介紹。

如果我有時(shí)間完成支持多核的操作系統(tǒng),我會(huì)再詳細(xì)地介紹APIC。

架構(gòu)簡(jiǎn)介

APIC包含了LAPIC(Local APIC)、和I/O APIC。每個(gè)CPU都內(nèi)嵌一個(gè)LAPIC,

關(guān)閉APIC

我們的項(xiàng)目中選擇使用遺留的8259,因此我們選擇關(guān)閉APIC。

Intel開發(fā)手冊(cè)卷三可知:關(guān)閉APIC有兩種方式。這里只簡(jiǎn)單介紹一種:通過MSR來關(guān)閉apic。

MSR寄存器的第11位表示了APIC是否開啟。

因?yàn)橥ㄟ^這么久的聯(lián)系,自己對(duì)匯編也算相對(duì)收悉了,所以部分代碼采用匯編編寫。

static void disable_local_apic(){
    uint32_t eax;
    uint32_t edx;
    
    cpuid(1, &eax, &edx);

    if (edx & CPUID_FLAG_APIC ){
        printf("Deteced APIC, will disable it.\n");
        if (edx & CPUID_FLAG_MSR){
            _shutdown_apic();
            printf("Disabled\n");
        } else {
            printf("No MSR detected!\n");
        }
    }
}

代碼會(huì)首先檢查是否有APIC和MSR寄存器,然后調(diào)用_shutdown_apic將apic關(guān)閉。

.global _shutdown_apic
.type _shutdown_apic, @function
_shutdown_apic:
    movl $0x1b, %ecx
    rdmsr 
    andl $0xFFFFF7FF, %eax
    wrmsr
    ret

其中MSR的地址為0x1b。

問題

我在閱讀資料的時(shí)候,產(chǎn)生了一些小問題,怕將來產(chǎn)生同樣的困惑故稍作記錄。

  • 如何檢測(cè)一個(gè)8259芯片是否有slave芯片?

雖然不知道如何檢測(cè)是否有slave芯片,但是除了特別特別早期的CPU,8259芯片都有slave芯片。

  • 如何檢測(cè)是否有8259芯片?

雖然不知道如何檢測(cè),但是常見的IBM-PC兼容機(jī)都有8259芯片。

  • APIC能與8259共存與一個(gè)芯片嗎?

可以并且使用APIC之前需要禁用8259。

接下來的內(nèi)容,我們將進(jìn)行如下假設(shè):我們的硬件滿足通用的IBM-PC兼容機(jī)中斷

PIC(8259A)相關(guān)編程實(shí)現(xiàn)

重置PIC映射關(guān)系

啟動(dòng)時(shí),BIOS程序默認(rèn)將8259的中斷映射為如下表所示關(guān)系:

芯片 中斷號(hào)(in 8259) CPU接收到的中斷號(hào)
Master 0~7 8~15
Slave 8~15 112~119

從上表中可以看出,由于IBM的設(shè)計(jì)失誤,8259Master芯片的默認(rèn)映射的中斷號(hào)8~15與intel保留的中斷號(hào)相沖突。因此,我們必須對(duì)其進(jìn)行重新配置。

具體的硬件細(xì)節(jié)不做過多介紹,可以參看代碼(后期會(huì)開源)。下述代碼將IRQ015映射到3247。

/* reinitialize the PIC controllers, giving them specified vector offsets
   rather than 8h and 70h, as configured by default */
 
#define ICW1_ICW4   0x01        /* ICW4 (not) needed */
#define ICW1_SINGLE 0x02        /* Single (cascade) mode */
#define ICW1_INTERVAL4  0x04        /* Call address interval 4 (8) */
#define ICW1_LEVEL  0x08        /* Level triggered (edge) mode */
#define ICW1_INIT   0x10        /* Initialization - required! */
 
#define ICW4_8086   0x01        /* 8086/88 (MCS-80/85) mode */
#define ICW4_AUTO   0x02        /* Auto (normal) EOI */
#define ICW4_BUF_SLAVE  0x08        /* Buffered mode/slave */
#define ICW4_BUF_MASTER 0x0C        /* Buffered mode/master */
#define ICW4_SFNM   0x10        /* Special fully nested (not) */
 
static void remap_pic(){
    //reinitialize pic
    outb(PIC1_COMMAND, ICW1_INIT+ICW1_ICW4);    //starts the initialization sequence (in cascade mode)
    io_wait();  //on older machines its necessary to give the PIC some time to react to commands as they might not be processed quickly
    outb(PIC2_COMMAND, ICW1_INIT+ICW1_ICW4);
    io_wait();
    outb(PIC1_DATA, ICW2_PIC1);     //ICW2: Master PIC vector offset
    io_wait();
    outb(PIC2_DATA, ICW2_PIC2);   //ICW2: Slave PIC vector offset
    io_wait();
    outb(PIC1_DATA, 4);         //ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100)
    io_wait();
    outb(PIC2_DATA, 2);     //ICW3: tell Slave PIC its cascade identity (0000 0010)
    io_wait();

    outb(PIC1_DATA, ICW4_8086);
    io_wait();
    outb(PIC2_DATA, ICW4_8086);
    io_wait();

    //Enable all
    outb(PIC1_DATA, 0);
    outb(PIC2_DATA, 0);
}

添加相應(yīng)的中斷處理程序

和之前處理IDT一樣,我們同樣采用兩段式的中斷處理程序。但是和通用的中斷處理程序不一樣的是:必須通知PIC,我們已經(jīng)完成了對(duì)其中斷的處理,它可以進(jìn)行下一輪的中斷請(qǐng)求。

匯編層“接收”中斷

提供如下匯編代碼作為參考:注意!在我的實(shí)現(xiàn)中,我用err_code來保存PIC的IRQ號(hào),而int_num保存的是IRQ映射的中斷號(hào)

.macro IRQ irq_num,idt_num
    .global irq\irq_num
    .type irq\irq_num, @function
    irq\irq_num:
        cli
        pushl $\irq_num
        pushl $\idt_num
        jmp irq_comman_stub
.endm

IRQ 0, 32
IRQ 1, 33
...
IRQ 15, 47

上訴代碼中的irq_comman_stub和上一篇博客內(nèi)的isr_comman_stub除了調(diào)用irq_handler之外,其它一致。

處理中斷,通知PIC

我們的操作系統(tǒng),在接收到IRQ之后,應(yīng)該通知PIC它可以處理下一個(gè)中斷。需要注意的是,如果IRQ由slave芯片發(fā)出,那么我們必須對(duì)兩塊芯片都進(jìn)行通知:

#define PIC_EOI     0x20
//Send EOI to PIC
if (regs->err_code >= 8){ //Also send EOI to slave chip
    outb(PIC2_COMMAND,PIC_EOI);
}
outb(PIC1_COMMAND,PIC_EOI);

其它邏輯

參考源碼或者上一篇介紹IDT的文章。

PIC示例:接收時(shí)鐘中斷

PIT,Programmable Interval Timer,是連接在8259的第0號(hào)輸入針腳(即IRQ0)的定時(shí)器??捎糜谝灾付ǖ臅r(shí)間間隔想CPU產(chǎn)生中斷。

因?yàn)闆]有什么復(fù)雜的知識(shí),在此不做過多介紹??梢灾苯訁⒖匆韵麓a:

void init_timer(uint32_t frequency){
   //Regiser timer callback
   register_i_handler(IRQ0, timer_callback);

   // The value we send to the PIT is the value to divide it's input clock
   // (1193180 Hz) by, to get our required frequency. Important to note is
   // that the divisor must be small enough to fit into 16-bits.
    uint32_t divisor = 1193180 / frequency;
    
    // Send command type
    outb(0x43, 0x36);

    //Divisor has to be sent byte-wise, so split here into upper/lower bytes.
    uint8_t l = (uint8_t)(divisor & 0xFF);
    uint8_t h = (uint8_t)((divisor >> 8) & 0xff);

    // Send the frequency divisor.
    outb(0x40, l);
    outb(0x40, h);
}

static uint32_t count = 0;
void timer_callback(registers_t *regs){
    printf("Tick: %d\n", count++);
}

VWware 運(yùn)行截圖

從現(xiàn)在開始,我將在VWware Fusion上運(yùn)行玩具系統(tǒng)。

可能會(huì)遇到的問題:接收不到8259中斷?

確定啟用了中斷(sti),并且能通過軟中斷的方式確認(rèn)IDT能正常工作。

參考資料

  1. http://jamesmolloy.co.uk/tutorial_html/5.-IRQs%20and%20the%20PIT.html
  2. http://wiki.osdev.org/PIC
  3. http://wiki.osdev.org/APIC
  4. http://wiki.osdev.org/IOAPIC
  5. http://wiki.osdev.org/Interrupts#General_IBM-PC_Compatible_Interrupt_Information
  6. http://wiki.osdev.org/Inline_Assembly
  7. http://wiki.osdev.org/Inline_Assembly/Examples
  8. http://wiki.osdev.org/Model_Specific_Registers
  9. http://wiki.osdev.org/CPUID
  10. http://forum.osdev.org/viewtopic.php?t=11998
  11. http://www.jaist.ac.jp/iscenter-new/mpc/altix/altixdata/opt/intel/vtune/doc/users_guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/mergedProjects/instructions/instruct32_hh/vc273.htm
  12. http://ethv.net/workshops/osdev/notes/notes-3
  13. http://www.intel.cn/content/www/cn/zh/processors/architectures-software-developer-manuals.html
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 陷阱分發(fā) 陷阱(trap)指的是這樣一種機(jī)制,當(dāng)異常或中斷發(fā)生時(shí),處理器捕捉到一個(gè)執(zhí)行線程,并且將控制權(quán)轉(zhuǎn)移到...
    kotw_zjc閱讀 1,406評(píng)論 0 0
  • 中斷——Interrupt what 中斷指一個(gè)由CPU外部(或者內(nèi)部)產(chǎn)生的信號(hào),用以通知CPU發(fā)生了某種特定的...
    VicX閱讀 2,277評(píng)論 0 50
  • 說幾句廢fu之言,前幾天沒有接著寫進(jìn)程調(diào)度記錄的文章,當(dāng)然現(xiàn)在也不會(huì)寫,如題,從現(xiàn)在開始記錄linux內(nèi)核基礎(chǔ)知識(shí)...
    Gitlusen閱讀 1,777評(píng)論 0 0
  • 內(nèi)核架構(gòu) 內(nèi)核的基礎(chǔ)知識(shí) 所有現(xiàn)代的操作系統(tǒng)在設(shè)計(jì)師都包含一個(gè)稱為內(nèi)核(kernel)的組件,是整個(gè)系統(tǒng)的核心。內(nèi)...
    CoderKo1o閱讀 2,941評(píng)論 1 4
  • 新生兒的睡眠是一道難題,如何讓寶寶養(yǎng)成良好、規(guī)律的睡眠習(xí)慣呢? 一張圖告訴你[比心]
    夏六月08閱讀 271評(píng)論 0 0

友情鏈接更多精彩內(nèi)容