freebsd9.2-管理物理页框-初始化函数vm_page_startup
本帖最后由 71v5 于 2014-07-17 21:23 编辑根据之前的描述:
函数getmemsize已经将数组phys_avail和dump_avail正确初始化,并且没有初始化的数组元素的值都为0,数组phys_avail用来描述系统中可用的
物理内存区域,该数组中每两个相邻元素描述一个物理内存区域,分别标识相应物理内存区域的起始物理地址和结尾物理地址,
比如(phys_avail,phys_avail),(phys_avail,phys_avail),(phys_avail,phys_avail),(phys_avail,phys_avail)等等.
但是位于(KERNLOAD,first)之间的物理内存不由数组phys_avail来描述。
数组dump_avail和数组phys_avail类似,只不过数组dump_avail描述了(KERNLOAD,first)之间的物理内存。
在这里没有必要知道这些数组元素对应的具体物理地址,只需要明白这些数组元素描述了系统中的可用物理内存区域就可以了。
不过为了分析方便,做下面的假设:
1:
phys_avail描述了4个可用物理内存区域,即:(phys_avail,phys_avail),(phys_avail,phys_avail),
(phys_avail,phys_avail),(phys_avail,phys_avail)
2:
物理内存区域(phys_avail,phys_avail) 是最大的物理内存区域。
根据之前212号系统调用的输出结果来看,(phys_avail,phys_avail)可能不是最大的物理内存区域,这里完全是为了分析方便,
这种假设并不影响我们理解相关机制原理。
图1:
[内核虚拟地址空间布局]-图2:
[函数vm_page_startup]:/***************************************************************************************
* 参数描述:
vaddr:内核可用的第一个虚拟线性地址。
该函数是在初始化函数vm_mem_init中调用的:
virtual_avail = vm_page_startup(virtual_avail);
全局变量virtual_avail,始终保存的是第一个可用的内核虚拟线性地址,动态更新,见上面
的图2。
该函数主要的工作:
1:
在物理内存区域(phys_avail,phys_avail)中分配一部分物理内存给内核其它部分使用,
其中最重要就是给数组vm_page_array分配物理内存。
2:
调用函数vm_phys_init根据数组phys_avail初始化数组vm_phys_segs,执行完后,系统中
的可用物理内存就通过数组vm_phys_segs来描述。
3:
调用函数vm_phys_add_page将空闲的物理页框添加到空闲物理页框队列的相应页框池中,
即三维数组vm_phys_free_queues。
4:
调用UMA相关的第一个初始化函数uma_startup,暂且略过。
为了使条理更清晰,上面第2步后面单独分析。
***************************************************/
245 vm_offset_t
246 vm_page_startup(vm_offset_t vaddr)
247 {
/****************************************************************************
*248-260:
一些局部变量,结合下面的分析会更清楚这些局部变量的具体含义。
***********************************/
248 vm_offset_t mapped;
249 vm_paddr_t page_range;
250 vm_paddr_t new_end;
251 int i;
252 vm_paddr_t pa;
253 vm_paddr_t last_pa;
254 char *list;
255
256 /* the biggest memory array is the second group of pages */
257 vm_paddr_t end;
258 vm_paddr_t biggestsize;
259 vm_paddr_t low_water, high_water;
260 int biggestone;
261
262 biggestsize = 0;
263 biggestone = 0;
264 vaddr = round_page(vaddr);
265
/*****************************************************************************
* #define trunc_page(x) ((x) & ~PAGE_MASK)
#define round_page(x) (((x) + PAGE_MASK) & ~PAGE_MASK)
宏trunc_page将x截尾取整,并按PAGE_SIZE对齐。
宏round_page将x向上取整,并按PAGE_SIZE对齐。
266-269:
将数组phys_avail中的数组元素按PAGE_SIZE对齐。
********************************/
266 for (i = 0; phys_avail; i += 2) {
267 phys_avail = round_page(phys_avail);
268 phys_avail = trunc_page(phys_avail);
269 }
270
/****************************************************************************
* 271-272:这里设置两个阈值。
low_water:可用的最小物理内存地址。
high_water:可用的最大物理内存地址。
274-285:
检查数组phys_avail,每次for循环,就检查一个物理内存区域(phys_avail,
phys_avail[i + 1)。
275:局部变量size为正在检查的物理内存区域的大小。
277-280:
更新变量biggestone和biggestsize,biggestsize始终记录最大的物理内存区域
的大小,biggestone始终记录描述物理内存区域起始物理地址的数组元素在
数组phys_avail中的索引,即对应的i的值,这里就为6.
281-284:更新变量low_water和high_water。
287-289:XEN相关,这里忽略。
*****************************************/
271 low_water = phys_avail;
272 high_water = phys_avail;
273
274 for (i = 0; phys_avail; i += 2) {
275 vm_paddr_t size = phys_avail - phys_avail;
276
277 if (size > biggestsize) {
278 biggestone = i;
279 biggestsize = size;
280 }
281 if (phys_avail < low_water)
282 low_water = phys_avail;
283 if (phys_avail > high_water)
284 high_water = phys_avail;
285 }
286
287 #ifdef XEN
288 low_water = 0;
289 #endif
290
/***********************************************************************************
* 291:
将end设置为最大的物理内存区域的结尾物理地址,这里就为phys_avail。
*****************************************/
291 end = phys_avail;
292
293
294
/********************************************************
* Initialize the page and queue locks.
295-300:
初始化相关的锁,暂且忽略.
********************/
295
296 mtx_init(&vm_page_queue_mtx, "vm page queue", NULL, MTX_DEF |
297 MTX_RECURSE);
298 mtx_init(&vm_page_queue_free_mtx, "vm page free queue", NULL, MTX_DEF);
299 for (i = 0; i < PA_LOCK_COUNT; i++)
300 mtx_init(&pa_lock.data, "vm page", NULL, MTX_DEF);
301
302
303
304
/**********************************************************************
* Initialize the queue headers for the hold queue, the active queue,
* and the inactive queue.
初始化数组vm_page_queues。
struct vmmeter cnt;
cnt是内核定义的一个类型为struct vmmeter的数据对象,内核中这个结构体
的描述为"System wide statistics counters",即相当于一个系统范围内的
计数器:
u_int v_active_count; /* (q) pages active */
u_int v_inactive_count; /* (q) pages inactive */
308-310:
将相应数组vm_page_queues元素的cnt成员设置为cnt.v_active_count或者
cnt.v_inactive_count的地址,通过直接更新cnt.v_active_count等成员,
就可以从数组vm_page_queues的cnt成员来访问。
*********************************************/
305
306 for (i = 0; i < PQ_COUNT; i++)
307 TAILQ_INIT(&vm_page_queues.pl);
308 vm_page_queues.cnt = &cnt.v_inactive_count;
309 vm_page_queues.cnt = &cnt.v_active_count;
310 vm_page_queues.cnt = &cnt.v_active_count;
311
312
313
314
/***********************************************************************************
* Allocate memory for use when boot strapping the kernel memory
* allocator.
316-321:执行后所分配的物理内存可以参考上面图1中的"A"处。
#define UMA_SLAB_SIZE PAGE_SIZE How big are our slabs?
#define UMA_BOOT_PAGES64 Pages allocated for startup
static int boot_pages = UMA_BOOT_PAGES;
316-317:
在物理内存区域(phys_avail,phys_avail)中分配一段
大小为(64 * PAGE_SIZE)的物理内存,new_end为该物理内存区域新的结尾物理地址。
这里的话,物理内存区域就为(phys_avail,phys_avail.
318-319:
将这段物理内存映射到内核的虚拟地址空间中。
320:将之间的物理内存清零
321:暂时略过对uma_startup函数的分析,UMA相关。
*****************************************************/
315
316 new_end = end - (boot_pages * UMA_SLAB_SIZE);
317 new_end = trunc_page(new_end);
318 mapped = pmap_map(&vaddr, new_end, end,
319 VM_PROT_READ | VM_PROT_WRITE);
320 bzero((void *)mapped, end - new_end);
321 uma_startup((void *)mapped, boot_pages);
322
323 #if defined(__amd64__) || defined(__i386__) || defined(__arm__) || \
324 defined(__mips__)
/**************************************************************************
* Allocate a bitmap to indicate that a random physical page
* needs to be included in a minidump.
*
* The amd64 port needs this to indicate which direct map pages
* need to be dumped, via calls to dump_add_page()/dump_drop_page().
*
* However, i386 still needs this workspace internally within the
* minidump code.In theory, they are not needed on i386, but are
* included should the sf_buf code decide to use them.
uint32_t *vm_page_dump;
int vm_page_dump_size;
336-345:执行完后,所分配的物理内存可以参考上面图1中的"B"处。
在物理内存区域(phys_avail,new_end])中分配一段大小为
vm_page_dump_size的物理内存。
1->这段物理内存由minidump code使用,其中每一个bit位对应系统中一个物理页框
(由数组dump_avail描述)。
2->将上面分配的物理内存映射到内核虚拟地址空间中,通过vm_page_dump
访问这段物理内存。
347-359:
针对AMD64位处理器,从这里看出,函数dump_add_page将之前分配给
system message buffer的物理页框对应的bit位设置为1,描述
物理页框的位图可以通过虚拟线性地址vm_page_dump访问。
****************************************/
335
336 last_pa = 0;
337 for (i = 0; dump_avail != 0; i += 2)
338 if (dump_avail > last_pa)
339 last_pa = dump_avail;
340 page_range = last_pa / PAGE_SIZE;
341 vm_page_dump_size = round_page(roundup2(page_range, NBBY) / NBBY);
342 new_end -= vm_page_dump_size;
343 vm_page_dump = (void *)(uintptr_t)pmap_map(&vaddr, new_end,
344 new_end + vm_page_dump_size, VM_PROT_READ | VM_PROT_WRITE);
345 bzero((void *)vm_page_dump, vm_page_dump_size);
346 #endif
347 #ifdef __amd64__
348 /*
349 * Request that the physical pages underlying the message buffer be
350 * included in a crash dump.Since the message buffer is accessed
351 * through the direct map, they are not automatically included.
352 */
353 pa = DMAP_TO_PHYS((vm_offset_t)msgbufp->msg_ptr);
354 last_pa = pa + round_page(msgbufsize);
355 while (pa < last_pa) {
356 dump_add_page(pa);
357 pa += PAGE_SIZE;
358 }
359 #endif
/*****************************************************************************************
* Compute the number of pages of memory that will be available for
* use (taking into account the overhead of a page structure per
* page).
#define atop(x) ((x) >> PAGE_SHIFT)
long first_page = 0;// 全局变量
执行到这里:
low_water:系统中可用的最小物理内存地址。
high_water:系统中可用的最大物理内存地址
365:first_page为系统中可用的第一个物理页框号。
page_range:系统中可用的物理页框的数目。
366-370:
如果之前定义过VM_PHYSSEG_SPARSE,即物理地址空间为稀疏模型,那么
page_range只记录了由数组phys_avail描述的物理页框的数目,不包括包含内核代码
数据等的物理页框。
370-372:
如果之前定义过VM_PHYSSEG_DENSE,即物理地址空间为密集模型。
那么page_range记录了系统中位于high_water之前的物理页框的数目,并且包括包含内核代码数据的物理页框,
被保留的物理页框等。
i386平台,freebsd9.2已经在"$FreeBSD: release/9.2.0/sys/i386/include/vmparam.h"
对VM_PHYSSEG_DENSE做出了定义,所以这里page_range选择370-372行之间重新设置后的值。
*********************************************/
364
365 first_page = low_water / PAGE_SIZE;
366 #ifdef VM_PHYSSEG_SPARSE
367 page_range = 0;
368 for (i = 0; phys_avail != 0; i += 2)
369 page_range += atop(phys_avail - phys_avail);
370 #elif defined(VM_PHYSSEG_DENSE)
371 page_range = high_water / PAGE_SIZE - first_page;
372 #else
373 #error "Either VM_PHYSSEG_DENSE or VM_PHYSSEG_SPARSE must be defined."
374 #endif
375 end = new_end;
376
/***********************************************************************
* Reserve an unmapped guard page to trap access to vm_page_array[-1].
在内核虚拟地址空间中保留一个大小为PAGE_SIZE的地址区间
**************************/
379
380 vaddr += PAGE_SIZE;
381
/*****************************************************************************************
* Initialize the mem entry structures now, and put them in the free
* queue.
typedef struct vm_page *vm_page_t;
vm_page_t vm_page_array;
386-389:
在物理内存区域(phys_avail,end)分配一段物理内存,这段物理内存的
大小为page_range * sizeof(struct vm_page),用来保存描述系统中物理页框的页框描述符,
并将这段物理内存映射到内核地址空间上,通过虚拟线性地址vm_page_array访问这段物理内存。
执行到这里,就可以把vm_page_array看成是一个数组,数组元素的类型为struct vm_page,
数组元素的数目为page_range。
所分配的物理内存可以参考上面图1中的"C"处。
********************************************/
385
386 new_end = trunc_page(end - page_range * sizeof(struct vm_page));
387 mapped = pmap_map(&vaddr, new_end, end,
388 VM_PROT_READ | VM_PROT_WRITE);
389 vm_page_array = (vm_page_t) mapped;
/***********************************************************************************************
* 390-395:
i386平台,freebsd9.2已经在"$FreeBSD: release/9.2.0/sys/i386/include/vmparam.h"
中做了下面的定义:
#define VM_NRESERVLEVEL 1
static vm_reserv_t vm_reserv_array;
在物理内存区域(phys_avail,new_end])分配一段物理内存,这段物理内存用来保存类型
为struct vm_reserv的数据对象,同时将这段物理内存映射到内核地址空间中,通过虚拟线性地址
vm_reserv_array来访问这段内存,这里也可把vm_reserv_array看作是一个类型为struct vm_reserv
的数组。
所分配的物理内存可以参考上面图1中的"D"处。
************************************************/
390 #if VM_NRESERVLEVEL > 0
391 /*******************************************************************
392 * Allocate memory for the reservation management system's data
393 * structures.
394 */
395 new_end = vm_reserv_startup(&vaddr, new_end, high_water);
396 #endif
/**************************************************************************************
* 397-406:针对AMD84或者MIPS
执行到这里,new_end和phys_avail之间的物理页框已经被分配,调用函数
dump_add_page将这部分物理页框对应的bit位设置为1,描述物理页框的位图可以通过虚拟线性
地址vm_page_dump来访问。
*******************************/
397 #if defined(__amd64__) || defined(__mips__)
398 /*
399 * pmap_map on amd64 and mips can come out of the direct-map, not kvm
400 * like i386, so the pages must be tracked for a crashdump to include
401 * this data.This includes the vm_page_array and the early UMA
402 * bootstrap pages.
403 */
404 for (pa = new_end; pa < phys_avail; pa += PAGE_SIZE)
405 dump_add_page(pa);
406 #endif
/*************************************************************************************
* 407:更新数组元素phys_avail,为物理内存区域新的结尾物理地址
********************************/
407 phys_avail = new_end;
408
/*******************************************************************************
* Clear all of the page structures
412: 将数组vm_page_array清零。
413-414:将全部页框描述符的order成员设置为VM_NFREEORDER,在伙伴系统中使用.
415:
long vm_page_array_size;
vm_page_array_size:数组vm_page_array的大小,即页框描述符的数目,这里设置
为page_range。
********************************************/
411
412 bzero((caddr_t) vm_page_array, page_range * sizeof(struct vm_page));
413 for (i = 0; i < page_range; i++)
414 vm_page_array.order = VM_NFREEORDER;
415 vm_page_array_size = page_range;
416
/************************************************************************************
* 417-441:
后续单独分析。
*******************************/
417 /********************************************************************
418 * Initialize the physical memory allocator.
419 */
420 vm_phys_init();
421
422 /*
423 * Add every available physical page that is not blacklisted to
424 * the free lists.
425 */
426 cnt.v_page_count = 0;
427 cnt.v_free_count = 0;
428 list = getenv("vm.blacklist");
429 for (i = 0; phys_avail != 0; i += 2) {
430 pa = phys_avail;
431 last_pa = phys_avail;
432 while (pa < last_pa) {
433 if (list != NULL &&
434 vm_page_blacklist_lookup(list, pa))
435 printf("Skipping page with pa 0x%jx\n",
436 (uintmax_t)pa);
437 else
438 vm_phys_add_page(pa);
439 pa += PAGE_SIZE;
440 }
441 }
442 freeenv(list);
443 #if VM_NRESERVLEVEL > 0
/*******************************************************************
* Initialize the reservation management system.
大概看了一下vm_reserv_init函数,其初始化的struct vm_reserv
类型的数据对象跟内核其它部分联系比较紧密,单独分析没有实际
意义,后续遇到相关函数时再回头结合vm_reserv_init函数一起看看
***********************************/
446
447 vm_reserv_init();
448 #endif
449 return (vaddr);
450 }
路过走一走。冒泡
页:
[1]