可編程中斷控制器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能正常工作。
參考資料
- http://jamesmolloy.co.uk/tutorial_html/5.-IRQs%20and%20the%20PIT.html
- http://wiki.osdev.org/PIC
- http://wiki.osdev.org/APIC
- http://wiki.osdev.org/IOAPIC
- http://wiki.osdev.org/Interrupts#General_IBM-PC_Compatible_Interrupt_Information
- http://wiki.osdev.org/Inline_Assembly
- http://wiki.osdev.org/Inline_Assembly/Examples
- http://wiki.osdev.org/Model_Specific_Registers
- http://wiki.osdev.org/CPUID
- http://forum.osdev.org/viewtopic.php?t=11998
- 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
- http://ethv.net/workshops/osdev/notes/notes-3
- http://www.intel.cn/content/www/cn/zh/processors/architectures-software-developer-manuals.html