1.4.1 MP Table
操作系统有两种获取处理器信息的方式:一种是Intel的MultiProcessor Specification(后续简称MP Spec)约定的方式;另外一种是ACPI MADT(Multiple APIC Description Table)约定的方式。MP Spec约定的核心数据结构包括两部分:MP Floating Pointer Structure(后续简称MPF)和MP Configuration Table(后续简称MP Table)的地址,如图1-6所示。
图1-6 MP Configuration数据结构
处理器的信息记录于MP Table,MP Table包含多个entry,entry分为不同的类型,有的entry是描述处理器信息的,有的entry是描述总线信息的,有的entry是描述中断信息的,等等。每个处理器类型的entry记录了一个处理器的信息。
而MP Table地址记录在MPF中,MP标准约定MPF可以存放在如下几个位置:
1)存放在BIOS扩展数据区的前1KB内。
2)系统基础内存最高的1KB内。比如对于640KB内存,那么MPF存放在639KB~640KB内。
3)存放在主板BIOS区域,即0xF0000~0xFFFFF之间。
操作系统启动时,将在MP Spec约定的位置搜索MPF。那么操作系统如何确定何处内存为MPF呢?根据MP Spec约定,MPF起始的4字节为_MP_。在定位了MPF后,操作系统就可以顺藤摸瓜,找到MP Table,从中获取处理器信息。
1.VMM准备处理器信息
kvmtool将MP Table放置在了主板BIOS所在的区域(0xF0000~0xFFFFF),在BIOS实际占据地址的末尾处。kvmtool首先申请了一块内存区,在其中组织MPF和MP Table,然后将组织好的数据结构复制到Guest中主板BIOS所在的区域,代码如下:
commit 0c7c14a747e9eb2c3cacef60fb74b0698c9d3adf kvm tools: Add MP tables support kvmtool.git/mptable.c 01 void mptable_setup(struct kvm *kvm, unsigned int ncpus) 02 { 03 unsigned long real_mpc_table, size; 04 struct mpf_intel *mpf_intel; 05 struct mpc_table *mpc_table; 06 struct mpc_cpu *mpc_cpu; 07 struct mpc_bus *mpc_bus; 08 … 09 void *last_addr; 10 … 11 real_mpc_table = ALIGN(MB_BIOS_BEGIN + bios_rom_size, 16); 12 … 13 mpc_table = calloc(1, MPTABLE_MAX_SIZE); 14 … 15 MPTABLE_STRNCPY(mpc_table->signature, MPC_SIGNATURE); 16 MPTABLE_STRNCPY(mpc_table->oem, MPTABLE_OEM); 17 … 18 mpc_cpu = (void *)&mpc_table[1]; 19 for (i = 0; i < ncpus; i++) { 20 mpc_cpu->type = MP_PROCESSOR; 21 mpc_cpu->apicid = i; 22 … 23 mpc_cpu++; 24 } 25 26 last_addr = (void *)mpc_cpu; 27 … 28 mpc_bus = last_addr; 29 mpc_bus->type = MP_BUS; 30 mpc_bus->busid = pcibusid; 31 … 32 last_addr = (void *)&mpc_bus[1]; 33 … 34 mpf_intel = (void *)ALIGN((unsigned long)last_addr, 16); 35 … 36 mpf_intel->physptr = (unsigned int)real_mpc_table; 37 … 38 size = (unsigned long)mpf_intel + sizeof(*mpf_intel) – 39 (unsigned long)mpc_table; 40 … 41 memcpy(guest_flat_to_host(kvm, real_mpc_table), 42 mpc_table, size); 43 … 44 }
函数mptable_setup首先申请了一块临时的内存区,见第13行代码。然后开始组织结构体MP Table,其中第15~17行代码是组织header部分。
紧接在header后面的就是各种entry了,首先是处理器类型的entry,见第18~24行代码。MP Spec约定,在处理器类型的entry中,需要提供处理器对应的LAPIC的ID,作为发送核间中断时的目的地址。根据代码可见,0号CPU对应的LAPIC的ID为0,1号CPU对应的LAPIC的ID为1,以此类推。
在处理器之后,还有各种总线、中断等entry,见代码第28~33行,这里我们不一一讨论了。
在组织完MP Table后,函数mptable_setup开始组织MPF。MPF紧邻MP Table,见第36行代码,其中的字段physptr指向了MP Table。
最后,将组织好的MPF和MP Table复制到主板BIOS区域,见第41、42行代码。其中,real_mpc_table是Guest中指向主板BIOS占据地址的结尾,见第11行代码。这个地址是Guest的地址空间,因此如果kvmtool需要访问,需要调用函数guest_flat_to_host将其转换为对应的Host的地址,见第41行代码。复制的区域包括整个MP Table和MPF,见第38、39行代码。
2.Guest读取处理器信息
虚拟机启动后,将扫描MP Spec约定的存放MPF的位置:
linux-1.3.31/arch/i386/mm/init.c unsigned long paging_init(unsigned long start_mem, …) { … smp_scan_config(0x0,0x400); /* Scan the bottom 1K for … */ … smp_scan_config(639*0x400,0x400); /* Scan the top 1K of …*/ smp_scan_config(0xF0000,0x10000); /* Scan the 64K … */ … }
操作系统如何确定某处内存存放的为MPF呢?根据MP Spec约定,MPF起始的4字节为_MP_,如图1-7所示。
图1-7 MPF格式
操作系统只要在MP Spec约定的几个区域内,以4字节为单位搜索到关键字_MP_,就可以认定这是结构体MPF。在下面的代码中,函数smp_scan_config以4字节为单位,地毯式匹配关键字_MP_,其中宏SMP_MAGIC_IDENT就是_MP_。当找到MPF后,如果其中指向MP Table的字段mpf_physptr非空,则调用函数smp_read_mpc遍历MP Table:
linux-1.3.31/arch/i386/kernel/smp.c void smp_scan_config(unsigned long base, unsigned long length) { unsigned long *bp=(unsigned long *)base; struct intel_mp_floating *mpf; … while(length>0) { if(*bp==SMP_MAGIC_IDENT) { mpf=(struct intel_mp_floating *)bp; if(mpf->mpf_length==1 && !mpf_checksum((unsigned char *)bp,16) && mpf->mpf_specification==1) { … if(mpf->mpf_physptr) smp_read_mpc((void *)mpf->mpf_physptr); … } } bp+=4; length-=16; } }
内核中定义了一个bitmask类型的变量cpu_present_map,比如发现了0号CPU,则设置变量cpu_present_map的第0位为1,之后根据cpu_present_map中标识的位启动对应的CPU。所以,函数smp_read_mpc的主要作用就是遍历MP Table,找出具体的CPU信息,将cpu_present_map中对应的位置位,记录下系统中有哪些处理器。
在前面kvmtool设置MP Table时,CPU entry中设置了LAPIC的ID,并且是从0开始的,0号CPU对应的LAPIC的ID为0,1号CPU对应的LAPIC的ID为1,所以,使用LAPIC的ID作为CPU的索引即可。函数smp_read_mpc中查找CPU的代码如下:
linux-1.3.31/arch/i386/kernel/smp.c static int smp_read_mpc(struct mp_config_table *mpc) { … while(count<mpc->mpc_length) { switch(*mpt) { case MP_PROCESSOR: { struct mpc_config_processor *m= (struct mpc_config_processor *)mpt; if(m->mpc_cpuflag&CPU_ENABLED) { … cpu_present_map|=(1<<m->mpc_apicid); } mpt+=sizeof(*m); count+=sizeof(*m); break; } … } } … }