对于APIC的一些资料

前言

(由于之前的blog已经关闭了,所以将此文章迁移至这里,并非转载)

本文内容主要翻译自osdev.org,因为当年再此学习了很多东西但是发现在国内很少有中文资料,于是下定决心为国家富强奋斗终身。要是感兴趣也可以看原文“http://wiki.osdev.org/APIC”。要是有一些疑问也可以e-mail,xuezhe.liu@hotmail.com。

APIC全称是”Advanced Programmable Interrupt Controller”(高级可编程中断控制器),是intel对过去PIC(可编程中断控制器)的一个升级版本。它用于多处理系统启动并且是先带Intel(或兼容)处理器中不可分割的一部分,APIC用于将中断重定向,并且用于发送处理器间中断消息(IPI消息)。这些事情在过去的PIC中标准都无法完成。

检测是否支持APIC

查看CPUID.01h:EDX[bit 9]标志位,就可以知道本地的CPU是否有内置的Local APIC了。

Local APIC和IOAPIC

在一个基于APIC的系统中,每一个核心都有一个Local APIC(这个问题由于当年年资料有限,Intel手册里面也没看太懂,争吵了好久。意思是说,有一个4个CPU且每个CPU都开启HT技术的情况下,有8个LocalAPIC,每个核心一个,并且完全独立。)。Local APIC负责处理CPU中特定的中断配置。还有其他事情,它包含了“Local Vector Table(LVT)”负责配置事件中断(例如定时器什么的,定时器就是传说中的APIC Timer)。

具体有关APIC的更多信息,可以参考Intel开发手册。

此外,还有一个CPU外面的IO APIC(例如Intel82093AA)的芯片组,并且提供基于多处理器的中断管理,在多个处理器之间,实现静态或动态的中断触发路由(可以理解为他是一个中断的调度器,他可以决定一个外部中断给不给上面,并且是给哪个CPU)。IO APIC中可以配置外部的每一个中断发送给的CPU以及用什么类型发送过去。

每一个中断针都能指定为边缘触发或电平触发。每个中断也都能制定特定的中断向量以及中断转向信息。通过将IOAPIC的IO空间映射到内存上来访问并操作IOAPIC。为了提高系统的灵活性,一般在分配映射内存时,内存分配的地址是可以变的,但是一般默认都分配到了0xFEC00000上。

Intel有关APIC的手册可以从Intel的网站上下载到。分类名是”Multiprocessor Specification”。或者是直接点这个链接下载(我不保证若干年之内还有效)。

处理器间中断(IPI)

Inter-Processor Interrupts(IPIs)是一种由Local APIC触发的中断,一般可以用于多CPU间调度之类的使用(简单的说就是一个CPU触发另外的或本身的或所有的CPU进入一个中断),CPU的启动引导序列也要用到这个(就是传说中的INIT-SIPI-SIPI)。还有其他功能,etc。

Local APIC配置方法

Local APIC在启动的时候开启(他是什么说,但是我发现我引导CPU后,APIC默认是不时能的,一定要自己开开一下,否则APICTImer不好用的。)并且如果要是想给禁用的话,配置IA32_APIC_BASE的MSR(Model Specific Register)。然后CPU接受中断,直接采用8259的兼容PIC模式。Intel手册中说明,但凡你给关了,你就不能给开开了,只能靠重置CPU。

想要开启Local APIC接收中断,则需要设置Spurious Interrupt Vector Register的第8位即可。

IO APIC可以被设置成兼容模式(就像是模拟8259一样)。

Local APIC的寄存器被映射到物理内存上,一般是0xFEE00XXX这种。其实这个地址可以被修改,在MSR中指定了实际的APIC基址。我们可以通过这一机制去移动他的基址。(网上说你不能移动到任何地方,例如大于4GB的地方。)

下面的代码用于访问APIC。

#define IA32_APIC_BASE_MSR 0x1B
#define IA32_APIC_BASE_MSR_ENABLE 0x800

/** returns a 'true' value if the CPU supports APIC
 *  and if the local APIC hasn't been disabled in MSRs
 *  note that this requires CPUID to be supported.
 */
bool cpuHasAPIC()
{
   uint32_t eax, edx;
   cpuid(1, &eax, &edx);
   return edx & CPUID_FLAG_APIC;
}

/* Set the physical address for local APIC registers */
void cpuSetAPICBase(uintptr_t apic)
{
   uint32_t edx = 0;
   uint32_t eax = (apic & 0xfffff000) | IA32_APIC_BASE_MSR_ENABLE;

#ifdef __PHYSICAL_MEMORY_EXTENSION__
   edx = (apic >> 32) & 0x0f;
#endif

   cpuSetMSR(IA32_APIC_BASE_MSR, eax, edx);
}

/**
 * Get the physical address of the APIC registers page
 * make sure you map it to virtual memory ;)
 */
uintptr_t cpuGetAPICBase()
{
   uint32_t eax, edx;
   cpuGetMSR(IA32_APIC_BASE_MSR, &eax, &edx);

#ifdef __PHYSICAL_MEMORY_EXTENSION__
   return (eax & 0xfffff000) | ((edx & 0x0f) << 32);
#else
   return (eax & 0xfffff000);
#endif
}

void enableAPIC()
{
    /* Hardware enable the Local APIC if it wasn't enabled */
    cpuSetAPICBase(cpuGetAPICBase());

    /* Set the Spourious Interrupt Vector Register bit 8 to start receiving interrupts */
    WriteRegister(0xF0, ReadRegister(0xF0) | 0x100);
}

IO APIC配置方法

IO APIC使用两个寄存器完成他的所有操作。一个叫做地址寄存器地址在IOAPICBASE+0,一个叫做数据寄存器地址在IOAPICBASE+0x10(如果愿意的话也可以叫做窗口寄存器和数据寄存器)。注意,一个操作必须使用双字进行操作。地址寄存器中,使用低8位来设置选择要写哪个地方。下面是访问的一个代码示例,大家可以参考下原理并且直接复制用就行了。

uint32_t cpuReadIoApic(void *ioapicaddr, uint32_t reg)
{
   uint32_t volatile *ioapic = (uint32_t volatile *)ioapicaddr;
   ioapic[0] = (reg & 0xff);
   return ioapic[4];
}

void cpuWriteIoApic(void *ioapicaddr, uint32_t reg, uint32_t value)
{
   uint32_t volatile *ioapic = (uint32_t volatile *)ioapicaddr;
   ioapic[0] = (reg & 0xff);
   ioapic[4] = value;
}
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值