在内核建立相应的内存管理区之前,先要进行物理内存的探测,获取相关的内存信息。对于X86架构,内核在void main()中调用detect_memory()来进行物理内存的探测。
-
void main(void)
-
{
-
...
-
...
-
-
detect_memory();
-
...
-
...
-
}
detect_memory()中分别通过调用0xE820H,0xE801H和0x88H从BIOS中获取内存布局和相关的信息。
-
"font-size:12px;">int detect_memory(void)
-
{
-
int err = -1;
-
-
if (detect_memory_e820() > 0)
-
"font-size:12px;"> err = 0;
-
-
if (!detect_memory_e801())
-
err = 0;
-
-
if (!detect_memory_88())
-
err = 0;
-
-
return err;
-
}
-
这三个函数都通过int 0x15触发BIOS中断来获取相关信息
下面我们着重分析detect_memory_e820().在此之前先了解一个关键的数据结构--struct e820entry.该结构用来保存一个物理内存段的地址信息以及类型。
-
"font-size:12px;">struct e820entry {
-
__u64 addr;
-
__u64 size;
-
__u32 type;
-
} __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,下面是相关代码
-
"font-size:12px;">static int detect_memory_e820(void)
-
{
-
int count = 0;
-
struct biosregs ireg, oreg;
-
struct e820entry *desc = boot_params.e820_map;
-
static struct e820entry buf;
-
-
initregs(&ireg);
-
ireg.ax = 0xe820;
-
ireg.cx = sizeof buf;
-
ireg.edx = SMAP;
-
ireg.di = (size_t)&buf;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
do {
-
intcall(0x15, &ireg, &oreg);
-
ireg.ebx = oreg.ebx;
-
-
-
-
-
if (oreg.eflags & X86_EFLAGS_CF)
-
break;
-
-
-
-
-
-
-
if (oreg.eax != SMAP) {
-
count = 0;
-
break;
-
}
-
-
*desc++ = buf;
-
count++;
-
} 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()中,我们可以看到很多与架构相关的初始化工作,接下来我们选择几个与内存管理相关的关键函数进行分析。
-
"font-size:12px;">void __init setup_arch(char **cmdline_p)
-
{
-
...
-
...
-
setup_memory_map();
-
...
-
...
-
max_pfn = e820_end_of_ram_pfn();
-
...
-
-
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中,我们可以看到该函数指针的初始化
-
"font-size:12px;">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(),
-
"font-size:12px;">char *__init default_machine_specific_memory_setup(void)
-
{
-
char *who = "BIOS-e820";
-
u32 new_nr;
-
-
-
-
-
-
-
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;
-
-
if (append_e820_map(boot_params.e820_map, boot_params.e820_entries)
-
< 0) {
-
u64 mem_size;
-
-
-
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);
-
}
-
-
-
return who;
-
}
可以看到该函数主要完成两个功能:
1.消除内存段的重叠部分
2.将内存布局信息从boot_params.e820_map拷贝到e820中
跟踪append_e820_map()-->__append_e820_map()
-
"font-size:12px;">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;
-
-
-
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,来看看这个函数
-
"font-size:12px;">void __init e820_add_region(u64 start, u64 size, int type)
-
{
-
__e820_add_region(&e820, start, size, type);
-
}
-
"font-size:12px;">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(),该函数用来遍历所有内存段的页框,找到低端内存的最大页框编号
-
"font-size:12px;">unsigned long __init e820_end_of_ram_pfn(void)
-
{
-
return e820_end_pfn(MAX_ARCH_PFN, E820_RAM);
-
}
E820_RAM代表可用内存
MAX_ARCH_PFN的定义如下
-
"font-size:12px;">#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的定义:
-
"font-size:12px;">#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):
-
"font-size:12px;">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)
-
continue;
-
if (end_pfn > limit_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().该函数用来划分低端内存和高端内存的界限,也可以说是确定高端内存的起始地址。再看这个函数之前,还得先看几个宏定义
-
"font-size:12px;">#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对应的页框编号
再看具体的代码:
-
"font-size:12px;">void __init find_low_pfn_range(void)
-
{
-
-
-
if (max_pfn <= MAXMEM_PFN)
-
lowmem_pfn_init();
-
else
-
highmem_pfn_init();
-
}
当实际的物理内存小于等于低端内存时,由lowmem_pfn_init()进行分配
-
class="cpp" name="code">"font-size:12px;">void __init lowmem_pfn_init(void)
-
{
-
-
-
-
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) {
-
-
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
-
}
-
-
-
-
"font-size:18px">当实际的物理内存大于896M,由highmem_pfn_init()进行分配
-
class
="cpp" name="code">"font-size:12px;">void __init highmem_pfn_init(void)
-
{
-
max_low_pfn = MAXMEM_PFN;
-
-
-
if (highmem_pages == -1)
-
highmem_pages = max_pfn - MAXMEM_PFN;
-
-
if (highmem_pages + MAXMEM_PFN < max_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
-
-
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 */
-
}
-
-
-
-
-
"font-size:18px">至此,已将内存探测和高低端内存的划分分析完了,接下来再分析管理区的初始化。
-
-
-
-
-