Linux深入理解内存管理19(基于Linux6.6)---内存组织介绍
一、基本概念
在现代计算机系统中,内存的访问方式对系统性能有着重要的影响。尤其是在多处理器(或多核)系统中,不同的内存组织方式可能导致显著的性能差异。**NUMA(Non-Uniform Memory Access)和UMA(Uniform Memory Access)**是两种主要的内存访问模型,在多处理器系统中具有不同的架构和性能特性。
1.1、UMA(Uniform Memory Access)
UMA即统一内存访问,是一种内存访问模型,在这种模型下,所有处理器(CPU)访问系统内存的时间延迟是相同的。具体来说,在UMA系统中,所有CPU对所有内存的访问速度是均等的,不会因为访问不同的内存区域而产生性能差异。
1、UMA的特征:
- 对称多处理(SMP)架构: 在UMA系统中,所有处理器共享同一块物理内存,每个处理器都能以相同的延迟和带宽访问内存。
- 内存访问时间一致: 所有的CPU对内存的访问速度相同,不受内存位置的影响。
- 简化的内存管理: 由于内存访问时间没有差异,操作系统不需要特别关注内存的分配和调度,因此内存管理相对简单。
2、UMA的优点:
- 简单易实现: UMA系统的架构较为简单,不需要复杂的内存访问控制。
- 均等的内存访问速度: 所有CPU可以公平地访问内存,适合大多数对内存带宽要求较低的应用。
3、UMA的缺点:
- 性能瓶颈: 在多处理器系统中,随着处理器数量的增加,访问共享内存的竞争可能会导致性能瓶颈,尤其是当多个处理器同时访问内存时。
- 不适合大规模系统: UMA通常适用于中小规模的多处理器系统,难以扩展到大型系统。
1.2、NUMA(Non-Uniform Memory Access)
NUMA是一种非统一内存访问的模型,通常用于具有多个处理器和大内存的计算机系统。与UMA不同,NUMA系统中每个处理器具有自己的本地内存(也称为节点内存),并且可以访问其他处理器的内存,但是访问非本地内存(即远程内存)通常会导致更大的延迟和带宽瓶颈。
1、NUMA的特征:
- 多节点系统: NUMA系统通常由多个处理器节点组成,每个节点都有自己独立的本地内存。每个节点内的CPU与内存的访问速度较快,但访问其他节点的内存则会引入更高的延迟。
- 非均匀访问时间: 在NUMA系统中,每个CPU的本地内存访问时间较短,而访问其他节点的内存则需要更长的时间,因此存在“非均匀”的内存访问延迟。
- 节点局部性: 由于每个节点的CPU与其本地内存之间的访问延迟较低,NUMA系统通常会尽量保证进程的内存分配在本地节点上,以减少远程内存访问的开销。
2、NUMA的优点:
- 可扩展性: NUMA架构适用于大规模的多处理器系统,可以有效扩展到更大的处理器和内存规模。
- 性能优化: 在NUMA系统中,如果进程能在本地节点内存上运行,能够大大减少内存访问延迟,提升性能。因此,操作系统可以通过内存亲和性(NUMA Affinity)来优化进程调度和内存分配。
3、NUMA的缺点:
- 复杂的内存管理: NUMA系统要求操作系统和应用程序能够智能地管理和优化内存的分配和访问,避免频繁的远程内存访问。否则,可能会导致性能瓶颈。
- 非均匀性: 由于存在不同的访问延迟和带宽限制,系统的整体性能可能受到影响,特别是在高负载或大规模系统中。
1.3、NUMA与UMA的比较
1、内存访问延迟:
- 在UMA系统中,所有处理器访问内存的延迟是相同的,而在NUMA系统中,访问本地内存的延迟较低,访问远程内存的延迟较高。
2、可扩展性:
- UMA系统在扩展时性能可能下降,因为多个处理器共享同一块内存,而NUMA系统则通过分布式的内存架构提高了可扩展性,支持更多的处理器和内存模块。
3、性能优化:
- UMA系统在内存访问方面是均匀的,因此内存管理相对简单。而NUMA系统需要操作系统和应用程序做更多的优化,确保内存分配和处理器调度能够充分利用本地内存,减少远程访问。
1.4、Node、Zone和页三级结构来描述物理内存
NUMA(Non-Uniform Memory Access,非统一内存访问)和UMA(Uniform Memory Access,统一内存访问):
- NUMA是从处理器对内存访问速度不同的结构
- UMA是处理器与所有内存的访问速度相同的结构
结点Node:
- 从1个CPU访问速度相同的内存集合
- 每个CPU对应一个本地物理内存
- 在内核中用pg_data_t类型,表示节点的结构体成为节点描述符
ZONE:
- 节点中具有相同属性的区域
- 内核使用struct zone结构体管理
页帧Page:
- ZONE中管理物理内存的最小单位称为页帧
- 页帧在Linux中由page结构体管理,通过mem_map全局数据访问
- Linux采用Node、Zone和页三级结构来描述物理内存的,如图所示:
二、结点
Linux采用一个struct pg_data_t结构体来描述系统的内存,每一个Node都对应一个struct pglist_data,系统中每个节点都挂接在一个pgdat_list列表中,对于NUMA系统中一个,使用的全局变量struct pglist_data __refdata contig_page_data。
include/linux/mmzone.h
typedef struct pglist_data {
/*
* node_zones contains just the zones for THIS node. Not all of the
* zones may be populated, but it is the full list. It is referenced by
* this node's node_zonelists as well as other node's node_zonelists.
*/
struct zone node_zones[MAX_NR_ZONES];
/*
* node_zonelists contains references to all zones in all nodes.
* Generally the first zones will be references to this node's
* node_zones.
*/
struct zonelist node_zonelists[MAX_ZONELISTS];
int nr_zones; /* number of populated zones in this node */
#ifdef CONFIG_FLATMEM /* means !SPARSEMEM */
struct page *node_mem_map;
#ifdef CONFIG_PAGE_EXTENSION
struct page_ext *node_page_ext;
#endif
#endif
#if defined(CONFIG_MEMORY_HOTPLUG) || defined(CONFIG_DEFERRED_STRUCT_PAGE_INIT)
/*
* Must be held any time you expect node_start_pfn,
* node_present_pages, node_spanned_pages or nr_zones to stay constant.
* Also synchronizes pgdat->first_deferred_pfn during deferred page
* init.
*
* pgdat_resize_lock() and pgdat_resize_unlock() are provided to
* manipulate node_size_lock without checking for CONFIG_MEMORY_HOTPLUG
* or CONFIG_DEFERRED_STRUCT_PAGE_INIT.
*
* Nests above zone->lock and zone->span_seqlock
*/
spinlock_t node_size_lock;
#endif
unsigned long node_start_pfn;
unsigned long node_present_pages; /* total number of physical pages */
unsigned long node_spanned_pages; /* total size of physical page
range, including holes */
int node_id;
wait_queue_head_t kswapd_wait;
wait_queue_head_t pfmemalloc_wait;
/* workqueues for throttling reclaim for different reasons. */
wait_queue_head_t reclaim_wait[NR_VMSCAN_THROTTLE];
atomic_t nr_writeback_throttled;/* nr of writeback-throttled tasks */
unsigned long nr_reclaim_start; /* nr pages written while throttled
* when throttling started. */
#ifdef CONFIG_MEMORY_HOTPLUG
struct mutex kswapd_lock;
#endif
struct task_struct *kswapd; /* Protected by kswapd_lock */
int kswapd_order;
enum zone_type kswapd_highest_zoneidx;
int kswapd_failures; /* Number of 'reclaimed == 0' runs */
#ifdef CONFIG_COMPACTION
int kcompactd_max_order;
enum zone_type kcompactd_highest_zoneidx;
wait_queue_head_t kcompactd_wait;
struct task_struct *kcompactd;
bool proactive_compact_trigger;
#endif
/*
* This is a per-node reserve of pages that are not available
* to userspace allocations.
*/
unsigned long totalreserve_pages;
#ifdef CONFIG_NUMA
/*
* node reclaim becomes active if more unmapped pages exist.
*/
unsigned long min_unmapped_pages;
unsigned long min_slab_pages;
#endif /* CONFIG_NUMA */
/* Write-intensive fields used by page reclaim */
CACHELINE_PADDING(_pad1_);
#ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
/*
* If memory initialisation on large machines is deferred then this
* is the first PFN that needs to be initialised.
*/
unsigned long first_deferred_pfn;
#endif /* CONFIG_DEFERRED_STRUCT_PAGE_INIT */
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
struct deferred_split deferred_split_queue;
#endif
#ifdef CONFIG_NUMA_BALANCING
/* start time in ms of current promote rate limit period */
unsigned int nbp_rl_start;
/* number of promote candidate pages at start time of current rate limit period */
unsigned long nbp_rl_nr_cand;
/* promote threshold in ms */
unsigned int nbp_threshold;
/* start time in ms of current promote threshold adjustment period */
unsigned int nbp_th_start;
/*
* number of promote candidate pages at stat time of current promote
* threshold adjustment period
*/
unsigned long nbp_th_nr_cand;
#endif
/* Fields commonly accessed by the page reclaim scanner */
/*
* NOTE: THIS IS UNUSED IF MEMCG IS ENABLED.
*
* Use mem_cgroup_lruvec() to look up lruvecs.
*/
struct lruvec __lruvec;
unsigned long flags;
#ifdef CONFIG_LRU_GEN
/* kswap mm walk data */
struct lru_gen_mm_walk mm_walk;
#endif
CACHELINE_PADDING(_pad2_);
/* Per-node vmstats */
struct per_cpu_nodestat __percpu *per_cpu_nodestats;
atomic_long_t vm_stat[NR_VM_NODE_STAT_ITEMS];
#ifdef CONFIG_NUMA
struct memory_tier __rcu *memtier;
#endif
} pg_data_t;
下面就对结构体提的主要域进行说明:
成员变量 | 类型 | 定义/描述 | 说明 |
---|---|---|---|
node_zones | struct zone *[MAX_NR_ZONES] | 这是一个指向每个内存区域的指针数组,表示该节点上所有的内存区域(zones)。 | 每个 NUMA 节点可以有多个内存区域(如 ZONE_DMA , ZONE_NORMAL , ZONE_HIGHMEM 等)。该数组存储了节点上的各个内存区域的指针。 |
node_zonelists | struct zonelist | 指向每个节点的 zonelist 的指针。zonelist 用于表示内存区域的优先级顺序。 | 用于描述节点内存区域的查找顺序,通常涉及到内存分配策略,帮助决定在哪些区域进行内存分配。 |
nr_zones | unsigned short | 该节点的内存区域数量。 | 表示当前 NUMA 节点上存在的内存区域的数量,通常是 ZONE_DMA 、ZONE_NORMAL 、ZONE_HIGHMEM 等区域的个数。 |
node_mem_map | pg_data_t * | 指向该节点的 pg_data_t 结构体,该结构体包含了该节点的页框信息。 | 存储该 NUMA 节点上的内存页的管理结构信息。每个 NUMA 节点都有一个 pg_data_t 结构体,描述该节点内存的详细信息。 |
node_start_pfn | unsigned long | 当前 NUMA 节点的起始页框号(Page Frame Number,PFN)。 | 表示该节点内存的开始页框号,用于定位节点内存区域的起始位置。 |
node_present_pages | unsigned long | 当前 NUMA 节点上实际存在的内存页数。 | 表示该节点上所有实际存在的页面数量,包含已被标记为可用或可访问的页面。 |
node_spanned_pages | unsigned long | 当前 NUMA 节点的跨越页数,通常表示该节点所跨越的物理内存总页数。 | 表示该节点的内存页的总数量,包括实际存在和未使用的页面。 |
kswapd | struct task_struct * | 指向该节点的 kswapd 线程的指针。 | kswapd 是内核中的一个内存回收线程,负责在内存不足时释放内存。每个 NUMA 节点可能都有自己的 kswapd 线程。 |
三、ZONE
每个结点的内存被分为多个块,称为zones,它表示内存中一段区域。一个zone用struct_zone_t结构描述,zone的类型主要有ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。ZONE_DMA位于低端的内存空间,用于某些旧的ISA设备,ISA总线的直接内存存储DMA,只能对RAM的前16MB进行寻址。ZONE_NORMAL的内存直接映射到Linux内核线性地址空间的高端部分,许多内核操作只能在ZONE_NORMAL中进行。因此对于内核来说, 不同范围的物理内存采用不同的管理方式和映射方式,Linux使用enum zone_type来标记内核所支持的所有内存区域。
include/linux/mmzone.h
enum zone_type
{
#ifdef CONFIG_ZONE_DMA
ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
ZONE_DMA32,
#endif
ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
ZONE_HIGHMEM,
#endif
ZONE_MOVABLE,
#ifdef CONFIG_ZONE_DEVICE
ZONE_DEVICE,
#endif
__MAX_NR_ZONES
}
枚举值 | 内存区域名称 | 条件编译宏 | 描述 |
---|---|---|---|
ZONE_DMA | DMA 区域 | CONFIG_ZONE_DMA | 适用于直接内存访问(DMA)的内存区域,通常位于低地址范围。用于老旧硬件需要直接访问内存的场景。 |
ZONE_DMA32 | DMA32 区域 | CONFIG_ZONE_DMA32 | 适用于支持 32 位 DMA 的区域,内存位于 4GB 以下,适用于较老的 32 位设备和架构。 |
ZONE_NORMAL | 正常内存区域 | 无 | 大部分内存区域都属于正常区域。通常由操作系统内核和用户空间应用程序使用。 |
ZONE_HIGHMEM | 高内存区域 | CONFIG_HIGHMEM | 高内存区域,通常在 32 位架构中使用,指的是超过内核能够直接访问的物理内存。必须通过特殊机制来访问这些内存。 |
ZONE_MOVABLE | 可移动内存区域 | 无 | 可移动内存区域,允许被系统移动的内存区域,通常用于内核和用户进程的内存回收。 |
ZONE_DEVICE | 设备内存区域 | CONFIG_ZONE_DEVICE | 用于设备的内存区域(如设备的直接内存映射内存)。通常是特殊设备所需的内存区域。 |
include/linux/mmzone.h
struct zone {
/* Read-mostly fields */
/* zone watermarks, access with *_wmark_pages(zone) macros */
unsigned long _watermark[NR_WMARK];
unsigned long watermark_boost;
unsigned long nr_reserved_highatomic;
/*
* We don't know if the memory that we're going to allocate will be
* freeable or/and it will be released eventually, so to avoid totally
* wasting several GB of ram we must reserve some of the lower zone
* memory (otherwise we risk to run OOM on the lower zones despite
* there being tons of freeable ram on the higher zones). This array is
* recalculated at runtime if the sysctl_lowmem_reserve_ratio sysctl
* changes.
*/
long lowmem_reserve[MAX_NR_ZONES];
#ifdef CONFIG_NUMA
int node;
#endif
struct pglist_data *zone_pgdat;
struct per_cpu_pages __percpu *per_cpu_pageset;
struct per_cpu_zonestat __percpu *per_cpu_zonestats;
/*
* the high and batch values are copied to individual pagesets for
* faster access
*/
int pageset_high;
int pageset_batch;
#ifndef CONFIG_SPARSEMEM
/*
* Flags for a pageblock_nr_pages block. See pageblock-flags.h.
* In SPARSEMEM, this map is stored in struct mem_section
*/
unsigned long *pageblock_flags;
#endif /* CONFIG_SPARSEMEM */
/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
unsigned long zone_start_pfn;
atomic_long_t managed_pages;
unsigned long spanned_pages;
unsigned long present_pages;
#if defined(CONFIG_MEMORY_HOTPLUG)
unsigned long present_early_pages;
#endif
#ifdef CONFIG_CMA
unsigned long cma_pages;
#endif
const char *name;
#ifdef CONFIG_MEMORY_ISOLATION
/*
* Number of isolated pageblock. It is used to solve incorrect
* freepage counting problem due to racy retrieving migratetype
* of pageblock. Protected by zone->lock.
*/
unsigned long nr_isolate_pageblock;
#endif
#ifdef CONFIG_MEMORY_HOTPLUG
/* see spanned/present_pages for more description */
seqlock_t span_seqlock;
#endif
int initialized;
/* Write-intensive fields used from the page allocator */
CACHELINE_PADDING(_pad1_);
/* free areas of different sizes */
struct free_area free_area[MAX_ORDER];
/* zone flags, see below */
unsigned long flags;
/* Primarily protects free_area */
spinlock_t lock;
/* Write-intensive fields used by compaction and vmstats. */
CACHELINE_PADDING(_pad2_);
/*
* When free pages are below this point, additional steps are taken
* when reading the number of free pages to avoid per-cpu counter
* drift allowing watermarks to be breached
*/
unsigned long percpu_drift_mark;
#if defined CONFIG_COMPACTION || defined CONFIG_CMA
/* pfn where compaction free scanner should start */
unsigned long compact_cached_free_pfn;
/* pfn where compaction migration scanner should start */
unsigned long compact_cached_migrate_pfn[ASYNC_AND_SYNC];
unsigned long compact_init_migrate_pfn;
unsigned long compact_init_free_pfn;
#endif
#ifdef CONFIG_COMPACTION
/*
* On compaction failure, 1<<compact_defer_shift compactions
* are skipped before trying again. The number attempted since
* last failure is tracked with compact_considered.
* compact_order_failed is the minimum compaction failed order.
*/
unsigned int compact_considered;
unsigned int compact_defer_shift;
int compact_order_failed;
#endif
#if defined CONFIG_COMPACTION || defined CONFIG_CMA
/* Set to true when the PG_migrate_skip bits should be cleared */
bool compact_blockskip_flush;
#endif
bool contiguous;
CACHELINE_PADDING(_pad3_);
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
} ____cacheline_internodealigned_in_smp;
zone它跟踪页框使用、空闲区域和锁等信息,结构体主要说明如下:
成员变量 | 类型 | 定义/描述 | 说明 |
---|---|---|---|
watermark | unsigned long [NR_WMARK] | 每个内存区域的水印值,用于表示内存管理中的“警戒线”或“阈值”。 | 水印值表示内存区域中的可用内存量的低、中、高水位,帮助内存分配器判断是否需要触发回收机制。NR_WMARK 定义了水位的数量。 |
lowmem_reserve | unsigned long [NR_LowMemoryReserve] | 用于保留低内存的数组,在低内存条件下防止内存分配失败。 | 主要用于内存区域的保留策略,确保系统内存不至于过度紧张,通常用于高优先级的内存分配场景。 |
zone_pgdat | struct pg_data_t * | 指向该内存区域的 NUMA 节点内存描述符(pg_data_t )。 | 存储该区域所在的 NUMA 节点的信息,pg_data_t 结构体描述了与该区域相关的内存管理数据。 |
zone_start_pfn | unsigned long | 当前内存区域的起始页框号(Page Frame Number,PFN)。 | 表示该内存区域的起始页框号,帮助内核确定该区域的物理内存起始地址。 |
managed_pages | unsigned long | 当前内存区域内已管理的页面数量。 | 代表当前内存区域内已经被内核管理并且有效的页面数量。 |
spanned_pages | unsigned long | 当前内存区域跨越的总页数,包括所有的内存区域,不管该区域是否被管理或有效。 | 表示当前内存区域覆盖的物理页面数量,包括未被分配或未使用的内存页。 |
present_pages | unsigned long | 当前内存区域上存在的有效页面数。 | 表示当前内存区域内实际存在并可用的物理页面数量。这些页面是经过初始化且可以访问的页面。 |
当系统中可用的内存比较少时,kswapd将被唤醒,并进行页交换。如果需要内存的压力非常大,进程将同步释放内存。每个zone有三个阙值,成为pages_low/pages_min/pages_high,用于跟踪该zone的内存压力。
- pages_min的页框数是由内存初始化free_area_init_core函数,根据zone内页框的比例计算,最小为20页,最大一般为255页
- 当到达pages_min时,说明页面数非常紧张,分配页面的动作和kswapd线程同步运行
- 当空闲也的数目达到pages_low时,说明页面刚开始紧张,则kswapd线程将被唤醒,并开始释放回收页面
- 当达到pages_high时,说明内存页面数很充足,不需要回收,kswapd线程将重新休眠,通常这个数值是page_min的3倍
四、page
每个物理页框都需要一个对应的page结构来进行管理,记录分配状态,分配和回收,互斥以及同步存在。 因为内核会为每一个物理页帧创建一个struct page的结构体,因此要保证page结构体足够的小,否则仅struct page就要占用大量的内存。出于节省内存的考虑,struct page中使用了大量的联合体union。
include/linux/mm_types.h
struct page {
unsigned long flags; /* Atomic flags, some possibly
* updated asynchronously */
/*
* Five words (20/40 bytes) are available in this union.
* WARNING: bit 0 of the first word is used for PageTail(). That
* means the other users of this union MUST NOT use the bit to
* avoid collision and false-positive PageTail().
*/
union {
struct { /* Page cache and anonymous pages */
/**
* @lru: Pageout list, eg. active_list protected by
* lruvec->lru_lock. Sometimes used as a generic list
* by the page owner.
*/
union {
struct list_head lru;
/* Or, for the Unevictable "LRU list" slot */
struct {
/* Always even, to negate PageTail */
void *__filler;
/* Count page's or folio's mlocks */
unsigned int mlock_count;
};
/* Or, free page */
struct list_head buddy_list;
struct list_head pcp_list;
};
/* See page-flags.h for PAGE_MAPPING_FLAGS */
struct address_space *mapping;
pgoff_t index; /* Our offset within mapping. */
/**
* @private: Mapping-private opaque data.
* Usually used for buffer_heads if PagePrivate.
* Used for swp_entry_t if PageSwapCache.
* Indicates order in the buddy system if PageBuddy.
*/
unsigned long private;
};
struct { /* page_pool used by netstack */
/**
* @pp_magic: magic value to avoid recycling non
* page_pool allocated pages.
*/
unsigned long pp_magic;
struct page_pool *pp;
unsigned long _pp_mapping_pad;
unsigned long dma_addr;
union {
/**
* dma_addr_upper: might require a 64-bit
* value on 32-bit architectures.
*/
unsigned long dma_addr_upper;
/**
* For frag page support, not supported in
* 32-bit architectures with 64-bit DMA.
*/
atomic_long_t pp_frag_count;
};
};
struct { /* Tail pages of compound page */
unsigned long compound_head; /* Bit zero is set */
/* First tail page only */
unsigned char compound_dtor;
unsigned char compound_order;
atomic_t compound_mapcount;
atomic_t compound_pincount;
#ifdef CONFIG_64BIT
unsigned int compound_nr; /* 1 << compound_order */
#endif
};
struct { /* Second tail page of compound page */
unsigned long _compound_pad_1; /* compound_head */
unsigned long _compound_pad_2;
/* For both global and memcg */
struct list_head deferred_list;
};
struct { /* Page table pages */
unsigned long _pt_pad_1; /* compound_head */
pgtable_t pmd_huge_pte; /* protected by page->ptl */
unsigned long _pt_pad_2; /* mapping */
union {
struct mm_struct *pt_mm; /* x86 pgds only */
atomic_t pt_frag_refcount; /* powerpc */
};
#if ALLOC_SPLIT_PTLOCKS
spinlock_t *ptl;
#else
spinlock_t ptl;
#endif
};
struct { /* ZONE_DEVICE pages */
/** @pgmap: Points to the hosting device page map. */
struct dev_pagemap *pgmap;
void *zone_device_data;
/*
* ZONE_DEVICE private pages are counted as being
* mapped so the next 3 words hold the mapping, index,
* and private fields from the source anonymous or
* page cache page while the page is migrated to device
* private memory.
* ZONE_DEVICE MEMORY_DEVICE_FS_DAX pages also
* use the mapping, index, and private fields when
* pmem backed DAX files are mapped.
*/
};
/** @rcu_head: You can use this to free a page by RCU. */
struct rcu_head rcu_head;
};
union { /* This union is 4 bytes in size. */
/*
* If the page can be mapped to userspace, encodes the number
* of times this page is referenced by a page table.
*/
atomic_t _mapcount;
/*
* If the page is neither PageSlab nor mappable to userspace,
* the value stored here may help determine what this page
* is used for. See page-flags.h for a list of page types
* which are currently stored here.
*/
unsigned int page_type;
};
/* Usage count. *DO NOT USE DIRECTLY*. See page_ref.h */
atomic_t _refcount;
#ifdef CONFIG_MEMCG
unsigned long memcg_data;
#endif
/*
* On machines where all RAM is mapped into kernel address space,
* we can simply calculate the virtual address. On machines with
* highmem some memory is mapped into kernel virtual memory
* dynamically, so we need a place to store that address.
* Note that this field could be 16 bits on x86 ... ;)
*
* Architectures with slow multiplication can define
* WANT_PAGE_VIRTUAL in asm/page.h
*/
#if defined(WANT_PAGE_VIRTUAL)
void *virtual; /* Kernel virtual address (NULL if
not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */
#ifdef CONFIG_KMSAN
/*
* KMSAN metadata for this page:
* - shadow page: every bit indicates whether the corresponding
* bit of the original page is initialized (0) or not (1);
* - origin page: every 4 bytes contain an id of the stack trace
* where the uninitialized value was created.
*/
struct page *kmsan_shadow;
struct page *kmsan_origin;
#endif
#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
int _last_cpupid;
#endif
} _struct_page_alignment;
五、总结
梳理了Node, Zone, Page Frame各个数据结构的成员变量和关系,对于Linux中管理内存的各个结构体之间的关系图如下图所示:
在Linux内核中,内存管理依赖于多个层级的结构体和它们之间的关系。这些结构体帮助内核有效地管理物理内存的分配、回收以及内存区域的状态等。以下是Linux内存管理的三个重要概念的结构体及它们之间的关系:
- Node(NUMA Node):表示一个物理节点,通常在非一致性内存访问(NUMA)系统中,多个节点负责各自的内存区域。
- Zone:内存区域,内存被划分为不同的区域,以便优化不同的内存管理操作。例如,分为低端内存、普通内存、DMA内存等。
- Page Frame:内存页框,物理内存被划分为固定大小的页面(通常为4KB)。每个页面由一个页面框架描述,用于实际的物理内存操作。
1、结构体及其成员变量
-
struct pg_data_t
(Node)- 代表一个NUMA节点,包含该节点的所有内存区域(zone)信息。
- 包含多个zone,指向物理内存区域。
-
struct zone
(Zone)- 每个Zone包含一系列的页框(Page Frame),并且负责该区域内的内存分配、回收、状态更新等任务。
- 包含物理内存的页表和水印信息(如
watermark
,managed_pages
)。 - 每个Zone有其起始和结束的页框编号。
-
struct page
(Page Frame)- 每个物理内存页框有一个对应的
struct page
结构体,负责描述该页面的状态(如是否被分配、是否空闲等)。 - 包括各种状态标志和指向其他页的指针,用于内存管理中的各种操作(如分配、释放等)。
- 每个物理内存页框有一个对应的
2、结构体关系图
+---------------------+ +---------------------+
| pg_data_t |<-------->| zone |
| (NUMA Node) | | (Memory Zone) |
|---------------------| |---------------------|
| - node_id | | - zone_start_pfn |
| - zone[] |----------| - spanned_pages |
| - node_lowmem_reserve[]| | - present_pages |
| - zone_pgdat[] | | - managed_pages |
+---------------------+ | - watermark[] |
+---------------------+
^
|
|
+---------------------------+
| struct page |
| (Page Frame) |
|---------------------------|
| - pfn (Page Frame Number) |
| - flags (Page status) |
| - lru (for page list) |
+---------------------------+
3、主要结构体及其成员关系
-
pg_data_t
(Node):- 一个NUMA节点的描述符,包含多个zone。
- 每个节点都有一个或多个内存区域(zone)。在NUMA架构中,物理内存被划分到不同的节点,每个节点管理它自己的内存区域。
-
zone
(Zone):- 每个Zone结构描述了一个内存区域,包含多个页面帧。
zone_start_pfn
表示该内存区域的起始页框号(Page Frame Number,PFN),spanned_pages
表示该区域跨越的页数,managed_pages
和present_pages
描述该区域内有效的内存页数。
-
page
(Page Frame):- 每个物理页面框架(Page Frame)由一个
struct page
结构体表示,包含有关该页面的信息(如是否分配、是否空闲等状态信息)。 pfn
为页面的页框号,flags
记录页面的状态(如是否可用、是否被锁定等)。
- 每个物理页面框架(Page Frame)由一个
4、关系概述
pg_data_t
代表一个 NUMA节点,包含指向多个 zone 的指针。每个节点有一个内存区域(zone)数组。- 每个 zone 结构体表示一块内存区域,并管理该区域内的内存页(Page Frame)。
zone
通过zone_start_pfn
确定区域的起始页面框号。 - 每个 page 结构体对应一个物理页面,描述了该页面的详细信息,包括页框号、状态等。
- zone 内的内存页由多个 page 结构体表示,这些结构体在内核内存管理中扮演着核心角色。