对称多处理器和处理器间中断是什么我这里就不解释了,网络上这部分内容比较多,随便一百度就有很多中文资料,我们来看一下他们是怎么实现的。
另外本文假定你已经具有了Local APIC的基础知识,至少要知道怎么写APIC的寄存器吧.
要启动"主"处理器之外的任何一个处理器(事实上,处理器没有主次之分,除了启动之外),或者要发送一个处理器间中断,离不开的一个Local Apic寄存器就是中断命令寄存器(Interrupt Command Register,ICR).
这就是ICR的结构图,地址是0x310(高32位),0x300(低32位),比较重要的是最高8位存放的是LocalApicID,INIT,StartUp,NMI,最低8位存放的Vector
(有时候不是这样的,这里指的是本文用到的模式.)
如何获取一共有几个CPU和每个CPU的LocalApicID?这显然是要启动一个"次"CPU首先要面对的问题.
这个问题我们要用到ACPI(不是APIC,高级配置和电源管理接口)
寻找ACPI可以这样写:
int initACPI(void)
{
u8 *start = (u8 *)0xe0000;
u8 * const end = (u8 *)0xfffff; /*BIOS read-only area.*/
while(start < end){
u64 signature = *(u64 *)start;
if(signature == *(const u64 *)"RSD PTR ")
{
if(parseRSDP(start) == 0)
{
return 0;
}
}
start += 0x10;
}
u8 *ebda = (u8 *)(pointer)((*((u16 *)0x40E))*0x10);
if(ebda != (u8 *)0x9FC00)
return -1; /*Maybe EBDA doesn't exist.*/
u8 * const ebdaEnd = ebda + 0x3FF;
while(ebda < ebdaEnd){
u64 signature = *(u64 *)ebda;
if(signature == *(const u64 *)"RSD PTR ")
{
if(parseRSDP(ebda) == 0)
{
return 0;
}
}
}
printkInColor(0xff,0x00,0x00,"Can't find ACPI.\n");
return -1;
}
找到RSDP就调用parseRSDP分析.
ACPI的RSDP前20个字节的和一定为0,如果不是0就代表出错了(checksum).
static int parseRSDP(u8 *rsdp)
{
u8 sum = 0;
for(int i = 0;i < 20;++i)
{
sum += rsdp[i];
}
if(sum)
{
return -1;
}
printkInColor(0x00,0xff,0x00,"ACPI is found!!\n");
printk("ACPI Information:");
{
char oem[7];
memcpy((void *)oem,(const void *)rsdp + 9,sizeof(oem)/sizeof(char) - 1);
printk("OEM = %s,",oem);
}
u8 version = rsdp[15];
printk("Version = %d\n",(int)version);
if(version == 0)
{
u32 rsdt = *(u32 *)(rsdp + 16);
parseRSDT((ACPIHeader *)(pointer)rsdt);
}else if(version == 2)
{
u64 xsdt = *(u64 *)(rsdp + 24);
u32 rsdt = *(u32 *)(rsdp + 16);
if(xsdt)
parseXSDT((ACPIHeader *)(pointer)xsdt);
else
parseRSDT((ACPIHeader *)(pointer)rsdt);
}else{
printkInColor(0xff,0x00,0x00,"\nUnknow ACPI's version!!!\n");
return -1;
}
printk("\n");
return 0;
}
然后可以从中获取XSDT或者RSDT(取决于版本和xsdt是否为0).
(用到了GCC的0长度数组扩展,不占用任何内存空间,只表示一个地址)
typedef struct ACPIHeader{
u32 signature;
u32 length;
u8 version;
u8 checksum;
u8 oem[6];
u8 oemTableID[8];
u32 oemVersion;
u32 creatorID;
u32 creatorVersion;
u32 data[0];
} __attribute__ ((packed)) ACPIHeader;
然后调用parseXSDT,parseRSDT,这两个函数实际上是完全一样的,我们只贴出parseRSDT
static int parseRSDT(ACPIHeader *rsdt)
{
u32 *start = rsdt->data;
u32 *end = (u32 *)((u8 *)rsdt + rsdt->length);
while(start < end)
{
u32 dt = *(start++);
parseDT((ACPIHeader *)(pointer)dt);
}
return 0;
}
ACPIHeader后面存放各种各样设备的信息的地址,比如APIC、FACP、SDDT、HPET等,对于每个设备调用parseDT
static int parseDT(ACPIHeader *dt)
{
u32 signature = dt->signature;
char signatureString[5];
memcpy((void *)signatureString,(const void *)&signature,4);
signatureString[4] = '\0';
printk("Found device %s from ACPI.\n",signatureString);
if(signature == *(u32 *)"APIC")
parseApic((ACPIHeaderApic *)dt);
return 0;
}
不难理解,打印出设备的名字然后判断是否是APIC,是则调用parseApic,这就是我们最核心的代码了
static int parseApic(ACPIHeaderApic *apic)
{
char temp[20];
temp[0] = '0';temp[1] = 'x';
itoa(apic->localApicAddress,temp + 2,0x10,8,'0',1);
printk("\nLocal Apic Address: %s\n",temp);
localApicAddress = (u8 *)(pointer)apic->localApicAddress;
u8 *start = apic->data;
u8 *end = ((u8 *)apic) + apic->header.length;
while(start < end)
{
ApicHeader *apicHeader = (ApicHeader *)start;
u8 type = apicHeader->type;
u8 length = apicHeader->length;
switch(type)
{
case APIC_TYPE_LOCAL_APIC:
{
LocalApic *localApic = (LocalApic *)apicHeader;
printk("Found CPU: processor ID => %d, apic ID => %d \n",
(int)localApic->apicProcessorID,(int)localApic->apicID);
cpus[cpuCount++] = localApic->apicID;
break;
}
case APIC_TYPE_IO_APIC:
{
IOApic *ioApic = (IOApic *)apicHeader;
itoa(ioApic->ioApicAddress,temp + 2,0x10,8,'0',1);
printk("Found I/O Apic : I/O Apic ID => %d, I/O Apic Address => %s\n",
(int)ioApic->ioApicID,temp);
ioApicAddress = (u8 *)(pointer)ioApic->ioApicAddress;
break;
}
case APIC_TYPE_INTERRUPT_OVERRIDE:
break;
default:
printk("Unknow Apic information type:%d,length:%d.\n",(int)type,(int)length);
break;
}
start += length;
}
printk("\n");
return 0;
}
一些常量和类型:
typedef struct ACPIHeaderApic{
ACPIHeader header;
u32 localApicAddress;
u32 flags;
u8 data[0];
} __attribute__ ((packed)) ACPIHeaderApic;
typedef struct ApicHeader{
u8 type;
u8 length;
} __attribute__ ((packed)) ApicHeader;
typedef struct LocalApic{
ApicHeader header;
u8 apicProcessorID;
u8 apicID;
u32 flags;
} __attribute__ ((packed)) LocalApic;
typedef struct IOApic{
ApicHeader header;
u8 ioApicID;
u8 reserved;
u32 ioApicAddress;
u32 globalSystemInterruptBase;
} __attribute__ ((packed)) IOApic;
#define APIC_TYPE_LOCAL_APIC 0x0
#define APIC_TYPE_IO_APIC 0x1
#define APIC_TYPE_INTERRUPT_OVERRIDE 0x2
大体就是先保存并显示LocalApicAddress,然后遍历ApicHeader后面的data区域,如果发现CPU就记录下来,发现IOApic也记录了下来(这个就和本文没有关系了....)
遍历结束后cpuCount中就是CPU个数,而cpus中保存着每个CPU的LocalApicID,这样我们以后启动CPU就有了充足的资料还记得刚刚我们讨论的LocalApic的中断命令寄存器吗?现在就要派上用场了.
启动CPU首先要对每个CPU执行Init指令,查一下上面的表格,我们知道init是将ICR的Delivery Mode设置为101 (2进制)
其他位我们这样设置:
Trigger Mode(触发模式)设置为0(边缘触发,EDGE Trigger,这个我也不是很清楚为什么,这样设置可以用)
Destination Mode(目标模式)设置为0(Physical,物理模式,如果不这样设置就不能直接用LocalApicID)
Destination Shorthand(目标速记)设置为0(No Shorthand,不使用速记,事实上我们可以设置为All Excluding Self,来一次性发往所有其他的CPU,但我们故意采用了相对麻烦的方法)
Level(高电平和低电平,这块我不清楚,设置为1可用)
Vector 不同Delivery Mode有不同作用,执行init时貌似不用,置为0
Destination Field结合上面我们设置的目标模式和目标速记,这个目标字段我们应该设置为目标CPU的LocalApicID<<24
知道了这个,下一步就是写入了,这是一个64位的寄存器,我们只能分两次写入,先写低位的话就开始执行了,后写的高位就没用了,所以我们要先写高位
高32位只有一个目标字段,于是:
localApicOut(0x310,0x1 << 24);
0x1就是我们要启动的CPU的LocalApicID
再写低32位
localApicOut(0x300,0x00004500);
0x310是IRC的高32位地址,0x300是低32位地址,不难理解吧
现在LocalApicID为1的CPU已经准备好了,现在我们来启动它
启动它就是要向他发送StartUp指令,这样Delivery Mode设置为110(二进制)
Startup的命令几乎和Init的完全一样,不同的就是Vector要设置成被启动CPU开始执行的物理地址/0x1000
localApicOut(0x310,0x1 << 24);
localApicOut(0x300,0x00004600 | 0x10);
这样一来,LocalApicID为0x1的CPU就已经启动了,从0x10 * 0x1000 的物理地址开始执行,注意这个CPU是在实模式中的,我们还要重新使这个CPU进入保护模式、长模式
结合我们刚才获得的cpus和cpuCount中的信息,我们就不难写出遍历所有CPU并启动的代码了,要注意判断这个CPU是不是已经启动运行的那个"主"CPU
处理器间中断的发送和上面很类似,只要将Vector设置为要发送的中断号,将Delivery Mode设置为0,就可以发送处理器间中断了
利用这个特性我们还可以将Delivery Mode设置成NMI来发送不可屏蔽中断,无论目标CPU正在干什么都会执行不可屏蔽中断(2号中断)的处理程序,实现了不可屏蔽中断的可编程化,但要注意,这种情况下Vector也是没用的,不能通过改变Vector来改变不可屏蔽中断的向量号
(LOCAL)APIC文档:Intel® 64 Architecture x2APIC Specification
http://www.intel.com/content/dam/doc/specification-update/64-architecture-x2apic-specification.pdf