高级程序设计语言 — Project 3 — Linux 5.17.9 源码分析之内存管理 — Page
Project 3 — Linux 5.17.9 源码分析之内存管理 — Page
Linux中内存分为3个级别,从下到上依次为:
1、Page: 一个page的大小为 4k, Page 是内存的一个最基本的单位。
2、Zone: Zone中提供了多个队列来管理page。
Zone分为3种
2.1、 ZONE_DMA: 用来存放DMA读取IO设备的数据,内核专用
2.2、 ZONE_NORMAL:用来存放内核的相关数据,内核专用
2.3、 ZONE_HIGHMEM:高端内存,用来用户进程存放数据
3、Node 节点,一个CPU对应着一个Node,一个Node包括一个Zone_DMA、 ZONE_NORMAL、ZONE_HIGHMEM。
同时当一个CPU对应的内存用光后,可以申请其他CPU对应的内存。
深入分析page
page(页)是linux内核管理物理内存的最小单位,内核将整个物理内存按照页对齐方式划分成千上万个页进行管理,内核为了管理这些页将每个页抽象成struct page结构管理每个页状态及其他属性,针对一个4GB内存,那么将会存在上百万个struct page结构。而struct page结构本身就占有一定内存,如果struct page结构设计过大,那么本身就会占用较多内存,而给系统或者用户可用的内存就较少,所以对strcut page结构大小非常敏感,即使增加一个字节 对系统影响也会非常大,故对struct page的结构做了严格设计,不会轻易增加字段:
page flags
page flags标志位主要采用bit 位方式,来描述一个物理页的状态信息。
一般情况下:
其中从0到63位最高位依次位FLAGS位(真正的页状态标志位)、中间剩余保留,以及ZONE和NODE部分,其中ZONE代表着该Page归属于的Zone区域,而NODE在NUMA系统中代表着该page所属于的node 节点id,如果是非NUMA系统则为0。中间剩余的部分为保留位。
unsigned long flags; /* Atomic flags, some possibly
* updated asynchronously *
/* Page flags: | [SECTION] | [NODE] | ZONE | [LAST_CPUPID] | ... | FLAGS | */过
通过位运算可以快速查看、更改page的状态。
linux对于此部分操作过多不一一展示,此处给出一个例子:
#define CLEARPAGEFLAG(uname, lname, policy) \
static __always_inline \
void folio_clear_##lname(struct folio *folio) \
{ clear_bit(PG_##lname, folio_flags(folio, FOLIO_##policy)); } \
static __always_inline void ClearPage##uname(struct page *page) \
{ clear_bit(PG_##lname, &policy(page, 1)->flags); }
PAGEFLAG(LRU, lru, PF_HEAD) __CLEARPAGEFLAG(LRU, lru, PF_HEAD)
uname和lname是FLAGS位后的小写部分
第一个Union
是记录page的主要功能数据,每个部分都有个结构体进行说明
Page cache and anonymous pages
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.
*/
struct list_head lru;// 为LRU链表,该链表会根据页面不同的用途挂载到不同的链表
/* 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;
};
page_pool used by netstack
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;
};
};
如果该页被用作DMA映射,dma_addr则代表的是映射的一个总线地址。
pp_magic去避免回收非 page_pool 分配的页面。
5.14版本对此处进行了一定的更新,这里先占个坑,以后再补。
Compound page
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;
unsigned int compound_nr; /* 1 << compound_order */
};
struct { /* Second tail page of compound page */
unsigned long _compound_pad_1; /* compound_head */
atomic_t hpage_pinned_refcount;
/* For both global and memcg */
struct list_head deferred_list;
};
compound page将多个连续的物理页组装联合在一起组成一个更大页,其最大的用途是可以创建一个huge 页。具体内容这里就不再展开。
Page table pages
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 pgd表*/
atomic_t pt_frag_refcount; /* powerpc pt(page table)的引用计数*/
};
#if ALLOC_SPLIT_PTLOCKS
spinlock_t *ptl;
#else
spinlock_t ptl;
#endif
};
该结构主要用于page table,page table 将一个线性地址(即虚拟地址)按照多级划分的划分,减少转换关系数组占用的物理空间。同时在多级划分过程中,内核为了兼容不同的芯片架构,将整个分级划分进行了抽象处理可以根据不同的架构进行配置,32位系统下支持三或者二级查找,64位系统下支持四级或者五级查找。
ZONE_DEVICE pages
页面属于 ZONE_DEVICE pages 的属性。
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
rcu_head主要被用作RCU锁
struct rcu_head rcu_head;
第二个union
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;
};
注释解析十分详细这里就不再过多解释。
探讨
内核有三种 slab allocator:SLAB(这是最早期的分配器,经常用在 Android 中),SLUB(经常被用于桌面环境和数据中心的系统中),以及 SLOB(一个用于嵌入式系统的更小型的分配器实现)
我们发现从5.17版之后 slab 从struct page 中移了出来,这里我们做下分析。
Slab
#if defined(CONFIG_SLAB)
union {
struct list_head slab_list;//指向的是slab list链表
struct rcu_head rcu_head;
};
struct kmem_cache *slab_cache;//指向的是slab缓存描述符
void *freelist; /* array of free object indexes */
void *s_mem; /* first object 指向第一个slab对象的起始地址*/
unsigned int active;// 应该是一个描述当前是否活跃的类似flag的标志
Slub
#elif defined(CONFIG_SLUB)
union {
struct list_head slab_list;
struct rcu_head rcu_head;
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct {
struct slab *next;//在slub中分配使用
int slabs; /* Nr of slabs left */
};
#endif
};
struct kmem_cache *slab_cache;
/* Double-word boundary */
void *freelist; /* first free object */
union {
unsigned long counters;// 被slub用作计数
struct {
unsigned inuse:16;
unsigned objects:15;
unsigned frozen:1;
};
};
unsigned int __unused;
Slob
#elif defined(CONFIG_SLOB)
struct list_head slab_list;
void *__unused_1;
void *freelist; /* first free block */
long units;
unsigned int __unused_2;
从实际内存的大小来看将此部分拆分并没有使得内存方面进行过多优化,此结构仍与struct page 有着相当强的绑定。好的地方在于让struct page这个最基本的单位更加简洁。
此外ifdef将 slab,slob,slub 进行分割管理,确保一个 slab allocator 不会去意外地使用属于另一个 slab allocator 的特有字段,从而在类型安全(type safety)方面带来更多好处,同时sturct slab 可以被更加灵活的使用。
下面引自[Struct slab comes to 5.17 [LWN.net]]
新改过的这个结构定义在了 mm/slab.h 中,它不在 include 之下,
因此它不能被内存管理子系统之外的代码所用。这给 x86 bootmem allocator 以及 zsmalloc() 带来了麻烦,
因为它们都在使用 page struct 中的 slab 相关的一些字段,尽管这些代码并不是一个 slab allocator。
这部分代码已经被改为使用 struct page 中的其他字段,而且还添加了注释,提醒说这种用法应该在今后的某一天被清理掉。
同时,slab allocator 中的代码也被修改为使用新的结构,在调用栈的一开始就改成使用新的这个 struc
t。这就将 slab 的大部分代码与 struct page 隔离开来,为今后的工作铺平了道路,也就是未来可以完全分离这两种 struct
了,并允许根据需要来动态分配 slab structure。
最终结果是为 slab allocator 在系统的 memory map 中有了更清晰的视图,开始将它们与底层的内存管理细
节分开,并增加了 type safety (类型安全)。同时,Linux 用户应该不会受到任何影响,并且如果幸
运的话,今后的 bug 数量也会有所减少。在更远的将来,可能会有这样一个时刻:struct slab 可以被动态分配并完全从 memory map 中分
离出来。不过这种改动还需要一段时间。同时,清理内存管理的核心类型则是朝着正确方向迈出的一步。
此文主要对关于 struct page 的各部分进行了主要探讨,更好的全面的认识struct page 这一结构在5.14,5.17版本后对其的更新,关于slab的内存管理机制我们并未进行更深一步的探讨。
作者认识较浅,如有勘误欢迎指正。

浙公网安备 33010602011771号