物理内存探测

 

              在内核建立相应的内存管理区之前,先要进行物理内存的探测,获取相关的内存信息。对于X86架构,内核在void main()中调用detect_memory()来进行物理内存的探测。

void main(void)
{        
	...
	...
         /* Detect memory layout */
	detect_memory();
	...
	...
}


detect_memory()中分别通过调用0xE820H,0xE801H和0x88H从BIOS中获取内存布局和相关的信息。

int detect_memory(void)
{
	int err = -1;

	if (detect_memory_e820() > 0)
		err = 0;

	if (!detect_memory_e801())
		err = 0;

	if (!detect_memory_88())
		err = 0;

	return err;
}


这三个函数都通过int 0x15触发BIOS中断来获取相关信息

 

下面我们着重分析detect_memory_e820().在此之前先了解一个关键的数据结构--struct e820entry.该结构用来保存一个物理内存段的地址信息以及类型。

struct e820entry {
	__u64 addr;	/* start of memory segment */
	__u64 size;	/* size of memory segment */
	__u32 type;	/* type of memory segment */
} __attribute__((packed));

addr:该内存段的起始地址

size:该内存段的大小

type:该内存段的类型,可分为Usable (normal) RAM,Reserved - unusable,ACPI reclaimable memory,ACPI NVS memory,Area containing bad memory,要获取所有的内存段的信息,detect_memory_e820()通过一个do_while循环来不断触发int 0x15中断来获取每个内存段的信息,并且将这些信息保存在一个struct e820entry类型的数组中,即boot_params.e820_map,下面是相关代码

static int detect_memory_e820(void)
{
	int count = 0;
	struct biosregs ireg, oreg;
	struct e820entry *desc = boot_params.e820_map;
	static struct e820entry buf; /* static so it is zeroed */

	initregs(&ireg);
	ireg.ax  = 0xe820;
	ireg.cx  = sizeof buf;
	ireg.edx = SMAP;
	ireg.di  = (size_t)&buf;

	/*
	 * Note: at least one BIOS is known which assumes that the
	 * buffer pointed to by one e820 call is the same one as
	 * the previous call, and only changes modified fields.  Therefore,
	 * we use a temporary buffer and copy the results entry by entry.
	 *
	 * This routine deliberately does not try to account for
	 * ACPI 3+ extended attributes.  This is because there are
	 * BIOSes in the field which report zero for the valid bit for
	 * all ranges, and we don't currently make any use of the
	 * other attribute bits.  Revisit this if we see the extended
	 * attribute bits deployed in a meaningful way in the future.
	 */

	do {
		intcall(0x15, &ireg, &oreg); /*触发0x15中断*/
		ireg.ebx = oreg.ebx; /* for next iteration... */

		/* BIOSes which terminate the chain with CF = 1 as opposed
		   to %ebx = 0 don't always report the SMAP signature on
		   the final, failing, probe. */
		if (oreg.eflags & X86_EFLAGS_CF)
			break;

		/* Some BIOSes stop returning SMAP in the middle of
		   the search loop.  We don't know exactly how the BIOS
		   screwed up the map at that point, we might have a
		   partial map, the full map, or complete garbage, so
		   just return failure. */
		if (oreg.eax != SMAP) {
			count = 0;
			break;
		}

		*desc++ = buf;/*保存获取的内存段信息*/
		count++;      /*获取的内存段数目加1*/
	} while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));

	return boot_params.e820_entries = count;
}


其他两个函数还没弄太明白,只知道是用来获取扩展内存的大小的~其中0x88H获取的内存上限为64M(以1K为单位),而0xe801可以获取到64M以上的内存(以64K为单位),它们也将获取的信息保存在boot_params中以供后用。

       在start_kernel()-->setup_arch()中,我们可以看到很多与架构相关的初始化工作,接下来我们选择几个与内存管理相关的关键函数进行分析。

     

