查看“PIC”的源代码
←
PIC
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
8259的 “可编程中断控制器” (PIC) 是组成x86架构的最重要的芯片之一。 没有它,x86体系结构就不会是中断驱动的体系结构。 8259A的功能是管理硬件中断,并将其发送到适当的系统 [[Interrupts|中断]]。 这允许系统响应设备需求而不损失时间 (相较于轮询设备)。 重要的是要注意,[[APIC]] 已经在更现代的系统中取代了8259的PIC,尤其是那些具有多核/处理器的系统。 == 8259 PIC是做什么的 ?== 8259 PIC通过接受几个中断请求并按顺序将它们投送到处理器来控制CPU的中断机制。 例如,当键盘注册一个keyhit时,它会沿着它的中断线 ([[IRQ]] 1) 向PIC芯片发送一个脉冲,然后PIC芯片将IRQ转换为系统中断,并发送一条消息来中断CPU的工作。 内核的部分工作是处理这些IRQ并执行必要的过程 (这个例子中是轮询键盘以获取scancode) 或报告用户空间程序中断 (向键盘驱动程序发送消息)。 如果没有PIC,你必须轮询系统中的所有设备,看看他们是否想做任何事情 (信号事件),但是有了PIC,你的系统可以保持运行,直到设备想要触发信号事件,这意味着你不会浪费时间去找设备,你会让设备准备好的时候来找你。(译者注:CPU的接线不可能接上所有提供中断的设备,所以需要通过PIC在中间做汇集) === IBM PC 8259 PIC架构 === 一开始 (IBM PC和XT),只使用了一个8259 PIC芯片,它为系统提供了8个IRQ。 传统上,BIOS将它们映射到中断8到15 (0x08到0x0F)。以前单PIC机器基本够用了。 === IBM PC/AT 8259 PIC架构 === IBM PC/AT通过添加第二个8259 PIC芯片扩展了PC体系结构。 这是可能的,因为8259A具有级联中断的能力,只要将一个芯片级联入另一个芯片即可。 这样总共给出了15个中断。 为什么是15而不是16?这是因为当你级联芯片时,PIC需要使用其中一个中断线向另一个芯片发出信号。 因此,在AT中,IRQ线2用于向第二芯片发送信号。 因此,IRQ 2无法由硬件设备使用,而硬件设备已连接到从属PIC上的IRQ 9。 实模式BIOS通常设置为IRQ 9中断处理程序重定向到IRQ 2处理程序。 这样,使用IRQ 2的DOS驱动程序继续工作。 这种双芯片架构仍然在现代系统中可用,并且没有改变 (除了上述APIC架构的出现)。 == 8259 PIC芯片是如何工作的 ?== 现代系统中的两个8259 PIC中的每一个都有8个输入。 当任何输入被触发时,PIC会在内部设置一个位,告诉其中一个输入需要获得服务。 然后,它检查该通道是否被屏蔽,以及是否有中断已经挂起。 如果通道未屏蔽并且没有中断待处理,则PIC将触发中断线。 在从芯片上,这将IRQ 2馈送到主机,并且主机连接到处理器中断线。 当处理器接受中断时,主芯片检查两个PIC中的哪一个负责应答,然后要么向处理器提供中断编号,要么要求从芯片这样做。 应答的PIC查找内部存储的 “向量偏移(vector offset)” 变量,并累加输入线路以形成请求的中断编号。 之后,处理器将查找中断地址并执行相应地操作 (有关更多详细信息,请参见 [[Interrupts]])。 == 用8259 PIC编程 == 每个芯片 (主从) 都有一个命令端口和一个数据端口 (下表中给出)。 当没有发出命令时,数据端口允许我们访问8259 PIC的中断掩码。 {| {{wikitable}} |- ! Chip - Purpose ! [[I/O ports|I/O port]] |- | Master PIC - Command | 0x0020 |- | Master PIC - Data | 0x0021 |- | Slave PIC - Command | 0x00A0 |- | Slave PIC - Data | 0x00A1 |} * 每个PIC向量偏移必须被8整除,因为8259A将较低的3位用于特定中断 (0..7) 的中断数。 * 改变8259 PIC使用的向量偏移的唯一方法是重新初始化它,这解释了为什么代码 “这么长”,以及很多显然没有理由在这里的东西。 * 如果你打算从保护模式返回真实模式 (出于任何目的),则确实必须将PIC恢复为以前的配置。 ===Real Mode=== {| {{wikitable}} |- ! Chip ! Interrupt numbers (IRQ) ! Vector offset ! Interrupt Numbers |- | Master PIC | 0 to 7 | 0x08 | 0x08 to 0x0F |- | Slave PIC | 8 to 15 | 0x70 | 0x70 to 0x77 |} 这些默认BIOS值非常适合实模式编程; 它们不会像在保护模式下那样与任何CPU异常冲突。 === 保护模式 === 在保护模式下,IRQ0到7与Intel保留的CPU异常冲突,直到0x1F。 (这是IBM设计错误。) 因此,很难区分IRQ或软件错误。 因此,建议更改PIC的偏移量 (也称为重新映射PIC),以便IRQs使用非保留向量。 一个常见的选择是将它们移动到可用范围的开始 (IRQs 0 .. 0xF -> INT 0x20 .. 0x2F)。 为此,我们需要将主PIC的偏移量设置为0x20,将从PIC的偏移量设置为0x28。 有关代码示例,请参见下文。 == 代码示例 == === 常用定义 === 这只是本节其余部分共有的一组定义。有关outb(),inb() 和io_wait() 函数,请参见 [[Inline_Assembly/Examples#I/O_access| 本页]]。 <source lang="C"> #define PIC1 0x20 /* IO base address for master PIC */ #define PIC2 0xA0 /* IO base address for slave PIC */ #define PIC1_COMMAND PIC1 #define PIC1_DATA (PIC1+1) #define PIC2_COMMAND PIC2 #define PIC2_DATA (PIC2+1) </source> === 中断结束 === 也许发布给PIC芯片的最常见命令是 “中断结束” (EOI) 命令 (代码0x20)。 在基于IRQ的中断例程结束时发给PIC芯片。 如果IRQ来自主PIC,则仅向主PIC发出此命令就足够了; 但是,如果IRQ来自从属PIC,则必须向两个PIC芯片发出命令。 <source lang="C"> #define PIC_EOI 0x20 /* End-of-interrupt command code */ void PIC_sendEOI(unsigned char irq) { if(irq >= 8) outb(PIC2_COMMAND,PIC_EOI); outb(PIC1_COMMAND,PIC_EOI); } </source> === 初始化 === 当你进入保护模式时 (或者甚至开始之前,如果你没有使用 [[GRUB]]),你需要给两个PIC的第一个命令是初始化命令 (代码0x11)。 此命令使PIC在数据端口上等待3个额外的 “初始化字”。 这些字节给出PIC以下信息: * 其向量偏移。(ICW2) * 告诉它是如何连接到主/从的。(ICW3) * 提供有关环境的其他信息。(ICW4) <source lang="C"> /* 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) */ /* arguments: offset1 - vector offset for master PIC vectors on the master become offset1..offset1+7 offset2 - same for slave PIC: offset2..offset2+7 */ void PIC_remap(int offset1, int offset2) { unsigned char a1, a2; a1 = inb(PIC1_DATA); // save masks a2 = inb(PIC2_DATA); outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); // starts the initialization sequence (in cascade mode) io_wait(); outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4); io_wait(); outb(PIC1_DATA, offset1); // ICW2: Master PIC vector offset io_wait(); outb(PIC2_DATA, offset2); // 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(); outb(PIC1_DATA, a1); // restore saved masks. outb(PIC2_DATA, a2); } </source> ''请注意io_wait() 调用的存在,因为在较旧的计算机上,有必要给PIC一些时间来对命令做出反应,因为它们可能无法快速响应处理'' == 禁用 == 如果要使用处理器本地APIC和IOAPIC,则必须首先禁用PIC。 这是通过以下方式完成的: <source lang=asm>mov al, 0xff out 0xa1, al out 0x21, al </source> == 掩码 == PIC有一个内部寄存器,称为IMR或中断掩码寄存器。 它是8位宽。 此寄存器是进入PIC的请求线路的位映射。 当设置了一个位时,PIC会忽略该请求并继续正常操作。 请注意,在较高的请求线路上设置掩码不会影响较低的线路。 屏蔽IRQ2会导致从PIC停止触发IRQs。 以下是如何屏蔽IRQ的示例: <source lang="C"> void IRQ_set_mask(unsigned char IRQline) { uint16_t port; uint8_t value; if(IRQline < 8) { port = PIC1_DATA; } else { port = PIC2_DATA; IRQline -= 8; } value = inb(port) | (1 << IRQline); outb(port, value); } void IRQ_clear_mask(unsigned char IRQline) { uint16_t port; uint8_t value; if(IRQline < 8) { port = PIC1_DATA; } else { port = PIC2_DATA; IRQline -= 8; } value = inb(port) & ~(1 << IRQline); outb(port, value); } </source> == ISR和IRR == PIC芯片具有两个中断状态寄存器: 服务中寄存器 (ISR In-Service Register) 和中断请求寄存器 (IRR Interrupt Request Register)。 ISR告诉我们正在服务哪些中断,这意味着irq发送到CPU。 IRR告诉我们哪些中断被触发了。 基于中断掩码 (IMR),PIC将从IRR向CPU发送中断,此时它们在ISR中被标记。 可以通过OCW3命令字读取ISR和IRR。 这是发送到具有位3设置的命令端口之一 (0x20或0xa0) 的命令。 要读取ISR或IRR,请将适当的命令写入命令端口,然后读取命令端口 (而不是数据端口)。 要读取IRR,请写入0x0a。 要读取ISR,请写入0x0b。 ISR和IRR各为8位。 以下是如何从两个级联图片中读取16位值的ISR和IRR数据的示例: <source lang="C"> #define PIC1_CMD 0x20 #define PIC1_DATA 0x21 #define PIC2_CMD 0xA0 #define PIC2_DATA 0xA1 #define PIC_READ_IRR 0x0a /* OCW3 irq ready next CMD read */ #define PIC_READ_ISR 0x0b /* OCW3 irq service next CMD read */ /* Helper func */ static uint16_t __pic_get_irq_reg(int ocw3) { /* OCW3 to PIC CMD to get the register values. PIC2 is chained, and * represents IRQs 8-15. PIC1 is IRQs 0-7, with 2 being the chain */ outb(PIC1_CMD, ocw3); outb(PIC2_CMD, ocw3); return (inb(PIC2_CMD) << 8) | inb(PIC1_CMD); } /* Returns the combined value of the cascaded PICs irq request register */ uint16_t pic_get_irr(void) { return __pic_get_irq_reg(PIC_READ_IRR); } /* Returns the combined value of the cascaded PICs in-service register */ uint16_t pic_get_isr(void) { return __pic_get_irq_reg(PIC_READ_ISR); } </source> 请注意,由于PIC的级联性质,每当设置任何PIC2位时,这些函数将显示位2 (0x0004) 为on。 另请注意,不必每次读取时都重置OCW3命令。 一旦为IRR或ISR设置了它,将来对CMD端口的读取将返回适当的寄存器。 芯片记住你使用了什么OCW3设置。 (免责声明: 我尚未测试最后一部分,但规范就是这么说的。) == 虚假IRQs == 当IRQ发生时,PIC芯片告诉CPU (通过PIC的INTR线路) 存在中断,CPU确认这一点并等待PIC发送中断向量。 这会产生冲突的情况: 如果IRQ在PIC告诉CPU有中断后消失,而在PIC向CPU发送中断向量之前,CPU会等待PIC告诉它哪个中断向量,但此时PIC又没有一个有效的中断向量来告诉CPU。 为了解决这个问题,PIC告诉CPU一个虚假的中断号。 这是一个虚假的IRQ。 假中断号是对应的PIC芯片的最低优先级中断号 (IRQ 7表示主PIC,IRQ 15表示从PIC)。 中断消失有几个原因。 根据我的经验,最常见的原因是软件在错误的时间发送EOI。 其他原因包括IRQ线路 (或INTR线路) 上的噪声。 === 处理虚假的IRQ === 对于虚假的IRQ,没有真实的IRQ,并且不会设置PIC芯片的相应IRQ的ISR (服务寄存器) 标志。 这意味着中断处理程序不得将EOI发送回PIC以重置ISR标志。 处理IRQ 7的正确方法是首先检查主PIC芯片的ISR,以查看IRQ是虚假的IRQ还是真实的IRQ。 如果它是一个真正的IRQ,那么它与任何其他真正的IRQ一样对待。 如果它是虚假的IRQ,那么你将忽略它 (并且不发送EOI)。 处理IRQ 15的正确方法是类似的,但由于从PIC和主PIC之间的相互作用,这有点棘手。 首先检查从PIC芯片的ISR,看看IRQ是虚假的IRQ还是真实的IRQ。 如果它是一个真正的IRQ,那么它与任何其他真正的IRQ一样对待。 如果它是虚假的IRQ,那么不要将EOI发送到从PIC; 然而,你仍然需要将EOI发送到主PIC,因为主PIC本身不会知道它是从机的虚假IRQ。 还请注意,某些内核 (例如Linux) 会跟踪已发生的虚假IRQ的数量 (例如,通过在发生虚假IRQ时增加计数器)。 这对于检测软件中的问题 (例如在错误的时间发送EOIs) 和检测硬件中的问题 (例如线路噪声) 可能是有用的。 == 另见 == === 文章 === * [[APIC]] * [[IOAPIC]] * http://www.brokenthorn.com/Resources/OSDevPic.html === 论坛主题 === === 外部链接 === * [http://pdos.csail.mit.edu/6.828/2005/readings/hardware/8259A.pdf Intel Datasheet] [[Category:Interrupts]] [[de:Programmable Interrupt Controller]]
本页使用的模板:
模板:Wikitable
(
查看源代码
)
返回至“
PIC
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息