热度 4
2023-11-14 08:31
1528 次阅读|
0 个评论
Linux内存管理 | 五、物理内存空间布局及管理 上章,我们介绍了物理内存的访问内存模型和组织内存模型,我们再来回顾一下: 物理内存的访问内存模型分为 : UMA:一致内存访问 NUMA:非一致内存访问 物理内存的组织模型 : FLATMEM:平坦内存模型 DISCONTIGMEM:不连续内存模型 SMARSEMEM:稀疏内存模型 Linux内核为了用统一的代码获取最大程度的兼容性,对物理内存的定义方面,引入了:内存结点(node)、内存区域(zone),内存页(page)的概念,下面我们来一一探究。 更多干货可见: 高级工程师聚集地 ,助力大家更上一层楼! 1、内存节点node 内存节点的引入,是Linux为了最大程度的提高兼容性,将UMA和NUMA系统统一起来,对于UMA而言是只有一个节点的系统 。 下面的代码部分,我们尽可能的只保留暂时用的到的部分,不涉及太多的体系架相关的细节。 在Linux内核中,我们使用 typedef struct pglist_data pg_data_t表示一个节点 /* *OnNUMAmachines,eachNUMAnodewouldhaveapg_data_ttodescribe *it'smemorylayout.OnUMAmachinesthereisasinglepglist_datawhich *describesthewholememory. * *Memorystatisticsandpagereplacementdatastructuresaremaintainedona *per-zonebasis. */ typedef struct pglist_data { ... int node_id; struct page * node_mem_map ; unsigned long node_start_pfn; unsigned long node_present_pages; /*totalnumberofphysicalpages*/ unsigned long node_spanned_pages; /*totalsizeofphysicalpage range,includingholes*/ ... } pg_data_t ; node_id:每个节点都有自己的ID node_mem_map:当前节点的struct page数组,用来管理这个节点的所有的页 node_start_pfn:这个节点的起始页号 node_present_pages:这个节点的真正可用的物理内存的页面数 node_spanned_pages:这个节点所包含的物理内存的页面数,包括不连续的内存空洞 例如,64M 物理内存隔着一个 4M 的空洞,然后是另外的 64M 物理内存。 这样换算成页面数目就是,16K 个页面隔着 1K 个页面,然后是另外 16K 个页面。 这种情况下,node_spanned_pages 就是 33K 个页面,node_present_pages 就是 32K 个页面。 内核使用了一个大小为 MAX_NUMNODES ,类型为 struct pglist_data 的全局数组 node_data ; structzonelistnode_zonelists ; intnr_zones; ... }pg_data_t; nr_zones:用于统计 NUMA 节点内包含的物理内存区域个数, 不是每个 NUMA 节点都会包含以上介绍的所有物理内存区域,NUMA 节点之间所包含的物理内存区域个数是不一样的 。 事实上只有第一个 NUMA 节点可以包含所有的物理内存区域,其它的节点并不能包含所有的区域类型,因为有些内存区域比如:ZONE_DMA,ZONE_DMA32 必须从物理内存的起点开始。这些在物理内存开始的区域可能已经被划分到第一个 NUMA 节点了,后面的物理内存才会被依次划分给接下来的 NUMA 节点。因此后面的 NUMA 节点并不会包含 ZONE_DMA,ZONE_DMA32 区域。 ZONE_NORMAL、ZONE_HIGHMEM 和 ZONE_MOVABLE 是可以出现在所有 NUMA 节点上的。 node_zones :node_zones该数组包括了所有的zone物理内存区域 node_zonelists :是 struct zonelist 类型的数组,它包含了备用 NUMA 节点和这些备用节点中的物理内存区域。 下面我们看一下struct zone结构体 struct zone { ...... struct pglist_data * zone_pgdat ; struct per_cpu_pageset __ percpu * pageset ; unsigned long zone_start_pfn; /* *spanned_pagesisthetotalpagesspannedbythezone,including *holes,whichiscalculatedas: *spanned_pages=zone_end_pfn-zone_start_pfn; * *present_pagesisphysicalpagesexistingwithinthezone,which *iscalculatedas: *present_pages=spanned_pages-absent_pages(pagesinholes); * *managed_pagesispresentpagesmanagedbythebuddysystem,which *iscalculatedas(reserved_pagesincludespagesallocatedbythe *bootmemallocator): *managed_pages=present_pages-reserved_pages; * */ unsigned long managed_pages; unsigned long spanned_pages; unsigned long present_pages; const char *name; ...... /*freeareasofdifferentsizes*/ struct free_area free_area ; /*zoneflags,seebelow*/ unsigned long flags; /*Primarilyprotectsfree_area*/ spinlock_t lock; ...... }____cacheline_internodealigned_in_ zone_start_pfn:表示属于这个zone的第一个页 spanned_pages:看注释我们可以知道,spanned_pages = zone_end_pfn - zone_start_pfn,表示该区域的所有物理内存的页面数,包括内存空洞 present_pages:看注释我们可以知道,present_pages = spanned_pages - absent_pages(pages in holes),表示该区域真实存在的物理内存页面数,不包括空洞 managed_pages:看注释我们可以知道,managed_pages = present_pages - reserved_pages,被伙伴系统管理的所有页面数。 per_cpu_pageset:用于区分冷热页, 什么叫冷热页呢?咱们讲 x86 体系结构的时候讲过,为了让 CPU 快速访问段描述符,在 CPU 里面有段描述符缓存。CPU 访问这个缓存的速度比内存快得多。同样对于页面来讲,也是这样的。如果一个页被加载到 CPU 高速缓存里面,这就是一个热页(Hot Page),CPU 读起来速度会快很多,如果没有就是冷页(Cold Page)。由于每个 CPU 都有自己的高速缓存,因而 per_cpu_pageset 也是每个 CPU 一个。 image.png 更多干货可见: 高级工程师聚集地 ,助力大家更上一层楼! 3、内存页page 内存页是物理内存最小单位,有时也叫页帧(page frame),Linux会为系统的物理内存的每一个页都创建了struct page对象,并用全局变量struct page *mem_map来存放所有物理页page对象的指针,页的大小取决于MMU(Memory Management Unit),后者主要用来将虚拟地址空间转换为物理地址空间。 看一下page的结构体 struct page { unsigned long flags; /*Atomicflags,somepossibly *updatedasynchronously*/ union { struct { /*Pagecacheandanonymouspages*/ /** *@lru:Pageoutlist,eg.active_listprotectedby *zone_lru_lock.Sometimesusedasagenericlist *bythepageowner. */ struct list_head lru ; /*Seepage-flags.hforPAGE_MAPPING_FLAGS*/ struct address_space * mapping ; pgoff_t index; /*Ouroffsetwithinmapping.*/ /** *@private:Mapping-privateopaquedata. *Usuallyusedforbuffer_headsifPagePrivate. *Usedforswp_entry_tifPageSwapCache. *IndicatesorderinthebuddysystemifPageBuddy. */ unsigned long private ; }; struct { /*slab,slobandslub*/ union { struct list_head slab_list ; /*useslru*/ struct { /*Partialpages*/ struct page * next ; # ifdef CONFIG_64BIT int pages; /*Nrofpagesleft*/ int pobjects; /*Approximatecount*/ # else short int pages; short int pobjects; # endif }; }; struct kmem_cache * slab_cache ; /*notslob*/ /*Double-wordboundary*/ void *freelist; /*firstfreeobject*/ union { void *s_mem; /*slab:firstobject*/ unsigned long counters; /*SLUB*/ struct { /*SLUB*/ unsigned inuse: 16 ; unsigned objects: 15 ; unsigned frozen: 1 ; }; }; }; ..... } 我们能够看到struct page有很多union组成,union 结构是在 C 语言中被用于 同一块内存根据情况保存不同类型数据的一种方式 。这里之所以用了 union,是因为一个物理页面使用模式有多种。 第一种模式:直接用一整页,这一整页的物理内存直接与虚拟地址空间建立映射关系,我们把这种称为匿名页(Anonymous Page)。或者用于关联一个文件,然后再和虚拟地址空间建立映射关系,这样的文件,我们称为内存映射文件(Memory-mapped File),这种分配页级别的,Linux采用一种被称为 伙伴系统 (Buddy System)的技术。 第二种模式:仅需要分配小的内存块。有时候,我们不需要一下子分配这么多的内存,例如分配一个 task_struct 结构,只需要分配小块的内存,去存储这个进程描述结构的对象。为了满足对这种小内存块的需要,Linux 系统采用了一种被称为 slab allocator 的技术 上面说的两种,都是页的分配方式,也就是物理内存的分配方式,下一章,我们继续深入分析物理内存的这两种分配方式。 img