对称多处理器和处理器间中断的实现
来源:互联网 发布:纸牌屋经典片段 知乎 编辑:程序博客网 时间:2024/05/16 14:23
对称多处理器和处理器间中断是什么我这里就不解释了,网络上这部分内容比较多,随便一百度就有很多中文资料,我们来看一下他们是怎么实现的。
另外本文假定你已经具有了Local APIC的基础知识,至少要知道怎么写APIC的寄存器吧.
中断命令寄存器
要启动"主"处理器之外的任何一个处理器(事实上,处理器没有主次之分,除了启动之外),或者要发送一个处理器间中断,离不开的一个Local Apic寄存器就是中断命令寄存器(Interrupt Command Register,ICR).
这就是ICR的结构图,地址是0x310(高32位),0x300(低32位),比较重要的是最高8位存放的是LocalApicID,INIT,StartUp,NMI,最低8位存放的Vector
(有时候不是这样的,这里指的是本文用到的模式.)
获取处理器的个数和每个处理器的LocalApicID
如何获取一共有几个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).
ACPIHeader的定义是这样的:
(用到了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就有了充足的资料启动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
- 对称多处理器和处理器间中断的实现
- 对称多处理器技术介绍
- 对称多处理器系统-SMP
- 对称多处理器系统SMP
- SMP对称多处理器结构
- 操作系统之线程、对称多处理器和微内核--基础知识
- smp-symmetric multi-processor 对称多处理器
- ARM处理器中断处理的编程实现
- ARM处理器中断处理的编程实现
- 微型处理器的中断系统
- ARM处理器架构------可嵌套中断的实现
- 基带处理器和应用处理器的核间通信
- 对称多处理器 Cache写回策略:写一次法(write-one)
- kvm 虚拟化 SMP(对称多处理器)介绍及配置
- 基于Blackfin处理器实现对硬盘FAT32文件系统的操作
- Cortex-M3 处理器的咬尾中断
- gnu-ucos 的s3c2440处理器 中断部分
- arm处理器中断设置
- Android - 文件读写操作 总结
- iOS开发-Email的发送方法
- 打印菱形星号
- 黑马程序员——java基础加强之枚举
- GrADS环境变量(Environment Variables )的设置
- 对称多处理器和处理器间中断的实现
- 2013年11月-2014年5月规划
- 关于rt.jar的源码问题
- 几段关于const的代码
- Error Handling with exceptions
- 求所有个位、十位、百位的三次方加和等于这个数本身的数
- 在网站制作中随时可用的10个 HTML5 代码片段
- 抱怨IT公司人才缺乏?留住现有人才方是正途
- D-Link 615 C2 路由器固件烧写与配置