多CPU操作系统是如何得到cpu的信息的,具体源代码是什么样?
最近对多cpu的启动比较感兴趣,无头苍蝇一样的查资料,最终查到了这个标准,并结合Linux 2.4版本的源代码对比学习,现将学习心得贴出来供大家参考。
首先是多cpu结构,如下:
多cpu结构中,中断形式跟单cpu不一样了,采用APIC形式,每个cpu有一个单独的Local APIC,而Local APIC的编号就代表了这个cpu。
当开机后,通过一定算法,确定其中一个cpu为主cpu(BSP),其它cpu称为APs,如下:
当然,对于如何确定主cpu的算法,那是芯片制造者的工作,对于操作系统编写者,我们只需要知道,当BIOS初始完之后,这些已经完成了,BIOS把cpu的信息按照一定格式放到了内存中的某个地方,我们的任务就是把这些信息提取出来。
要提取信息,首先要了解这些信息的存放格式,首先是MP Floating Pointer Structure
这个结构里最重要的是PHYSICAL ADDRESS POINTER,它指向MP Configuration Table Header结构
而MP Configuration Table Header结构之后紧跟着Base MP Configuration Table Entries,数量是ENTRY COUNT。
Base MP Configuration Table Entries都是什么呢,有以下几种:
Entry Type字段是0就代表这是个cpu。所以,只要我们能找到MP Floating Pointer Structure,就能把cpu以及总线等信息找出来。
那么MP Floating Pointer Structure结构在哪呢。
我们知道,BIOS初始化完之后,cpu是处于实模式下,所以MP Floating Pointer Structure这些结构信息都应该存在1M空间内,具体来说,MP Floating Pointer Structure从下面这些地址去找:
做一些解释,a.首先是EBDA,有EBDA那之前肯定有BDA(BIOS Data Area),BDA位于0x400处,即中断向量表之后,EBDA的段地址位于0x40E处,比如我的电脑上运行bochs后0x40E处的值是0x9fc0,所以EBDA位于0x9fc0:0处,也就是639k处。
b.基本内存的最后1k。如果是1M内存,那么基本内存大小是640k,最后1k就是639k。但早期很多内存可能不到1M,那么基本内存也会变小,基本内存的大小可以通过int 0x12获得。当然,我们现在练习的环境不会碰到基本内存小于640k的情况了。
c.Rom区
我们在这三处查找,方法就是通过暴力搜索。MP Floating Pointer Structure的SIGNATURE是
"_MP_",我们按结构长度16字节16字节的对比,如果都找不到就不是多cpu系统。Linux 2.4版本关于这块的源代码有两个文件mpspec.h和mpparse.c。
我们先看mpparse.c中关于查找MP Floating Pointer Structure的代码
void __init find_intel_smp (void)
{
unsigned int address;
/*
* FIXME: Linux assumes you have 640K of base ram..
* this continues the error...
*
* 1) Scan the bottom 1K for a signature
* 2) Scan the top 1K of base RAM
* 3) Scan the 64K of bios
*/
if (smp_scan_config(0x0,0x400) ||
smp_scan_config(639*0x400,0x400) ||
smp_scan_config(0xF0000,0x10000))
return;
/*
* If it is an SMP machine we should know now, unless the
* configuration is in an EISA/MCA bus machine with an
* extended bios data area.
*
* there is a real-mode segmented pointer pointing to the
* 4K EBDA area at 0x40E, calculate and scan it here.
*
* NOTE! There are Linux loaders that will corrupt the EBDA
* area, and as such this kind of SMP config may be less
* trustworthy, simply because the SMP table may have been
* stomped on during early boot. These loaders are buggy and
* should be fixed.
*/
address = *(unsigned short *)phys_to_virt(0x40E);
address <<= 4;
smp_scan_config(address, 0x1000);
if (smp_found_config)
printk(KERN_WARNING "WARNING: MP table in the EBDA can be UNSAFE, contact linux-smp@vger.kernel.org if you experience SMP problems!\n");
linux 对这些地方进行搜索,搜索时用到一个函数smp_scan_config,如下
static int __init smp_scan_config (unsigned long base, unsigned long length)
{
unsigned long *bp = phys_to_virt(base);//base是实模式下的值,我们现在已经启用了分页,得用线性地址
struct intel_mp_floating *mpf;
Dprintk("Scan SMP from %p for %ld bytes.\n", bp,length);
if (sizeof(*mpf) != 16)
printk("Error: MPF size\n");
while (length > 0) {
mpf = (struct intel_mp_floating *)bp;
if ((*bp == SMP_MAGIC_IDENT) && //#define SMP_MAGIC_IDENT (('_'<<24)|('P'<<16)|('M'<<8)|'_')
(mpf->mpf_length == 1) &&
!mpf_checksum((unsigned char *)bp, 16) &&
((mpf->mpf_specification == 1)
|| (mpf->mpf_specification == 4)) ) {
smp_found_config = 1; //等于1表示找到了
printk("found SMP MP-table at %08lx\n",
virt_to_phys(mpf));
reserve_bootmem(virt_to_phys(mpf), PAGE_SIZE); //bootmem我还没研究,但大家知道这句就是把找到的MP Floating Pointer Structure信息存起来的意思
if (mpf->mpf_physptr)
reserve_bootmem(mpf->mpf_physptr, PAGE_SIZE);//存储 MP Configure Table Header以及之后的entries.
mpf_found = mpf;//mpf就指向了我们存储的MP Floating Pointer Structrure.以后通过它来操作
return 1;
}
bp += 4; //如果没找到,移动16字节到下一处
length -= 16;
}
return 0;
}
这里面有一个问题,也就是说这些地方的信息不能被破坏。其中c是处于rom,一般不会被破坏,但a,b是处于基本内存中。而0.11版本的loader模块会把基本内存中的信息都破坏掉。如果cpu信息存在这那就麻烦了。
随着操作系统的发展,loader模块逐渐和操作系统分开了。有人专门写boot loader,现在linux最著名的boot loader就是grub,我们安装window也经常用到U深度之类loader。现在的loader其实也是一个保护模式的操作系统,只不过它做的不用那么复杂,带个文件系统就行(现在有些可以网络安装的那就再带个网络模块)。
有人专门写loader了,那么实际编写操作系统的人就不用在loader放太多精力了。所以现在的linux也就仅仅放了一个早期的支持软盘的boot模块(基本上用不上),实际上主要就靠专业的loader(目前就是grub)来负责了。
既然专业的loader来处理加载工作,那么这个loader就不能破坏我们上面所说的基本内存区域。所以linus说(mpparse.c)
NOTE! There are Linux loaders that will corrupt the EBDA
* area, and as such this kind of SMP config may be less
* trustworthy, simply because the SMP table may have been
* stomped on during early boot. These loaders are buggy and
* should be fixed.
找到了cup 信息,我们继续来分析
ENTRY TYPE是0就表示这是个cpu了,LOCAL APIC ID代表了这个cpu,EN位0表示不可用,1表示可用,BP位是1表示这个cpu是主cpu(BSP),为0则是AP。SIGNATURE代表了具体的CPU家族信息,FEATUREFLAGS代表对一些特性是否支持。
但Intel Multiprocessor Specification v1.4只支持到9,后面的都保留,我们再看2.4中的代码
static void __init MP_processor_info (struct mpc_config_processor *m)
{
int ver;
if (!(m->mpc_cpuflag & CPU_ENABLED))
return;
printk("Processor #%d %s APIC version %d\n",
m->mpc_apicid,
mpc_family( (m->mpc_cpufeature & CPU_FAMILY_MASK)>>8 ,
(m->mpc_cpufeature & CPU_MODEL_MASK)>>4),
m->mpc_apicver);
if (m->mpc_featureflag&(1<<0))
Dprintk(" Floating point unit present.\n");
if (m->mpc_featureflag&(1<<7))
Dprintk(" Machine Exception supported.\n");
if (m->mpc_featureflag&(1<<8))
Dprintk(" 64 bit compare & exchange supported.\n");
if (m->mpc_featureflag&(1<<9))
Dprintk(" Internal APIC present.\n");
if (m->mpc_featureflag&(1<<11))
Dprintk(" SEP present.\n");
if (m->mpc_featureflag&(1<<12))
Dprintk(" MTRR present.\n");
if (m->mpc_featureflag&(1<<13))
Dprintk(" PGE present.\n");
if (m->mpc_featureflag&(1<<14))
Dprintk(" MCA present.\n");
if (m->mpc_featureflag&(1<<15))
Dprintk(" CMOV present.\n");
if (m->mpc_featureflag&(1<<16))
Dprintk(" PAT present.\n");
if (m->mpc_featureflag&(1<<17))
Dprintk(" PSE present.\n");
if (m->mpc_featureflag&(1<<18))
Dprintk(" PSN present.\n");
if (m->mpc_featureflag&(1<<19))
Dprintk(" Cache Line Flush Instruction present.\n");
/* 20 Reserved */
if (m->mpc_featureflag&(1<<21))
Dprintk(" Debug Trace and EMON Store present.\n");
if (m->mpc_featureflag&(1<<22))
Dprintk(" ACPI Thermal Throttle Registers present.\n");
if (m->mpc_featureflag&(1<<23))
Dprintk(" MMX present.\n");
if (m->mpc_featureflag&(1<<24))
Dprintk(" FXSR present.\n");
if (m->mpc_featureflag&(1<<25))
Dprintk(" XMM present.\n");
if (m->mpc_featureflag&(1<<26))
Dprintk(" Willamette New Instructions present.\n");
if (m->mpc_featureflag&(1<<27))
Dprintk(" Self Snoop present.\n");
/* 28 Reserved */
if (m->mpc_featureflag&(1<<29))
Dprintk(" Thermal Monitor present.\n");
/* 30, 31 Reserved */
if (m->mpc_cpuflag & CPU_BOOTPROCESSOR) {
Dprintk(" Bootup CPU\n");
boot_cpu_id = m->mpc_apicid;
}
num_processors++;
if (m->mpc_apicid > MAX_APICS) {
printk("Processor #%d INVALID. (Max ID: %d).\n",
m->mpc_apicid, MAX_APICS);
return;
}
ver = m->mpc_apicver;
phys_cpu_present_map |= 1 << m->mpc_apicid;
/*
* Validate version
*/
if (ver == 0x0) {
printk("BIOS bug, APIC version is 0 for CPU#%d! fixing up to 0x10. (tell your hw vendor)\n", m->mpc_apicid);
ver = 0x10;
}
apic_version[m->mpc_apicid] = ver;
}
这时显然已经支持到29了,但我还没有找到这方面信息,不过从代码中也能看出来是什么了。
这次的学习就到这。