内存初始化模式怎么选(内存管理14zone初始化2)

内存管理(13)zone初始化1中说到free_area_init_node函数,接下来主要做的工作如下:

1.计算有效页框数,总页框数

2.初始化内存节点管理数据结构pgdat

2.初始化zone管理数据结构其中包含空闲链表

3.初始化zone下的管理的页框属性参数

大致如下图所示:

内存初始化模式怎么选(内存管理14zone初始化2)(1)

zone初始化过程


free_area_init_node函数

内存初始化模式怎么选(内存管理14zone初始化2)(2)

free_area_init_node实现1

  • 第78~79行:初始化pg_data_t中的node_id内存节点ID和node_start_pfn内存节点起始页框号
  • 第80~84行:如果定义了CONFIG_HAVE_MEMBLOCK_NODE_MAP计算得到本内存节点内的物理页框起始结束编号,get_pfn_range_for_nid函数和实现细节如下↓
  • 第85行:计算内存节点内总的页框数(包含空洞),实现细节如下↓

内存初始化模式怎么选(内存管理14zone初始化2)(3)

free_area_init_node实现2

  • 第88行:初始化pgdat.node_mem_map,实现细节见alloc_node_mem_map函数↓
  • 第95行:初始化内存节点内各个zone的实际处理函数,实现细节见free_area_init_core函数↓
get_pfn_range_for_nid函数

内存初始化模式怎么选(内存管理14zone初始化2)(4)

get_pfn_range_for_nid实现

  • 在get_pfn_range_for_nid函数主要是看for_each_mem_pfn_range宏的实现,而for_each_mem_pfn_range的实现主要看__next_mem_pfn_range函数的实现,以下就是该函数的实现细节↓
__next_mem_pfn_range函数

内存初始化模式怎么选(内存管理14zone初始化2)(5)

__next_mem_pfn_range实现

  • 遍历当前memory_type下的所有regions,每遍历一个region获取该region的起始页框号返回。另外需要指出的是如果当前nid等于MAX_NUMNODES说明所有内存节点已经处理完毕。
  • 返回到get_pfn_range_for_nid函数中与已有起始结束页框号进行比较,起始页框号取最小,结束页框号取最大。
  • 当本内存节点内的region都遍历完毕,最终在start_pfn和end_pfn中就会保存内存节点的起始结束物理页框编号
calculate_node_totalpages函数

内存初始化模式怎么选(内存管理14zone初始化2)(6)

calculate_node_totalpages实现

  • 第41~46行:计算内存节点(包含空洞在内的)总页框数,并初始化pgdat中的node_spanned_pages
  • 第48~54行:计算内存节点内(不包含空洞区域)总页框总数,并初始化pgdat中的node_present_pages
  • zone_spanned_pages_in_node和zone_absent_pages_in_node这两个函数的实现细节如下↓

内存初始化模式怎么选(内存管理14zone初始化2)(7)

zone_spanned_pages_in_node和zone_absent_pages_in_node实现

  • 在内存管理(13)zone初始化1中有分析过zones_size和zholes_size所表示的含义

平坦内存模型则处理alloc_node_mem_map函数

alloc_node_mem_map函数

内存初始化模式怎么选(内存管理14zone初始化2)(8)

alloc_node_mem_map 实现1

内存初始化模式怎么选(内存管理14zone初始化2)(9)

alloc_node_mem_map 实现2

  • 第66行:如果定义了CONFIG_FLAT_NODE_MEM_MAP(平坦内存模型),平坦内存模型意味着没有空洞,在此前提之下。
  • 如果没有初始化pgdat->node_mem_map则对其执行初始化处理。主要以1K页为对齐计算起始终止页框号。ZONE起始页框位置忽略小于1K页框数的部分,ZONE结束页框的位置低于1K页的向上取整K页框数记做end,这么做是为了方便伙伴系统按2^ORDER的顺序进行页框分配。计算总的(end-start)页的struct page数据结构所占用空间,然后向memblock申请这段空间并用map指向它。
  • 第85行:假设内存节点的物理起始页框编号本来是1025,前面为了方便伙伴系统按MAX_ORDER序列分配页框,使其与1024对齐所以start被记做1024,这时候计算的struct page所占用内存为end-1024 而实际有效的物理页框是从1025开始的所以才有了第85行这一步,node_mem_map偏移(node_start_pfn - start)个页框,使其指到有效位置。
  • 最后87~97行主要就做一件事,系统内存节点是否为单节点,是的话就用mem_map全局变量来记录pgdat->node_mem_map
free_area_init_core函数

