Intel MultiProcessor Specification v1.4学习心得

多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了,但我还没有找到这方面信息,不过从代码中也能看出来是什么了。

这次的学习就到这。

  • 27
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值