- 论坛徽章:
- 0
|
本帖最后由 71v5 于 2014-07-14 08:27 编辑
最近重新看了一下虚拟内存初始化过程,感觉函数getmemsize比较重要,值得我们简单分析一下,在初始化函数init386中被调用,主要完成下面的工作:
1:
初始化下面两个数组:- #define PHYSMAP_SIZE 32
- vm_paddr_t phys_avail[PHYSMAP_SIZE + 2];
- vm_paddr_t dump_avail[PHYSMAP_SIZE + 2];
复制代码 数组phys_avail用来描述系统中可用的物理内存区域,该数组中每两个相邻元素描述一个物理内存区域,分别标识相应物理内存区域的起始
物理地址和结尾物理地址,比如(phys_avail[0],phys_avail[1]),(phys_avail[2],phys_avail[3]),(phys_avail[4],phys_avail[5]),
(phys_avail[6],phys_avail[7]),(phys_avail[8],phys_avail[9])等等.但是位于(KERNLOAD,first)之间的物理内存不由数组phys_avail
来描述,KERNLOAD,first等参考下面的图1。
数组dump_avail和数组phys_avail类似,只不过数组dump_avail描述了(KERNLOAD,first)之间的物理内存。
数组dump_avail和数组phys_avail都不描述物理页框0,物理页框0由VM86使用,在后续虚拟内存的初始化阶段,都要使用上面的两个数组。
图1:
2:
调用函数pmap_bootstrap,该函数保留一部分内核虚拟地址区域和这些虚拟地址区域对应的页表项,函数pmap_bootstrap执行完后,内核虚拟地址空间结构如下:
图2:
[struct bootinfo的数据对象的bi_modulep成员]:
图3:
[函数getmemsize]:- /*********************************************************************
- * 参数描述:
- first:内核映像后第一个可用的物理内存地址。
- ***************************/
- 2149
- 2150 static void
- 2151 getmemsize(int first)
- 2152 {
- 2153 int has_smap, off, physmap_idx, pa_indx, da_indx;
- 2154 u_long physmem_tunable, memtest;
- 2155 vm_paddr_t physmap[PHYSMAP_SIZE];
- 2156 pt_entry_t *pte;
- 2157 quad_t dcons_addr, dcons_size;
- 2158 #ifndef XEN
- 2159 int hasbrokenint12, i;
- 2160 u_int extmem;
- 2161 struct vm86frame vmf;
- 2162 struct vm86context vmc;
- 2163 vm_paddr_t pa;
- 2164 struct bios_smap *smap, *smapbase, *smapend;
- 2165 u_int32_t smapsize;
- 2166 caddr_t kmdp;
- 2167 #endif
- 2168
- 2169 has_smap = 0;
- /* 2170-2177:XEN相关,这里忽略 */
- ..............................................................................................
- ..............................................................................................
- /* 2178-2189:XBOX相关,这里忽略 */
- ..............................................................................................
- ..............................................................................................
- 2190 bzero(&vmf, sizeof(vmf));
- 2191 bzero(physmap, sizeof(physmap));
- 2192 basemem = 0;
- /**********************************************************************************************************
- * Check if the loader supplied an SMAP memory map. If so,
- * use that and do not make any VM86 calls.
- 通常,可以利用BIOS的中断来获取所有已经安装的物理内存信息,至于BIOS怎么获取
- 物理内存信息,我们并不关心,我们关心的是freebsd9.2如何利用这些物理内存信息
- 建立后续在virtual memory system init阶段需要用到的数据结构,即数组dump_avail和数组phys_avail。
- 关于BIOS提供的这个中断,简要的描述如下:
- INT 15h, AX=E820h - Query System Address Map;
- Real mode only.
- This call returns a memory map of all the installed RAM, and of physical
- memory ranges reserved by the BIOS. The address map is returned by making
- successive calls to this API, each returning one "run" of physical address
- information. Each run has a type which dictates how this run of physical
- address range should be treated by the operating system.
- 可以参考这个里面的介绍"http://www.uruk.org/orig-grub/mem64mb.html".
- 获取的这个物理内存信息可以用下面的结构体来描述,不管这个物理内存信息由loader
- 提供,还是我们使用BIOS中断来获取,都是将物理内存信息保存在一个内存区域中,
- 这个内存区域可以看成是一个类型为struct bios_smap的数组,数组中的每个元素
- 用来描述一个物理内存区域,然后就可以通过(struct bios_smap *)来访问这个内存区域:
- struct bios_smap {
- u_int64_t base; /* 物理内存段的物理基地址,十进制表示 */
- u_int64_t length; /* 物理内存段的长度 单位Byte */
- u_int32_t type; /* 物理内存段的类型 8/
- } __packed;
- 成员type取值如下:
- #define SMAP_TYPE_MEMORY 1
- #define SMAP_TYPE_RESERVED 2
- #define SMAP_TYPE_ACPI_RECLAIM 3
- #define SMAP_TYPE_ACPI_NVS 4
- #define SMAP_TYPE_ACPI_ERROR 5
- 2200-2220:
- 如果loader已经使用BIOS中断获取了物理内存信息,那么就使用它。
- 2222-2257:
- 需要我们自己使用BIOS的中断来获取物理内存信息,在vm86模式下,这里将这部分忽略这部
- 分代码。
- 2200-2220:
- 假设looader已经使用BIOS中断获取了物理内存信息,物理内存信息可以通过boot2和
- loader建立的一个类型为struct bootinfo的数据对象的bi_modulep成员来访问。
- 看了一下boot2和loader的源代码,bi_modulep成员具体的含义不好用语言来描述,
- 用图更直观,参考上面图3。
-
- 2201-2205:
- preload_search_*等函数操作bi_modulep成员指向的内存区域包含的数据,这些函数
- 的实现比较简单,在"$FreeBSD: release/9.2.0/sys/kern/subr_module.c"可以找到。
- 2204:smapbase指向保存物理内存信息的内存区域的起始地址
- 2212:smapsize为这个内存区域的大小。
- 2213:smapend指向这个内存区域的结尾。
- 2214:将变量has_smap设置为1,表示已经获取到了物理内存信息。
- 我们将之前注册的212号系统调用简单修改一下,就可以将smapbase和smapend之间
- 物理内存信息形象化的输出,测试了一下,结果如下,这也验证了loader已经为我们获取了物理内存
- 信息:
- struct bios_smap cu_bios_smap[] = {
- {.base = 0,.length = 653312,.type = 1}, // 元素0
- {.base = 653312,.length = 2048,.type = 2},
- {.base = 901120,.length = 147456,.type = 2},
- {.base = 1048576,.length = 3219062784,.type = 1}, // 元素3
- {.base = 3220111360,.length = 61440,.type = 3},
- {.base = 3220172800,.length = 4096,.type = 4},
- {.base = 3220176896,.length = 1048576,.type = 1}, // 元素6
- {.base = 4026531840,.length = 134217728,.type = 2},
- {.base = 4273995776,.length = 65536,.type = 2},
- {.base = 4276092928,.length = 4096,.type = 2},
- {.base = 4294836224,.length = 131072,.type = 2},
- };
-
- 2216-2218:
- 数组physmap中的每对相邻数组元素用来描述一个物理内存区域,即(start = physmap[0],end = physmap[1])描述
- 第一个物理内存区域,(start = physmap[2],end = physmap[3])描述第二个物理内存区域等等。
- 函数add_smap_entry比较简单,做一些简单的检查,然后使用cu_bios_smap数组中的元素初始化数组physmap,
- 该函数只对cu_bios_smap数组中.type成员为SMAP_TYPE_MEMORY的的数组元素感兴趣,从上面的测试结果来看,
- 有3个感兴趣的数组元素,函数执行完后:
- physmap[0] = cu_bios_smap[0].base,physmap[1] = cu_bios_smap[0].base + cu_bios_smap[0].length;
- physmap[2] = cu_bios_smap[3].base,physmap[3] = cu_bios_smap[3].base + cu_bios_smap[3].length;
- physmap[4] = cu_bios_smap[6].base,physmap[5] = cu_bios_smap[6].base + cu_bios_smap[6].length;
- 变量physmap_idx被设置为4,从这里就可以看出变量physmap_idx的具体含义了,并且数组元素
- physmap[physmap_idx + 1]的值为可以使用的最大物理内存地址。
- 函数add_smap_entry可以在"$FreeBSD: release/9.2.0/sys/i386/i386/machdep.c"中找到。
-
- 2219:跳转到have_smap处。
- *******************************************************/
- 2197
- 2198 physmap_idx = 0;
- 2199 smapbase = NULL;
- 2200 kmdp = preload_search_by_type("elf kernel");
- 2201 if (kmdp == NULL)
- 2202 kmdp = preload_search_by_type("elf32 kernel");
- 2203 if (kmdp != NULL)
- 2204 smapbase = (struct bios_smap *)preload_search_info(kmdp,
- 2205 | MODINFOMD_SMAP);
- 2206 if (smapbase != NULL) {
- 2207 /*
- 2208 * subr_module.c says:
- 2209 * "Consumer may safely assume that size value precedes data. "
- 2210 * ie: an int32_t immediately precedes SMAP.
- 2211 */
- 2212 smapsize = *((u_int32_t *)smapbase - 1);
- 2213 smapend = (struct bios_smap *)((uintptr_t)smapbase + smapsize);
- 2214 has_smap = 1;
- 2215
- 2216 for (smap = smapbase; smap < smapend; smap++)
- 2217 if (!add_smap_entry(smap, physmap, &physmap_idx))
- 2218 break;
- 2219 goto have_smap;
- 2220 }
- 2221
- /* 2222-2257: BIOS中断相关,这里忽略 */
- ...........................................................................................................
- ...........................................................................................................
- 2258
- 2259 have_smap:
- 2260
- 2261
- 2262
- /******************************************************************
- * 2264-2276: 确定变量basemem的值。
- u_int basemem;
-
- 结合上面已经初始化的数组physmap,将会做下面的设置:
- basemem = 653312/1024;即638Kb.
- 2275:
- 函数basemem_setup不是很重要,完成下面的工作:
- #define ISA_HOLE_START 0xa0000
- 1->修改内核地址空间的部分页表项,这些页表项将映射
- trunc_page(basemem * 1024)和ISA_HOLE_START之间的物理页框。
- 2->修改VM86模式使用的部分页表项,这些页表项映射
- basemem和ISA_HOLE_START之间的物理页框。
- 2278:
- physmap[1] = cu_bios_smap[0].base + cu_bios_smap[0].length,
- 值明显非零,这里跳转到physmap_done处。
- *********************/
- 2263
- 2264 if (basemem == 0) {
- 2265 for (i = 0; i <= physmap_idx; i += 2) {
- 2266 if (physmap[i] == 0x00000000) {
- 2267 basemem = physmap[i + 1] / 1024;
- 2268 break;
- 2269 }
- 2270 }
- 2271
- 2272 /* XXX: If we couldn't find basemem from SMAP, just guess. */
- 2273 if (basemem == 0)
- 2274 basemem = 640;
- 2275 basemem_setup();
- 2276 }
- 2277
- 2278 if (physmap[1] != 0)
- 2279 goto physmap_done;
- 2280
- /* 2281-2327:BIOS中断相关,这里忽略 */
- .................................................................................................
- .................................................................................
- 2328 physmap_done:
- 2329
- 2330
- 2331
- /*************************************************************************
- * Now, physmap contains a map of physical memory.
- 执行到这里的话,数组physmap中的元素描述了系统中的可用的物理内存区域。
- ****************************/
- 2332
- 2333
- /*****************************************************************************************
- * 2334-2337:
- 在SMP系统中,mp_bootaddress函数在物理内存区域(physmap[0],physmap[1])之间保留
- 一部分物理内存,这部分物理内存用来保存AP启动时要执行的代码,并更新数组元素physmap[1]。
- ****************************/
- 2334 #ifdef SMP
- 2335 /* make hole for AP bootstrap code SMP启动代码相关代码的存放区域 */
- 2336 physmap[1] = mp_bootaddress(physmap[1]);
- 2337 #endif
- 2338
- /***********************************************************************************
- * #define atop(x) ((x) >> PAGE_SHIFT)
- long Maxmem = 0;该全局变量为系统中可以使用的最大物理页框号。
- 2345:
- 结合上面的physmap数组,正确设置Maxmem.
-
- 2347-2349:
- 如果内核编译时,选择了选项MAXMEM,就重新设置变量Maxmem,根据212号系统调用的输出
- 没有选择MAXMEM选项,这里忽略。
- 2351-2359:重新设置变量Maxmem?,这里不做任何调整,忽略。
- 2361-2369:
- 根据这里注释的描述,memtest非零的话,就要进行不必要的内存检测;
- 这里memtest的值为0.
- 2371-2373:输出一些信息。
- 2375-2380:更新数组元素physmap[physmap_idx + 1]?
- ************************************************************/
- 2345 Maxmem = atop(physmap[physmap_idx + 1]);
- 2346
- 2347 #ifdef MAXMEM
- 2348 Maxmem = MAXMEM / 4;
- 2349 #endif
- 2350
- 2351 if (TUNABLE_ULONG_FETCH("hw.physmem", &physmem_tunable))
- 2352 Maxmem = atop(physmem_tunable);
- 2353
- 2354 /*
- 2355 * If we have an SMAP, don't allow MAXMEM or hw.physmem to extend
- 2356 * the amount of memory in the system.
- 2357 */
- 2358 if (has_smap && Maxmem > atop(physmap[physmap_idx + 1]))
- 2359 Maxmem = atop(physmap[physmap_idx + 1]);
- 2360
- 2361 /*
- 2362 * By default enable the memory test on real hardware, and disable
- 2363 * it if we appear to be running in a VM. This avoids touching all
- 2364 * pages unnecessarily, which doesn't matter on real hardware but is
- 2365 * bad for shared VM hosts. Use a general name so that
- 2366 * one could eventually do more with the code than just disable it.
- 2367 */
- 2368 memtest = (vm_guest > VM_GUEST_NO) ? 0 : 1;
- 2369 TUNABLE_ULONG_FETCH("hw.memtest.tests", &memtest);
- 2370
- 2371 if (atop(physmap[physmap_idx + 1]) != Maxmem &&
- 2372 (boothowto & RB_VERBOSE))
- 2373 printf("Physical memory use set to %ldK\n", Maxmem * 4);
- 2374
- 2375 /*
- 2376 * If Maxmem has been increased beyond what the system has detected,
- 2377 * extend the last memory segment to the new limit.
- 2378 */
- 2379 if (atop(physmap[physmap_idx + 1]) < Maxmem)
- 2380 physmap[physmap_idx + 1] = ptoa((vm_paddr_t)Maxmem);
- 2381
- 2382
- /**********************************************************************************
- * call pmap initialization to make new kernel address space
-
- 2383:
- 函数pmap_bootstrap保留一部分内核虚拟地址区域和这些虚拟地址区域
- 对应的页表项,该函数可以在"$FreeBSD: release/9.2.0/sys/i386/i386/pmap.c"中
- 找到,这里只给出其执行完后内核虚拟地址空间的结构图,参见上面图2。
- **************************************/
- 2383 pmap_bootstrap(first);
- 2384
- 2385 /*
- 2386 * Size up each available chunk of physical memory.
- 2387 */
- /**************************************************************************************
- * 2388-2521:
- 执行到这里的话,数组physmap已经正确被初始化,现在使用该数组physmap中的元素初始化
- 数组phys_avail和数组dump_avail。
-
- 全局变量physmem为系统中可用物理页框的数目。
- 2388-2521之间的代码比较容易理解,执行完后,将由数组phys_avail和数组dump_avail
- 描述系统中的可用物理内存区域。
- 注意一下2419-2423之间代码,位于(KERNLOAD,first)之间的物理内存将由数组dump_avail
- 来描述。
- ****************************************/
- 2388 physmap[0] = PAGE_SIZE; /* mask off page 0 */
- 2389 pa_indx = 0;
- 2390 da_indx = 1;
- 2391 phys_avail[pa_indx++] = physmap[0];
- 2392 phys_avail[pa_indx] = physmap[0];
- 2393 dump_avail[da_indx] = physmap[0];
- 2394 pte = CMAP1;
- 2395
- 2396 /*
- 2397 * Get dcons buffer address
- 2398 */
- 2399 if (getenv_quad("dcons.addr", &dcons_addr) == 0 ||
- 2400 getenv_quad("dcons.size", &dcons_size) == 0)
- 2401 dcons_addr = 0;
- 2402
- 2403
- 2404 /*
- 2405 * physmap is in bytes, so when converting to page boundaries,
- 2406 * round up the start address and round down the end address.
- 2407 */
- 2408 for (i = 0; i <= physmap_idx; i += 2) {
- 2409 vm_paddr_t end;
- 2410
- 2411 end = ptoa((vm_paddr_t)Maxmem);
- 2412 if (physmap[i + 1] < end)
- 2413 end = trunc_page(physmap[i + 1]);
- 2414 for (pa = round_page(physmap[i]); pa < end; pa += PAGE_SIZE) {
- 2415 int tmp, page_bad, full;
- 2416 int *ptr = (int *)CADDR1;
- 2417
- 2418 full = FALSE;
- 2419 /*
- 2420 * block out kernel memory as not available.
- 2421 */
- 2422 if (pa >= KERNLOAD && pa < first)
- 2423 goto do_dump_avail;
- 2424
- 2425 /*
- 2426 * block out dcons buffer
- 2427 */
- 2428 if (dcons_addr > 0
- 2429 && pa >= trunc_page(dcons_addr)
- 2430 && pa < dcons_addr + dcons_size)
- 2431 goto do_dump_avail;
- 2432
- 2433 page_bad = FALSE;
- 2434 if (memtest == 0)
- 2435 goto skip_memtest;
- /* 2346-2471: 内存检测相关,这里忽略 */
- .............................................................................................
- .............................................................................................
- 2472
- 2473 skip_memtest:
- 2474 /*
- 2475 * Adjust array of valid/good pages.
- 2476 */
- 2477 if (page_bad == TRUE)
- 2478 continue;
- 2479 /*
- 2480 * If this good page is a continuation of the
- 2481 * previous set of good pages, then just increase
- 2482 * the end pointer. Otherwise start a new chunk.
- 2483 * Note that "end" points one higher than end,
- 2484 * making the range >= start and < end.
- 2485 * If we're also doing a speculative memory
- 2486 * test and we at or past the end, bump up Maxmem
- 2487 * so that we keep going. The first bad page
- 2488 * will terminate the loop.
- 2489 */
- 2490 if (phys_avail[pa_indx] == pa) {
- 2491 phys_avail[pa_indx] += PAGE_SIZE;
- 2492 } else {
- 2493 pa_indx++;
- 2494 if (pa_indx == PHYS_AVAIL_ARRAY_END) {
- 2495 printf(
- 2496 "Too many holes in the physical address space, giving up\n");
- 2497 pa_indx--;
- 2498 full = TRUE;
- 2499 goto do_dump_avail;
- 2500 }
- 2501 phys_avail[pa_indx++] = pa; /* start */
- 2502 phys_avail[pa_indx] = pa + PAGE_SIZE; /* end */
- 2503 }
- 2504 physmem++;
- 2505 do_dump_avail:
- 2506 if (dump_avail[da_indx] == pa) {
- 2507 dump_avail[da_indx] += PAGE_SIZE;
- 2508 } else {
- 2509 da_indx++;
- 2510 if (da_indx == DUMP_AVAIL_ARRAY_END) {
- 2511 da_indx--;
- 2512 goto do_next;
- 2513 }
- 2514 dump_avail[da_indx++] = pa; /* start */
- 2515 dump_avail[da_indx] = pa + PAGE_SIZE; /* end */
- 2516 }
- 2517 do_next:
- 2518 if (full)
- 2519 break;
- 2520 }
- 2521 }
- 2522 *pte = 0;
- 2523 invltlb();
- /* 2524-2530:KEN相关,这里忽略 */
- ...............................................................................................
- ...............................................................................................
- 2531
- 2532 /*
- 2533 * XXX
- 2534 * The last chunk must contain at least one page plus the message
- 2535 * buffer to avoid complicating other code (message buffer address
- 2536 * calculation, etc.).
- 2537 */
- /*******************************************************************************************
- * msgbufp is used to map the system message buffer.
- struct msgbuf *msgbufp = 0;
- int msgbufsize; size of kernel message buffer
- msgbufsize = MSGBUF_SIZE;
- #define MSGBUF_SIZE (32768 * 2) 即64KB
- msgbufp是用来访问system message buffer的虚拟线性地址,在函数pmap_bootstrap中设置。
- 执行到这里的话,(phys_avail[pa_indx - 1],phys_avail[pa_indx])描述了系统中最后一个
- 可用物理内存区域。
- 2538-2543:
- 检查可用物理内存区域(phys_avail[pa_indx - 1],phys_avail[pa_indx])是否有足够的空间
- 来保存system message。
- 2545:更新变量Maxmem。
- 2548:
- 将system message buffer从系统可用物理内存中移除,并更新数组元素phys_avail[pa_indx],
- 此时数组元素phys_avail[pa_indx]的值为system message buffer的起始物理地址。
- 2551-2553:
- 修改msgbufp对应的页表项,这些页表项将映射保存system message的物理页框。
- 2555:在编译了XEN时PT_UPDATES_FLUSH宏才有效。
- ****************************************************************************/
- 2538 while (phys_avail[pa_indx - 1] + PAGE_SIZE +
- 2539 round_page(msgbufsize) >= phys_avail[pa_indx]) {
- 2540 physmem -= atop(phys_avail[pa_indx] - phys_avail[pa_indx - 1]);
- 2541 phys_avail[pa_indx--] = 0;
- 2542 phys_avail[pa_indx--] = 0;
- 2543 }
- 2544
- 2545 Maxmem = atop(phys_avail[pa_indx]);
- 2546
- 2547 /* Trim off space for the message buffer. */
- 2548 phys_avail[pa_indx] -= round_page(msgbufsize);
- 2549
- 2550 /* Map the message buffer. */
- 2551 for (off = 0; off < round_page(msgbufsize); off += PAGE_SIZE)
- 2552 pmap_kenter((vm_offset_t)msgbufp + off, phys_avail[pa_indx] +
- 2553 off);
- 2554
- 2555 PT_UPDATES_FLUSH();
- 2556 }
复制代码 |
|