内存初始化模式怎么选(内存管理14zone初始化2)(10)

free_area_init_core 实现1

  • 第111行:如果定义了CONFIG_MEMORY_HOTPLUG初始化pgdat->node_size_lock
  • 第112~115行:如果定义了CONFIG_NUMA_BALANCING初始化BALANCING相关成员
  • 第117~119行:初始化其他一些pgdat相关成员
  • 第121行:开始遍历ZONE
  • 第125行:计算当前zone下包含空洞在内所占的总页框数
  • 第127行:计算当前zone除空洞外所占页框数
  • 第137行:以页为单位计算当前zone里页框的struct page数据结构所占物理页框数,calc_memap_size实现如下↓
  • 第138~145行:减去struct page所占物理页框数,重新计算非高端内存的空闲空间物理页框数

内存初始化模式怎么选(内存管理14zone初始化2)(11)

free_area_init_core 实现3

  • 第152~156行:如果有DMA,则减去DMA保留空间所占的物理页框数计算剩余空闲空间物理页框数
  • 第157~161行:统计直接映射的物理页框数,用nr_kernel_pages全局变量保存
  • 第162行:统计剩余可用的空闲空间页框数,用nr_all_pages全局变量保存
  • 第164~165行:初始化zone的spanned_pages和present_pages
  • 第171行:初始化zone的managed_pages,如果是高端内存则managed_pages就是实际的物理页框数,如果是低端内存那么managed_pages是系统ZONE中剩余的空闲页框数
  • 第172~177行:初始化针对NUMA系统的其他参数

内存初始化模式怎么选(内存管理14zone初始化2)(12)

free_area_init_core 实现4

  • 第178~182行:初始化zone的相关成员
  • 第183行:初始化per_cpu的pageset管理单个CPU页框数据结构,缓解锁的竞争
  • 第186行:看了半天没看懂什么意思(先留个疑问吧)
  • 第192行:检查pageblock是否设定
  • 第193行:未定义CONFIG_SPARSEMEM时计算zone里面page_flags所占内存空间大小,setup_usemap函数实现细节↓
  • 第194行:初始化等待队列散列表,init_currently_empty_zone实现如下↓
  • 第197行:初始化zone下所有page数据结构的相关参数,memmap_init_zone函数实现如下↓
calc_memmap_size函数

内存初始化模式怎么选(内存管理14zone初始化2)(13)

calc_memmap_size实现

  • 这个函数的关键就是第335行,为什么要做这么一个判断呢,它要表达的意思很简单如果spanned_page比present_page多超过present_page的1/16,并且内存模型是稀疏内存模型,那么就使用present_page来计算struct page所占用的空间。因为在稀疏内存模型下,zone内部可能有空洞,系统为了尽可能的权衡,所以使用present来计算memmap映射的page数据结构所占空间。而当其小于present_page的1/16,则仍旧使用spanned_pages来计算。
setup_usemap函数

内存初始化模式怎么选(内存管理14zone初始化2)(14)

setup_usemap实现

  • 在setup_usemap函数中第348行看usemap_size函数的实现细节可知,每个pageblock用4个bits来表示pageblock_flags,所以usemap_size其实就是计算所有的pageblock_flags所占用的字节空间
  • 而352行就是在计算到这段空间大小后申请它,并用pageblock_flags指向它
init_currently_empty_zone函数

内存初始化模式怎么选(内存管理14zone初始化2)(15)

init_currently_empty_zone实现

  • 第429行:初始化进程等待队列散列表数组,zone_wait_table_init实现如下↓
  • 第442行:初始化ZONE空闲链表,zone_init_free_lists函数实现如下↓
zone_wait_table_init函数

内存初始化模式怎么选(内存管理14zone初始化2)(16)

zone_wait_table_init实现

  • 这个函数中主要初始化了进程等待队列散列表相关的参数信息,其中wait_table表示进程等待一个page释放的等待队列哈希表。它会被wait_on_page(),unlock_page()函数使用. 用哈希表,而不用一个等待队列的原因,防止进程长期等待资源。wait_table_hash_nr_entries表示哈希表中的等待队列的数量。wait_table_bits表示等待队列散列表数组的大小。他们相当于如下一个模型,等待队列模型如下图所示:

内存初始化模式怎么选(内存管理14zone初始化2)(17)

等待队列模型

zone_init_free_lists函数

内存初始化模式怎么选(内存管理14zone初始化2)(18)

