71v5 发表于 2014-07-16 00:33

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        }

luokanx 发表于 2015-12-23 12:00

路过走一走。冒泡
页: [1]
查看完整版本: freebsd9.2-管理物理页框-初始化函数vm_page_startup