一、总览
对于物理内存内存,linux对内存的组织逻辑从上到下依次是:node,zone,page,这些page是根据buddy分配算法组织的,看下面两张图:
上面的概念做下简单的介绍:
- Node:每个CPU下的本地内存节点就是一个Node,如果是UMA架构下,就只有一个Node0,在NUMA架构下,会有多个Node
- Zone:每个Node会划分很多域Zone,大概有下面这些:
- ZONE_DMA:定义适合DMA的内存域,该区域的长度依赖于处理器类型。比如ARM所有地址都可以进行DMA,所以该值可以很大,或者干脆不定义DMA类型的内存域。而在IA-32的处理器上,一般定义为16M。
- ZONE_DMA32:只在64位系统上有效,为一些32位外设DMA时分配内存。如果物理内存大于4G,该值为4G,否则与实际的物理内存大小相同。
- ZONE_NORMAL:定义可直接映射到内核空间的普通内存域。在64位系统上,如果物理内存小于4G,该内存域为空。而在32位系统上,该值最大为896M。
- ZONE_HIGHMEM:只在32位系统上有效,标记超过896M范围的内存。在64位系统上,由于地址空间巨大,超过4G的内存都分布在ZONE_NORMA内存域。
- ZONE_MOVABLE:伪内存域,为了实现减小内存碎片的机制。
- 分配价值链
- 除了只能在某个区域分配的内存(比如ZONE_DMA),普通的内存分配会有一个"价值"的层次结构,按分配的"廉价度"依次为:ZONE_HIGHMEM > ZONE_NORMAL > ZONE_DMA。
- 即内核在进行内存分配时,优先从高端内存进行分配,其次是普通内存,最后才是DMA内存
- Page :zone下面就是真正的内存页了,每个页基础大小是4K,他们维护在一个叫free_area的数组结构中
- order:数组的index,也叫order,实际对应的是page的大小,比如order为0,那么就是一堆1个空闲页(4K)组成的链表,order为1,就是一堆2个空闲页(8K)组成的链表,order为2,就是一堆4个空闲页(16K)组成的链表
二、源码分析
内存节点结构体在linux内核include/linux/mmzone.h文件
level | struct | desc |
---|---|---|
node | struct pglist_data | NUMA下每个node由一个pglist_data结构体描述 UMA下只有一个node,即全局变量struct pglist_data contig_page_data; |
zone | struct zone | 每个node下的物理内存被划分为不同zone,体现不同用途。整个系统里可能只有几个struct zone 。 |
page | struct page | 描述每个物理页(page frame)的结构,每个物理页都有一个struct page ,寸土寸金。 |
2.1、 struct pglist_data
mmzone.h - include/linux/mmzone.h - Linux source code v5.4.285 - Bootlin Elixir Cross Referencer
每个NUMA node对应一个struct pglist_data
结构体,一些重要成员有:
name | type | desc |
---|---|---|
node_zones | struct zone 数组,长度为MAX_NR_ZONES |
该node下的zone。数组长度虽然是MAX_NR_ZONES,但是不代表当前node的实际zone数目就是MAX_NR_ZONES。实际的数量用nr_zones表示 |
node_zonelists | struct zonelist 数组,长度为MAX_ZONELISTS |
内存分配器在分配内存时,按照一定的策略遍历不同的内存区域,以找到合适的内存块。不同的内存区域可能有不同的属性(dma,normal,high等),node_zonelists帮助内存分配器根据需求选择合适的区域 |
nr_zones | int | 该node下实际zone的数量 |
node_start_pfn | unsigned long long | node的起始pfn号,起始的物理地址 |
node_present_pages | unsigned long long | node实际包括的page数目(不含空洞) |
node_spanned_pages | unsigned long long | node横跨的page数目(包括空洞) |
kswapd | struct task_struct * | 每个node都有一个kswapd线程,用于回收不经常使用的页面或者内存不足时回收内存 |
kswapd_wait | struct wait_queue_head | kswapd_wait表示是一个kswapd等待队列,里面存放的是等待kswapd线程执行异步回收的线程,在free_area_init_core 函数中被初始化。 |
pfmemalloc_wait | struct wait_queue_head | 表示等待直接内存回收(direct reclaim)结束的线程等待队列。里面存放的都是等待由kswapd帮忙做完直接内存回收的线程。当kswapd直接内存回收后,整个node的free pages满足要求时,在kswapd睡眠前,kswapd会唤醒pfmemalloc_wait里面的线程,线程直接进行内存分配,这个等待队列的线程跳过了自己direct reclaim的操作。 |
kswapd_order | int | 表示kswapd线程内存回收的单位(2^kswapd_order),要求大于线程内存分配所需求的order,否则会更新为线程内存分配对应的order。 |
node_mem_map; | struct page * | 当前node中所有struct page构成的mem_map数组 |
常见的zone
类型有:
- ZONE_DMA,一些设备地址线数目有限(如24位),进行DMA时只能使用低地址(如<16MB),划分出ZONE_DMA防止低地址的页被分出去导致设备无法进行DMA。
- ZONE_DMA32,64位系统下,区分支持32位地址线和24位地址线的DMA设备。
- ZONE_NORMAL,一般内存处于这个zone区域。
- ZONE_HIGHMEM,32位系统虚拟地址空间太小,只有1G给内核态,因此只能映射最多1G物理内存。高端内存区用于建立到物理地址的临时映射,使内核态可以寻址更多物理地址。64位架构下虚拟地址空间足够大,不需要这个zone。
cpp
enum zone_type {
#ifdef CONFIG_ZONE_DMA
/*
* ZONE_DMA is used when there are devices that are not able
* to do DMA to all of addressable memory (ZONE_NORMAL). Then we
* carve out the portion of memory that is needed for these devices.
* The range is arch specific.
*
* Some examples
*
* Architecture Limit
* ---------------------------
* parisc, ia64, sparc <4G
* s390, powerpc <2G
* arm Various
* alpha Unlimited or 0-16MB.
*
* i386, x86_64 and multiple other arches
* <16M.
*/
ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
/*
* x86_64 needs two ZONE_DMAs because it supports devices that are
* only able to do DMA to the lower 16M but also 32 bit devices that
* can only do DMA areas below 4G.
*/
ZONE_DMA32,
#endif
/*
* Normal addressable memory is in ZONE_NORMAL. DMA operations can be
* performed on pages in ZONE_NORMAL if the DMA devices support
* transfers to all addressable memory.
*/
ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
/*
* A memory area that is only addressable by the kernel through
* mapping portions into its own address space. This is for example
* used by i386 to allow the kernel to address the memory beyond
* 900MB. The kernel will set up special mappings (page
* table entries on i386) for each page that the kernel needs to
* access.
*/
ZONE_HIGHMEM,
#endif
ZONE_MOVABLE,
#ifdef CONFIG_ZONE_DEVICE
ZONE_DEVICE,
#endif
__MAX_NR_ZONES
};
2.2、 struct zone
mmzone.h - include/linux/mmzone.h - Linux source code v5.4.285 - Bootlin Elixir Cross Referencer
zone
通过pfn和一些计数器描述它管理的物理地址空间:
zone_start_pfn
,该zone
所描述空间的起始页号。spanned_pages
,整个空间(包括空洞)占据的物理页数目,它等于zone_end_pfn - zone_start_pfn
。present_pages
,存在于空间中的物理页数目,等于spanned_pages
减掉空洞中的页数目。managed_pages
,被buddy system管理的页数目,等于present_pages
减掉被zone
预留做其他用途的页数目。struct free_area free_area[MAX_ORDER]
,buddy system使用的空闲链表,是一个per zone的结构。它直接存储了指向struct page*
的指针。(详细见:伙伴(buddy)系统原理_伙伴系统算法-CSDN博客)- unsigned long _watermark[NR_WMARK]:内存水位值,分别为WMARK_MIN, WMARK_LOW, WMARK_HIGH水位,这在页面分配器和kswapd页面回收中会用到。
- long lowmem_reserve[MAX_NR_ZONES]:zone中预留的内存。在内存分配时,高区的内存不足可以向低区的内存申请。reserve内存是确保在内存紧张的时候,仍然保留一定数量的低区内存,确保高区内存不会完全耗尽低区内存资源。
2.3、 struct page
mm_types.h - include/linux/mm_types.h - Linux source code v5.4.285 - Bootlin Elixir Cross Referencer
这是一个特别复杂的结构,里面有很多的 union,这里之所以用了 union,是因为一个物理页面使用模式有两种。第一种模式,仅需分配小块内存,Linux 系统采用了一种被称为 slab allocator的技术。第二种模式,要用就用一整页。这一整页的内存,或者直接和虚拟地址空间建立映射关系,我们把这种称为匿名页(Anonymous Page)。或者用于关联一个文件,然后再和虚拟地址空间建立映射关系,这样的文件,我们称为内存映射文件(Memory-mapped File)。
每个物理页对应一个page结构体,称为页描述符,内存节点的pglist_data实例的成员node_mem_map指向该内存节点包含的所有物理页的页描述符组成的数组。
一些重要成员有:
|-------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| name | type | desc |
| flags | unsigned long | enum pgdat_flags 可以指示节点的状态或特定的内存管理属性。以便在内存分配、回收和其他操作中做出相应的决策。 以下是一些常见的 pgdat_flags 标志: PGDAT_CONGESTED:表示该节点的内存可能处于拥塞状态。 PGDAT_WRITEBACK:表示该节点的内存正在进行写回操作。 PGDAT_RECLAIM_LOCKED:表示该节点的内存回收操作被锁定。 |
| | | |
| | | |
| | | |
三、建立与初始化过程
ref:
Linux内存管理-1 | Do not touch fish!
Linux内存初始化(3)------pglist_data/zone初始化_linux struct page结构初始化-CSDN博客