zone_init_free_lists实现

  • 如文章开头所提示意图,每个ZONE下对应一个free_area数组,数组大小为MAX_ORDER,在系统中MAX_ORDER大小设为11,在每一个free_area中都包含MIGRATE_TYPES个链表,如上图所示MIGRATE_UNMOVABLE、MIGRATE_RECLAIMABLE、MIGRATE_MOVABLE各对应一条空闲页框链表下页框属性,链表中节点所占页框数就是2^index个页框。

memmap_init_zone函数主要就是初始化page数据结构相关的一些参数。所以在描述这个函数之前有必要先就struct page 数据结构的某些关键成员做一个简单介绍

struct page数据结构(部分)

内存初始化模式怎么选(内存管理14zone初始化2)(19)

struct page结构体

  • flags:描述page的状态和其他信息。由section node zone flags组成,其中section用于稀疏内存模型,node表示当前页所占的内存节点,zone表示当前页所在的内存域flags代表页状态。

比较常用的几种flags如下:

内存初始化模式怎么选(内存管理14zone初始化2)(20)

page.flags标志定义

  • _count:引用计数,表示内核中引用该page的次数,如果要操作该page引用计数会 1,操作完成-1。当该值为0时,表示没有引用该page的位置,所以该page可以被解除映射,这往往在内存回收时是有用的。
  • _mapcount:被页表映射的次数,也就是说该page同时被多少个进程共享。初始值为-1,如果只被一个进程的页表映射了,该值为0 。如果该page处于伙伴系统中,该值为PAGE_BUDDY_MAPCOUNT_VALUE(-128),内核通过判断该值是否为PAGE_BUDDY_MAPCOUNT_VALUE来确定该page是否属于伙伴系统。


注意区__count和_mapcount,_mapcount表示的是映射次数,而_count表示的是使用次数;被映射了不一定在使用,但要使用必须先映射。

  • mapping:有三种含义:如果mapping = 0,说明该page属于交换缓存(swap cache),当需要使用地址空间时会指定交换分区的地址空间swapper_space;如果mapping != 0,bit[0] = 0,说明该page属于页缓存或文件映射,mapping指向文件的地址空间address_space; 如果mapping != 0,bit[0] != 0,说明该page为匿名映射,mapping指向struct anon_vma对象。
  • index:在映射的虚拟空间(vma_area)内的偏移;一个文件可能只映射一部分,假设映射了1M的空间,index指的是在1M空间内的偏移,而不是在整个文件内的偏移。
  • private:私有数据指针,由应用场景确定其具体的含义:如果设置了PG_private标志,表示buffer_heads; 如果设置了PG_swapcache标志,private存储了该page在交换分区中对应的位置信息swp_entry_t。 如果_mapcount = PAGE_BUDDY_MAPCOUNT_VALUE,说明该page位于伙伴系统,private存储该伙伴的阶。
  • lru:链表头,主要有3个用途:a:page处于伙伴系统中时,用于链接相同阶的伙伴(只使用伙伴中的第一个page的lru即可达到目的)。 b:page属于slab时,page->lru.next指向page驻留的缓存的管理结构,page->lru.prec指向保存该page的slab的管理结构。 c:page被用户态使用或被当做页缓存使用时,用于将该page连入zone中相应的lru链表,供内存回收时使用。
memmap_init_zone函数

内存初始化模式怎么选(内存管理14zone初始化2)(21)

memmap_init_zone 实现1

  • 第213行确定zone地址;第214行遍历zone下的页框;第226行确定页框地址


内存初始化模式怎么选(内存管理14zone初始化2)(22)

memmap_init_zone 实现2

  • 第227行:set_page_links函数确定page所在的section、node、zone,设定到page->flags中,set_page_links的实现如下↓
  • 第228行:检测section等的合法性
  • 第229~230行:初始化page->_count,页框引用次数;page->_mapcount映射次数
  • 第231~232行:初始化page相关参数
  • 第234~237行:表示把每个pageblock的第一个page属性设置为MIGRATE_MOVEABLE
  • 初始化page相关其他参数
set_page_links函数

内存初始化模式怎么选(内存管理14zone初始化2)(23)

set_page_links实现

  • 从set_page_links函数的实现可以看出section、node、zone处在flags中的位置大致如下:

内存初始化模式怎么选(内存管理14zone初始化2)(24)

flags结构

左边代表高位,右边为低位


zone的初始化基本介绍完毕。其初始化过程基本如开篇所给出的图那样。当然这只是初始化的介绍,后面介绍诸如页的交换、回收、映射、分配等很多场景中会再次介绍其中很多参数所起的实际作用,这里没有说只是觉得说了也没什么意义,只有真正用到的时候我们才会明白其真正的含义。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页