【自制操作系统】(六)接受CPU外部中断--PIC简介

可编程中断控制器PIC

x86体系架构包含一个可编程的中断控制器PIC(Programmable Interrupt Controller),用于收集外部中断并将其发送给CPU。外部设备不能直接和CPU链接^ ^_

intel体系结构的PIC通常包含两种:8259,以及最新的APIC(Advanced PIC)。

8259芯片(重点)

简介

8259芯片由IBM公司开发,用于接受外部设备(如键盘)的中断请求并将其发送给CPU。

起初,只有一个8259芯片,能向CPU提供8种中断请求。后来,通过将另一块8259芯片与其相连,则能提供总共15种中断请求。

其结构如下图所示:

PIC与CPU交互

PIC作为可编程的硬件,我们可以通过CPU执行相关指令来对其进行操作。

每个8259芯片都有一个命令端口和数据端口,具体如下:

端口名称 IO端口号
Master 命令 0x20
Master 数据 0x21
Slave 命令 0xA0
Slave 数据 0xA1

APIC(简介)

Advanced PIC,常见于现代多核心CPU。、其实从P5开始就已经有APIC了,虽然当时并为内嵌进CPU中。相对8259,APIC有很多优势,将来有时间再做详细介绍。

为什么不详细介绍?

我们现在常见的PC,以及模拟器其实都是有APIC的。之所以不详细介绍APIC是因为市面上很多将操作系统的教材都只是在介绍8286,对APIC设计不多,考虑到学习曲线,故只做简单介绍。

如果我有时间完成支持多核的操作系统,我会再详细地介绍APIC。

架构简介

APIC包含了LAPIC(Local APIC)、和I/O APIC。每个CPU都内嵌一个LAPIC,

关闭APIC

我们的项目中选择使用遗留的8259,因此我们选择关闭APIC。

Intel开发手册卷三可知:关闭APIC有两种方式。这里只简单介绍一种:通过MSR来关闭apic。

MSR寄存器的第11位表示了APIC是否开启。

因为通过这么久的联系,自己对汇编也算相对收悉了,所以部分代码采用汇编编写。

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

代码会首先检查是否有APIC和MSR寄存器,然后调用_shutdown_apic将apic关闭。

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

其中MSR的地址为0x1b。

问题

我在阅读资料的时候,产生了一些小问题,怕将来产生同样的困惑故稍作记录。

  • 如何检测一个8259芯片是否有slave芯片?

虽然不知道如何检测是否有slave芯片,但是除了特别特别早期的CPU,8259芯片都有slave芯片。

  • 如何检测是否有8259芯片?

虽然不知道如何检测,但是常见的IBM-PC兼容机都有8259芯片。

  • APIC能与8259共存与一个芯片吗?

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

接下来的内容,我们将进行如下假设:我们的硬件满足通用的IBM-PC兼容机中断

PIC(8259A)相关编程实现

重置PIC映射关系

启动时,BIOS程序默认将8259的中断映射为如下表所示关系:

芯片 中断号(in 8259) CPU接收到的中断号
Master 0~7 8~15
Slave 8~15 112~119

从上表中可以看出,由于IBM的设计失误,8259Master芯片的默认映射的中断号8~15与intel保留的中断号相冲突。因此,我们必须对其进行重新配置。

具体的硬件细节不做过多介绍,可以参看代码(后期会开源)。下述代码将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);
}

添加相应的中断处理程序

和之前处理IDT一样,我们同样采用两段式的中断处理程序。但是和通用的中断处理程序不一样的是:必须通知PIC,我们已经完成了对其中断的处理,它可以进行下一轮的中断请求。

汇编层“接收”中断

提供如下汇编代码作为参考:注意!在我的实现中,我用err_code来保存PIC的IRQ号,而int_num保存的是IRQ映射的中断号

.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和上一篇博客内的isr_comman_stub除了调用irq_handler之外,其它一致。

处理中断,通知PIC

我们的操作系统,在接收到IRQ之后,应该通知PIC它可以处理下一个中断。需要注意的是,如果IRQ由slave芯片发出,那么我们必须对两块芯片都进行通知:

#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示例:接收时钟中断

PIT,Programmable Interval Timer,是连接在8259的第0号输入针脚(即IRQ0)的定时器。可用于以指定的时间间隔想CPU产生中断。

因为没有什么复杂的知识,在此不做过多介绍。可以直接参看以下代码:

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 运行截图

从现在开始,我将在VWware Fusion上运行玩具系统。

可能会遇到的问题:接收不到8259中断?

确定启用了中断(sti),并且能通过软中断的方式确认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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容