void __init setup_arch(char **cmdline_p)
{
	...
	...
	setup_memory_map();
	...
	...	 
	max_pfn = e820_end_of_ram_pfn(); /*找到最大的页框编号*/
	...
	/* max_low_pfn get updated here */
	find_low_pfn_range();/*设定高、低内存的分界线*/
	...
	...	
}



首先来看setup_memory_map().

 

void __init setup_memory_map(void)
{
	char *who;

	who = x86_init.resources.memory_setup();
	memcpy(&e820_saved, &e820, sizeof(struct e820map));
	printk(KERN_INFO "BIOS-provided physical RAM map:\n");
	e820_print_map(who);
}


该函数中,通过结构体x86_init_resources调用memory_setup(),在x86_init.c中,我们可以看到该函数指针的初始化

struct x86_init_ops x86_init __initdata = {

	.resources = {
		.probe_roms		= x86_init_noop,
		.reserve_resources	= reserve_standard_io_resources,
		.memory_setup		= default_machine_specific_memory_setup,
	},

跟踪找到default_machine_specific_memory_setup(),

 
char *__init default_machine_specific_memory_setup(void)
{
	char *who = "BIOS-e820";
	u32 new_nr;
	/*
	 * Try to copy the BIOS-supplied E820-map.
	 *
	 * Otherwise fake a memory map; one section from 0k->640k,
	 * the next section from 1mb->appropriate_mem_k
	 */
	new_nr = boot_params.e820_entries;
	sanitize_e820_map(boot_params.e820_map,  /*消除重叠的内存段*/
			ARRAY_SIZE(boot_params.e820_map),
			&new_nr);
	boot_params.e820_entries = new_nr;
	/*将内存布局的信息从boot_params.e820_map拷贝到struct e820map e820*/
	if (append_e820_map(boot_params.e820_map, boot_params.e820_entries)
	  < 0) {
		u64 mem_size;

		/* compare results from other methods and take the greater */
		if (boot_params.alt_mem_k
		    < boot_params.screen_info.ext_mem_k) {
			mem_size = boot_params.screen_info.ext_mem_k;
			who = "BIOS-88";
		} else {
			mem_size = boot_params.alt_mem_k;
			who = "BIOS-e801";
		}

		e820.nr_map = 0;
		e820_add_region(0, LOWMEMSIZE(), E820_RAM);
		e820_add_region(HIGH_MEMORY, mem_size << 10, E820_RAM);
	}

	/* In case someone cares... */
	return who;
}

可以看到该函数主要完成两个功能:

1.消除内存段的重叠部分

2.将内存布局信息从boot_params.e820_map拷贝到e820中

跟踪append_e820_map()-->__append_e820_map()

static int __init __append_e820_map(struct e820entry *biosmap, int nr_map)
{
	while (nr_map) {
		u64 start = biosmap->addr;
		u64 size = biosmap->size;
		u64 end = start + size;
		u32 type = biosmap->type;

		/* Overflow in 64 bits? Ignore the memory map. */
		if (start > end)
			return -1;

		e820_add_region(start, size, type);

		biosmap++;
		nr_map--;
	}
	return 0;
}


其中传给biosmap的参数为boot_params.e820_map,即内存布局的起始地址,传给nr_map的参数为boot_params.e820_entries,即获取的内存段的数目。在该函数中,对每个段调用e820_add_region,来看看这个函数

void __init e820_add_region(u64 start, u64 size, int type)
{
	__e820_add_region(&e820, start, size, type);
}

 

static void __init __e820_add_region(struct e820map *e820x, u64 start, u64 size,
					 int type)
{
	int x = e820x->nr_map;

	if (x >= ARRAY_SIZE(e820x->map)) {
		printk(KERN_ERR "Ooops! Too many entries in the memory map!\n");
		return;
	}

	e820x->map[x].addr = start;
	e820x->map[x].size = size;
	e820x->map[x].type = type;
	e820x->nr_map++;
}


最终由__e820_add_region()函数将内存段的信息保存到e820的map数组中。

下面再来看看e820_end_of_ram_pfn(),该函数用来遍历所有内存段的页框,找到低端内存的最大页框编号

unsigned long __init e820_end_of_ram_pfn(void)
{
	return e820_end_pfn(MAX_ARCH_PFN, E820_RAM);
}


E820_RAM代表可用内存

MAX_ARCH_PFN的定义如下

#ifdef CONFIG_X86_32
# ifdef CONFIG_X86_PAE
#  define MAX_ARCH_PFN		(1ULL<<(36-PAGE_SHIFT))
# else
#  define MAX_ARCH_PFN		(1ULL<<(32-PAGE_SHIFT))
# endif
#else /* CONFIG_X86_32 */
# define MAX_ARCH_PFN MAXMEM>>PAGE_SHIFT
#endif

 

这里可以看到如果定义了CONFIG_X86_32,那么在没使用PAE的情况下,MAX_ARCH_PFN的值就为4GB内存对应的页框号,而如果定义了则MAX_ARCH_PFN的值为MAXMEM对应的页框号,我们来看MAXMEM的定义:

#define MAXMEM	(VMALLOC_END - PAGE_OFFSET - __VMALLOC_RESERVE)


这里面的宏都和线性地址的最后1GB有关,其中__VANALLOC_RESERVE为128M,下图说明了第4GB的内存划分

结合这个图我们可以得出MAXMEM为一个略小于896M的值(896M-8K-4M-4M),即略小于低端内存的上限,高端内存的起始地址,这样是为了避免某些情况下产生的OOPS。

走得貌似有点远了,再回到e820_end_of_ram_pfn(),跟踪e820_end_pfn(MAX_ARCH_PFN, E820_RAM):

static unsigned long __init e820_end_pfn(unsigned long limit_pfn, unsigned type)
{
	int i;
	unsigned long last_pfn = 0;
	unsigned long max_arch_pfn = MAX_ARCH_PFN;

	for (i = 0; i < e820.nr_map; i++) {  /*循环遍历内存布局数组*/
		struct e820entry *ei = &e820.map[i];
		unsigned long start_pfn;
		unsigned long end_pfn;

		if (ei->type != type)
			continue;

		start_pfn = ei->addr >> PAGE_SHIFT;
		end_pfn = (ei->addr + ei->size) >> PAGE_SHIFT;

		if (start_pfn >= limit_pfn)/*起始地址大于MAX_ARCH_PFN,无视之*/
			continue;
		if (end_pfn > limit_pfn) { /*结束地址大于MAX_ARCH_PFN则直接最大页框编号设为MAX_ARCH_PFN*/
									
			last_pfn = limit_pfn;
			break;
		}
		if (end_pfn > last_pfn)    /*该内存段的末地址大于之前找到的最大页框编号,
								则重置最大页框编号*/
			last_pfn = end_pfn;
	}

	if (last_pfn > max_arch_pfn)
		last_pfn = max_arch_pfn;

	printk(KERN_INFO "last_pfn = %#lx max_arch_pfn = %#lx\n",
			 last_pfn, max_arch_pfn);
	return last_pfn;
}

 

最后分析一个函数,setup_arch()-->find_low_pfn_range().该函数用来划分低端内存和高端内存的界限,也可以说是确定高端内存的起始地址。再看这个函数之前,还得先看几个宏定义

#define MAXMEM_PFN	PFN_DOWN(MAXMEM)
#define PFN_UP(x)	(((x) + PAGE_SIZE-1) >> PAGE_SHIFT)
#define PFN_DOWN(x)	((x) >> PAGE_SHIFT)

可以看出

PFN_DOWN(x)计算出地址值x对应的页框的编号

PFN_UP(x)计算出地址值对应的页框的下一个页框的编号

MAXMEM_PFN为MAXMEM对应的页框编号

再看具体的代码:

void __init find_low_pfn_range(void)
{
	/* it could update max_pfn */

	if (max_pfn <= MAXMEM_PFN)    /*实际物理内存小于等于低端内存896M*/
		lowmem_pfn_init();
	else			      /*实际的物理内存大于896M*/
		highmem_pfn_init();
}


当实际的物理内存小于等于低端内存时,由lowmem_pfn_init()进行分配

void __init lowmem_pfn_init(void)
{
	/* max_low_pfn is 0, we already have early_res support */
	/*将分界线初始化为实际物理内存的最大页框号,由于系统的内存小于896M,
	所以全部内存为低端内存,如需要高端内存,则从中分一部分出来进行分配*/
	max_low_pfn = max_pfn;

	if (highmem_pages == -1)
		highmem_pages = 0;
#ifdef CONFIG_HIGHMEM  /*如果用户定义了HIGHMEM,即需要分配高端内存*/
	if (highmem_pages >= max_pfn) {       /*如果高端内存的页起始地址>=最大页框号,则无法分配*/
		printk(KERN_ERR MSG_HIGHMEM_TOO_BIG,
			pages_to_mb(highmem_pages), pages_to_mb(max_pfn));
		highmem_pages = 0;
	}
	if (highmem_pages) {
		/*这个条件保证低端内存不能小于64M*/
		if (max_low_pfn - highmem_pages < 64*1024*1024/PAGE_SIZE) {
			printk(KERN_ERR MSG_LOWMEM_TOO_SMALL,
				pages_to_mb(highmem_pages));
			highmem_pages = 0;
		}
		max_low_pfn -= highmem_pages; /*设定好低、高端内存的分界线*/
	}
#else
	if (highmem_pages)
		printk(KERN_ERR "ignoring highmem size on non-highmem kernel!\n");
#endif
}


 

当实际的物理内存大于896M,由highmem_pfn_init()进行分配

void __init highmem_pfn_init(void)
{
	max_low_pfn = MAXMEM_PFN; /*设定高端内存和低端内存的分界线*/
	
								
	if (highmem_pages == -1)  /*未设定高端内存的页框数*/
		highmem_pages = max_pfn - MAXMEM_PFN;  /*默认为最大页框数减去MAXMEM_PFN*/

	if (highmem_pages + MAXMEM_PFN < max_pfn)      /*高端内存页框数加上MAXMEM_PFN小于最大页框数*/
		max_pfn = MAXMEM_PFN + highmem_pages;  /*将最大页框数下调到前两者的和*/

	if (highmem_pages + MAXMEM_PFN > max_pfn){     /*申请的高端内存超过范围则不分配*/
		printk(KERN_WARNING MSG_HIGHMEM_TOO_SMALL,
			pages_to_mb(max_pfn - MAXMEM_PFN),
			pages_to_mb(highmem_pages));
		highmem_pages = 0;
	}
#ifndef CONFIG_HIGHMEM
	/* Maximum memory usable is what is directly addressable */
	printk(KERN_WARNING "Warning only %ldMB will be used.\n", MAXMEM>>20);
	if (max_pfn > MAX_NONPAE_PFN)
		printk(KERN_WARNING "Use a HIGHMEM64G enabled kernel.\n");
	else
		printk(KERN_WARNING "Use a HIGHMEM enabled kernel.\n");
	max_pfn = MAXMEM_PFN;
#else /* !CONFIG_HIGHMEM */
#ifndef CONFIG_HIGHMEM64G
	if (max_pfn > MAX_NONPAE_PFN) {
		max_pfn = MAX_NONPAE_PFN;
		printk(KERN_WARNING MSG_HIGHMEM_TRIMMED);
	}
#endif /* !CONFIG_HIGHMEM64G */
#endif /* !CONFIG_HIGHMEM */
}




至此,已将内存探测和高低端内存的划分分析完了,接下来再分析管理区的初始化。




  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 内存SPD(Serial Presence Detect)文件是一种包含有关特定内存模块信息的文件。它是一个存储在内存模块上的EEPROM芯片中的数据。下面是内存SPD文件内容的规范: 1. 包含制造商信息:SPD文件应该包含制造商的名称、地址和联系信息。这使得用户可以联系制造商获取更多有关内存模块的信息。 2. 内存模块规格:SPD文件应该包含有关内存模块的规格信息,例如容量、速度等。这些规格信息有助于用户确定内存模块是否与其系统兼容。 3. 时序参数:SPD文件还应包含有关内存模块时序的信息。这些信息描述了内存读写数据的时钟速度、延迟等参数。根据这些参数,系统可以正确配置内存控制器以实现最佳性能。 4. 温度传感器数据:现代内存模块通常带有温度传感器,以监控内存模块的温度。SPD文件应该包含有关温度传感器数据的信息,例如可操作的温度范围和当前温度。 5. 特殊功能支持:某些内存模块具有特殊功能,例如ECC(错误检测与纠正)或XMP(延伸内存配置文件)。SPD文件应包含有关这些功能的信息,以便系统可以正确配置和使用它们。 6. 校验和:为了确保SPD文件的完整性,文件中应包含一个校验和。校验和是通过对文件内容进行计算生成的值,用于验证文件的完整性和准确性。 总结而言,内存SPD文件内容规范应包含制造商信息、内存模块规格、时序参数、温度传感器数据、特殊功能支持以及校验和。这些信息有助于系统正确配置和使用内存模块,以提高性能和稳定性。 ### 回答2: 内存 SPD 文件是一种记录着内存模块制造商、规格和其他相关信息的特定文档。SPD 是 Serial Presence Detect 的缩写,意为串行存在探测。SPD 文件的内容应遵循一定规范,以确保可靠性和互操作性。 内存 SPD 文件的内容规范通常包括以下几个方面: 1. 制造商信息:SPD 文件应包含内存模块的制造商名称和其他相关信息,例如联系方式和制造日期。 2. 模块规格:SPD 文件应提供内存模块的物理规格,包括尺寸、插槽类型和电源要求等信息。这有助于确保内存模块与主板和其他硬件设备的兼容性。 3. SPD 版本:SPD 文件应指明采用的 SPD 规范版本,以确保读取和解析 SPD 数据的兼容性。 4. 内存参数:SPD 文件应包含详细的内存参数信息,例如模块类型、容量、时序参数和频率等。这些参数是重要的,因为它们决定了内存模块的性能和兼容性。 5. 扩展信息:一些 SPD 文件可能包含扩展信息,如内存模块的序列号、厂商特定标识和选项设置等。这些信息通常是可选的,但对于一些特殊需求的应用可能会有一定的重要性。 通过遵循内存 SPD 文件的内容规范,系统和软件开发人员能够准确获取有关内存模块的信息,从而确保系统的稳定性和性能。同时,SPD 文件还有助于内存模块制造商在市场上推出兼容的产品,并提供一致的标准供参考。 ### 回答3: 内存SPD文件内容规范是指内存模块中的SPD(Serial Presence Detect)芯片中存储的信息的格式和规范。 SPD文件内容规范包括以下几个方面: 1. 芯片总体信息:SPD文件中会包含内存模块的基本信息,如制造商、型号、版本号等。 2. 整体参数:SPD文件中会列出内存模块的总容量、时序信息以及电气特性等。这些信息包括内存模块的列数、行数、刷新周期等。 3. 芯片特性:SPD文件还会包括内存模块的特性信息,如内存类型(如DDR4、DDR3等)、频率、工作电压和时序设置等。这些信息对于计算机系统中的内存识别和配置非常重要。 4. 校验和:SPD文件中还会有一个校验和字段,用于校验数据的完整性和正确性。这个校验和字段通过对SPD中的各个数据进行计算得出,如果校验和不匹配,可能意味着数据被篡改或损坏。 SPD文件内容规范的制定是为了确保内存模块的信息能够被计算机系统正确读取和识别。通过遵循SPD文件内容规范,计算机系统可以在启动时读取内存模块中的信息,并根据该信息来正确配置内存的工作参数,以保证系统的正常运行。 总之,内存SPD文件内容规范定义了内存模块中SPD芯片中存储的信息的格式和规范,以确保计算机系统能够正确读取和配置内存模块。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值