首页 > 代码库 > linux内核探索之内存管理(二):linux系统中的内存组织--结点、内存域和页帧
linux内核探索之内存管理(二):linux系统中的内存组织--结点、内存域和页帧
本文主要参考《深入linux内核架构》(3.2节)及Linux3.18.3内核源码
概述:本文主要描述了内存管理相关的数据结构:结点pg_data_t、内存域struct zone以及页帧(物理页):struct page ,以及该结构相关的一些基本概念。
1. 概述
内存划分为接点,每个结点关联到系统中的一个处理器,在内核中表示为pg_data_t.
各个结点又划分为内存域,比如DMA内存域,高端内存域,普通内存域。
内核内存域的宏:
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, __MAX_NR_ZONES };
ZONE_DMA:标记用于DMA的内存区。该区域的长度依赖于处理器类型。在IA-32平台,一般为16MB。
ZONE_DMA32:标记了使用32位地址字可寻址、适合DMA的区域,只有在64位系统上,两种DMA内存域才有差别,在32位计算机上,这里是空的。
ZONE_NORMAL:普通内存域。所有体系结构上都保证存在。但无法保证该地址范围对应了实际的物理内存。
ZONE_HIGHMEM:高端内存区。标记超出内核段的物理内存(比如大于896M,内核地址空间无法全部映射物理内存)。64位系统不需要高端内存。
ZONE_MOVABLE:这是一个伪内存域,用于防止物理内存碎片。
各个内存域都关联了一个数组,用来组织属于该内存域的物理内存页(页帧)。对于每个页帧,都分配了一个struct page实例以及所需的管理数据。
各个内存节点保存在一个单链表中,供内核遍历。
2. 数据结构
(1)结点和节点状态
结点管理
pg_data_t表示节点,定义如下:
include/linux/mmzone.h typedef struct pglist_data { struct zone node_zones[MAX_NR_ZONES]; struct zonelist node_zonelists[MAX_ZONELISTS]; int nr_zones; struct page *node_mem_map; struct page_cgroup *node_page_cgroup; struct bootmem_data *bdata; spinlock_t node_size_lock; 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; struct task_struct *kswapd; /* Protected by mem_hotplug_begin/end() */ int kswapd_max_order; enum zone_type classzone_idx; spinlock_t numabalancing_migrate_lock; unsigned long numabalancing_migrate_next_window; unsigned long numabalancing_migrate_nr_pages; } pg_data_t;
node_zones是一个数组,包含了节点中各内存域的数据结构
node_zonelists指定了备用结点及其内存区的列表,以便在当前结点没有可用空间时,在备用结点分配内存。
nr_zones保存结点中不同内存区的数目
node_mem_map是指向page实例数组的指针,用于描述结点的所有物理内存页,它包含了结点中所有内存区的页。
bdata指向自举内存分配器数据结构的实例。在系统启动时,内存管理子系统初始化之前,内核也需要使用内存,此时使用了自举内存分配器。
node_start_pfn是该NUMA结点第一个页帧的逻辑编号。系统中所有结点的页帧是依次编号的,每个页帧的号码都是全局(不止本结点)唯一的。在UMA系统中,该值总是0.
node_present_pages指定了结点中页帧的数目
node_spanned_pages则给出了该结点以页帧为单位计算的长度,包含空洞。
node_id是一个全局结点ID
pgdat_next连接到下一个内存结点,系统中所有的结点都通过单链表连接,其末尾通过空指针标记。
kswapd_wait是交换守护进程的等待队列,在将页帧换出结点时会用到。Kswapd指向负责该结点的交换守护进程的task_struct。Kswapd_max_order用于页交换子系统的实现,用来定义需要释放的区域的长度。
结点状态管理
include/linux/nodemask.h: /* * Bitmasks that are kept for all the nodes. */ enum node_states { N_POSSIBLE, /* The node could become online at some point */ N_ONLINE, /* The node is online */ N_NORMAL_MEMORY, /* The node has regular memory */ #ifdef CONFIG_HIGHMEM N_HIGH_MEMORY, /* The node has regular or high memory */ #else N_HIGH_MEMORY = N_NORMAL_MEMORY, #endif #ifdef CONFIG_MOVABLE_NODE N_MEMORY, /* The node has memory(regular, high, movable) */ #else N_MEMORY = N_HIGH_MEMORY, #endif N_CPU, /* The node has one or more cpus */ NR_NODE_STATES };
状态N_POSSIBLE,N_ONLINE和N_CPU用于CPU和内存的热插拔。对于内存管理有必要的标志是N_HIGH_MEMORY、N_NORMAL_MEMORY。如果结点有普通或高端内存则使用N_HIGH_MEMORY,仅当结点没有高端内存时才设置N_NORMAL_MEMROY.
static inline void node_set_state(int node, enum node_states state);用于设置位于特定结点中的一个比特位。
static inline void node_clear_state(int node, enum node_states state);用于设置位于特定结点中的一个比特位。
#define for_each_node_state(__node, __state) for_each_node_mask((__node), node_states[__state]) 该宏用于遍历出于特定状态的所有结点
#define for_each_online_node(node) for_each_node_state(node, N_ONLINE) 该宏用于遍历所有活动结点。
如果内核只支持单个结点,上述操作为空操作。
2. 内存域
内核使用struct zone来表述内存域,其定义如下:
enum zone_watermarks { WMARK_MIN, WMARK_LOW, WMARK_HIGH, NR_WMARK };
#define min_wmark_pages(z) (z->watermark[WMARK_MIN])
#define low_wmark_pages(z) (z->watermark[WMARK_LOW]) #define high_wmark_pages(z) (z->watermark[WMARK_HIGH])
struct zone { unsigned long watermark[NR_WMARK]; long lowmem_reserve[MAX_NR_ZONES]; int node; unsigned int inactive_ratio; struct pglist_data *zone_pgdat; struct per_cpu_pageset __percpu *pageset; unsigned long dirty_balance_reserve; unsigned long *pageblock_flags; unsigned long min_unmapped_pages; unsigned long min_slab_pages; unsigned long zone_start_pfn; unsigned long managed_pages; unsigned long spanned_pages; unsigned long present_pages; const char *name; int nr_migrate_reserve_block; unsigned long nr_isolate_pageblock; seqlock_t span_seqlock; wait_queue_head_t *wait_table; unsigned long wait_table_hash_nr_entries; unsigned long wait_table_bits; ZONE_PADDING(_pad1_) spinlock_t lock; struct free_area free_area[MAX_ORDER]; unsigned long flags; ZONE_PADDING(_pad2_) spinlock_t lru_lock; struct lruvec lruvec; atomic_long_t inactive_age; unsigned long percpu_drift_mark; unsigned long compact_cached_free_pfn; unsigned long compact_cached_migrate_pfn[2]; unsigned int compact_considered; unsigned int compact_defer_shift; int compact_order_failed; bool compact_blockskip_flush; ZONE_PADDING(_pad3_) atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS]; } ____cacheline_internodealigned_in_smp;
该结构由ZONE_PENDDING分隔为几个部分,这是因为对zone结构的访问非常频繁。在多处理器系统上,通常会有不同的CPU试图访问结构成员。因此使用锁防止他们彼此干扰,避免错误和不一致。由于内核对该结构的访问非常频繁,因此会经常性的获取该结构的两个自旋锁zone->lock和zone->lru_lock。
如果数据保存在CPU高速缓存中,那么会处理的更快。高速缓存分为行,每一行负责不同的内存区。内核使用ZONE_PADDING宏生成填充字段添加到结构中,以确保每个自旋锁都出于自身的缓存行中。关键字 ____cacheline_internodealigned_in_smp,用来实现最优的高速缓存对齐方式。
该结构的最后两个部分也通过填充字段彼此分隔,主要目的是将数据保留在一个缓存行中,便于快速访问。
第一部分主要成员:
watermark[NR_WMARK]是页换出时使用的水印。如果内存不足,内核可以将页写到硬盘,这三个成员会影响到交换守护进程的行为。
如果空闲页多于watermark[WMARK_HIGH],则内存域的状态是理想的。
如果空闲页的数目低于watermark[WMARK_LOW],则内核开始将页换出到硬盘
如果空闲页的数目低于watermark[WMARK_MIN],那么页回收工作的压力就会比较大,因为内存域中急需空闲页。
lowmem_reserve[MAX_NR_ZONES]分别为各种内存域指定了若干页,用于一些无论如何都不能失败的关键性内存分配。
Pageset是一个数组,用于实现每个CPU的冷/热页帧列表。内核使用这些列表来保存可用于满足实现的“新鲜”页。但冷热页帧对应的高速缓存状态不同:有些页帧很可能仍然在高速缓存中,因此可以快速访问,这些成为热页帧;为缓存的页帧成为冷页帧。
free_area[MAX_ORDER]是同名数据结构的数组,用于实现伙伴系统。每个数组元素(#define MAX_ORDER 11)都表示某种固定长度的一些连续内存区域。对于包含在每个区域中的空闲内存页的管理,free_area是一个起点。
第二部分主要成员:
第二部涉及的成员,用来根据活动情况对内存域中的使用的页进行编目。如果也访问频繁,则内核认为它是活动的。在需要换出页时,这种区别是很重要的:如果可能的话,频繁使用的页应该保持不动,而多于的页则可以换出。
Flasgs描述内存域的当前状态,允许使用如下标志:
/* zone flags, see below */
unsigned long flags;
enum zone_flags { ZONE_RECLAIM_LOCKED, //防止并发回收 ZONE_OOM_LOCKED, // zone is in OOM killer zonelist,内存域即刻被回收 ZONE_CONGESTED, // zone has many dirty pages backed by a congested BDI ZONE_DIRTY, //reclaim scanning has recently found many dirty file pages at the tail of the LRU. ZONE_WRITEBACK, //reclaim scanning has recently found many pages under writeback ZONE_FAIR_DEPLETED,//fair zone policy batch depleted };
vm_stat[NR_VM_ZONE_STAT_ITEMS]维护了大量有关该内存区的统计信息。
wait_queue_head_t *wait_table;
unsigned long wait_table_hash_nr_entries;
unsigned long wait_table_bits;
这三个成员实现一个等待队列,可供等待某一页变为可用的进程使用。进程排成一个队列,等待某些条件,该条件为真时,内核会通知进程恢复工作。
struct pglist_data *zone_pgdat;内存域和父节点之间的关联由zone_pgdat建立,zone_pgdat指向对应的pg_list_data实例。
unsigned long zone_start_pfn;是内存域第一个页帧的索引。zone_start_pfn == zone_start_paddr >> PAGE_SHIFT。
const char *name;是一个字符串,保存该内存域惯用名称。目前3个选项:Normal、DMA和HighMem.
unsigned long spanned_pages;指定内存域中页的总数,但并非所有页都是可用的,可能存在内存空洞。
unsigned long present_pages;是实际可用的页数目,一般与spanned_pages相同。
3. 内存域水印的计算
在计算各种水印之前,内核首先需要确定需要为关键性分配保留的内存空间的大小值。该值随可用内存的大小而非线性增长,并保存在全局变量min_free_kbytes中。
用户层可以通过文件/proc/sys/vm/min_free_kbytes来读取和修改关键性分配内存空间最小值。如下是主内存域min_fre_kbytes的一个经验值:
主内存大小 | Min_free_kbytes |
16MB | 512KB |
32MB | 724KB |
256MB | 2MB |
512MB | 2896KB |
1024MB | 4MB |
2048MB | 5792KB |
4096MB | 8MB |
结构体中水印值的填充由init_per_zone_pages_min处理,该函数由内核在启动期间调用,无需显式调用。
__setup_per_zone_wmark设置struct zone的watermark[WMARK_MIN]、watermark[WMARK_LOW]、watermark[WMARK_HIGH]
static void __setup_per_zone_wmarks(void)//page_alloc.c { unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10); unsigned long lowmem_pages = 0; struct zone *zone; unsigned long flags; /* Calculate total number of !ZONE_HIGHMEM pages */ for_each_zone(zone) { if (!is_highmem(zone)) lowmem_pages += zone->managed_pages; } for_each_zone(zone) { u64 tmp; spin_lock_irqsave(&zone->lock, flags); tmp = (u64)pages_min * zone->managed_pages; do_div(tmp, lowmem_pages); if (is_highmem(zone)) { unsigned long min_pages; min_pages = zone->managed_pages / 1024; min_pages = clamp(min_pages, SWAP_CLUSTER_MAX, 128UL); zone->watermark[WMARK_MIN] = min_pages; } else { zone->watermark[WMARK_MIN] = tmp; } zone->watermark[WMARK_LOW] = min_wmark_pages(zone) + (tmp >> 2); zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + (tmp >> 1); __mod_zone_page_state(zone, NR_ALLOC_BATCH, high_wmark_pages(zone) - low_wmark_pages(zone) - atomic_long_read(&zone->vm_stat[NR_ALLOC_BATCH])); setup_zone_migrate_reserve(zone); spin_unlock_irqrestore(&zone->lock, flags); } /* update totalreserve_pages */ calculate_totalreserve_pages(); }
高端内存域的下界SWAP_CLUSTER_MAX,对整个页面回收子系统来说,是一个重要的数值。该子系统的代码经常对页进行分组式批处理操作,SWAP_CLUSTER_MAX定义了分组的大小。
lowmem_reserve[MAX_NR_ZONES]的计算由setup_per_zone_lowmem_reserve();完成。内核遍历系统的所有结点,对每个结点的各个内存域分别计算预留内存最小值,具体的算法是将内存域中页帧的总数除以sysctl_lowmem_reserve_ratio[idx]。除数的默认设置对低端内存域是256,对高端内存域是32。
/* * setup_per_zone_lowmem_reserve - called whenever * sysctl_lower_zone_reserve_ratio changes. Ensures that each zone * has a correct pages reserved value, so an adequate number of * pages are left in the zone after a successful __alloc_pages(). */ static void setup_per_zone_lowmem_reserve(void)//page_alloc.c { struct pglist_data *pgdat; enum zone_type j, idx; for_each_online_pgdat(pgdat) { for (j = 0; j < MAX_NR_ZONES; j++) { struct zone *zone = pgdat->node_zones + j; unsigned long managed_pages = zone->managed_pages; zone->lowmem_reserve[j] = 0; idx = j; while (idx) { struct zone *lower_zone; idx--; if (sysctl_lowmem_reserve_ratio[idx] < 1) sysctl_lowmem_reserve_ratio[idx] = 1; lower_zone = pgdat->node_zones + idx; lower_zone->lowmem_reserve[j] = managed_pages / sysctl_lowmem_reserve_ratio[idx]; managed_pages += lower_zone->managed_pages; } } } /* update totalreserve_pages */ calculate_totalreserve_pages(); }
4. 冷热页
Struct zone的pageset成员用于实现冷热分配器(hot-n-cold allocator)。页时热的,指页已经加载到CPU高速缓存,与在内存中的也相比,其数据能够更快地访问。页是冷的,指页不在高速缓存中。在多处理器系统上每个CPU都有一个或多个高速缓存,各个CPU的管理必须是独立的。
尽管内存域可能属于一个特定的NUMA结点,因而关联到某个特定的CPU。但是其他CPU的高速缓存仍然可能包含该内存域中的页。每个处理器都可以访问系统中所有的页,尽管速度不同。因此,特定于内存域的数据结构不仅要考虑到所属NUMA结点的CPU,还必须考虑到系统中其他的CPU。
老的内核中pageset是一个数组,在最新的3.18.3内核中是指针:struct per_cpu_pageset __percpu *pageset。但是无论数组还是指针,在单处理器系统上都是只有一个元素,而SMP系统编译的内核中,其值可能在2~32中之间,该值并不是系统中实际存在的CPU数目,而是内内核支持的CPU的最大数目。
数组元素的类型为
include/linux/mmzone.h:
struct per_cpu_pageset {
struct per_cpu_pages pcp;
#ifdef CONFIG_NUMA
s8 expire;
#endif
#ifdef CONFIG_SMP
s8 stat_threshold;
s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS];
#endif
};
该结构主要由struct per_cpu_pages结构体构成:
struct per_cpu_pages {
int count; /* number of pages in the list */
int high; /* high watermark, emptying needed */
int batch; /* chunk size for buddy add/remove */
/* Lists of pages, one per migrate type stored on the pcp-lists */
struct list_head lists[MIGRATE_PCPTYPES];
};
Count记录了与该列表相关的页的数目,high是一个水印。如果count值超出了high,则表明列表中的页太多了。对容量过低的状态没有显式使用水印:如果列表中没有成员,则冲洗填充。
List是一个双向链表,保存了当前CPU的冷页或热页,可以使用内核的标准方法处理。
CPU的高速缓存不是用单个页来填充的,而是用多个页组成的块,batch是每次添加页数的一个参考值。
5. 页帧
页帧代表系统内存的最小单位,对内存中的每个页都会创建struct page的一个实例。所以struct page需要保持尽可能小。因为系统内存会分解为大量的页:即使主内存为384MB,一个page为4KB大小的话,也大概有10000个页。这就是为什么尽力保持struct page尽可能小的原因。在典型应用中,页的数量巨大,对page结构的小改动,也可能导致保存所有page实例所需的物理内存暴涨。
页的广泛使用,增加了保持结构长度的难度。内存管理的许多部分使用页,用于各种不同的用途。内核的一个部分可能完全依赖于struct page提供的信息,而该信息对内核的另一部分可能完全无用。所以struct page中使用了union类型。
struct page结构的定义:
Include/linux/mm_types.h /* * Each physical page in the system has a struct page associated with * it to keep track of whatever it is we are using the page for at the * moment. Note that we have no way to track which tasks are using * a page, though if it is a pagecache page, rmap structures can tell us * who is mapping it. * * The objects in struct page are organized in double word blocks in * order to allows us to use atomic double word operations on portions * of struct page. That is currently only used by slub but the arrangement * allows the use of atomic double word operations on the flags/mapping * and lru list pointers also. */ struct page { /* First double word block */ unsigned long flags; //原子标记,有些情况下会异步更新 union { struct address_space *mapping; //如果最低位为0,则指向inode address_space,或为NULL //如果页映射为匿名内存,最低位置位,而且该指针指向anon_vma对象, //则参考PAGE_MAPPING_ANON. void *s_mem; /* slab first object */ }; /* Second double word */ struct { union { pgoff_t index; //在映射内的偏移量 void *freelist; //SLUB:freelist req. Slab lock bool pfmemalloc; }; union { #if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE) /* Used for cmpxchg_double in slub */ unsigned long counters; #else unsigned counters; #endif struct { union { atomic_t _mapcount;// 内存管理子系统中映射的页表项计数,用于表示 //页是否已经映射,还用于限制逆向映射搜索 struct { /* SLUB */ //用于slab分配器 unsigned inuse:16; //对象的数目 unsigned objects:15; unsigned frozen:1; }; int units; /* SLOB */ }; atomic_t _count; //使用计数 }; unsigned int active; /* SLAB */ }; }; /* Third double word block */ union { struct list_head lru; //换出页列表,例如由zone->lru_lock保护的active_list struct { struct page *next; #ifdef CONFIG_64BIT int pages; /* Nr of partial slabs left */ int pobjects; /* Approximate # of objects */ #else short int pages; short int pobjects; #endif }; struct slab *slab_page; /* slab fields */ struct rcu_head rcu_head; /* Used by SLAB * when destroying via RCU */ #if defined(CONFIG_TRANSPARENT_HUGEPAGE) && USE_SPLIT_PMD_PTLOCKS pgtable_t pmd_huge_pte; /* protected by page->ptl */ #endif }; /* Remainder is not double word aligned */ union { unsigned long private; //由映射私有,不透明数据: //如果设置了PagePrivate,通常用于buffer_heads; //如果设置了PageSwapCache,则用于swp_entry_t; //如果设置了PG_buddy,则用于表示伙伴系统中的阶 #if USE_SPLIT_PTE_PTLOCKS #if ALLOC_SPLIT_PTLOCKS spinlock_t *ptl; #else spinlock_t ptl; #endif #endif struct kmem_cache *slab_cache; //用于slub分配器,指向slab的指针 struct page *first_page; //用于复合页的页尾,指向首页 }; #if defined(WANT_PAGE_VIRTUAL) void *virtual; //内核虚拟地址(如果没有映射则为NULL,即高端内存) #endif /* WANT_PAGE_VIRTUAL */ #ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS unsigned long debug_flags; /* Use atomic bitops on this */ #endif #ifdef CONFIG_KMEMCHECK void *shadow; #endif #ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS int _last_cpupid; #endif }
这个结构体的定义是非常复杂的,原因就是内核中每一个物理页帧都要对应这么一个结构,所以需要很多个这样的结构,所以这里需要保持该结构很小。而该结构用处又非常多,用处多导致每个地方有不同的需求,导致成员多。所以就出来这么纠结的一个定义。这里只保留非常重要的部分。
该结构的格式是体系结构无关的,不依赖于CPU类型,每个页帧都由该结构描述。出了slub相关成员以外(slab、freelist和inuse),page结构也包含了若干其他成员。这里只是概述一些内容,后面还会有介绍。
flags:存储了体系结构无关的标志,用于描述页的属性
_count:引用计数,表示内核中引用该页的次数。在其值为0时,内核就知道page实例当前未使用,因此可以删除。如果其值大于0,该实例决不会从内存删除。
_mapcount表示在页表中有多少项指向该页
lru:是一个表头,用于在各种链表上维护该页,以便将页按不同类别分组,以便将页按照不同类别分组,最重要的是活动页和不活动页
内核将多个毗连的页合并为较大的复合页(compound page)。分组中的第一个页称为首页(head page),而所有其余页叫做尾页(tail page)。所有尾页对应的page实例中,都将first_pag设为指向首页。
Mapping指定了页帧所在的地址空间。Index是页帧在映射内部的偏移量。地址空间是一个非常一般的概念。例如可以用在向向内存读取文件时,地址空间用于将文件的内容与装载数据的内存区关联起来。Mapping不仅能够保存一个指针,而且还能包含一些额外的信息,用于判断页是否属于未关联到地址空间的某个匿名内存区。
Private是一个指向“私有”数据的指针,虚拟内存管理会忽略该数据。根据页的用途,可以用不同的方式使用该指针。大多数情况下它将用于页与数据缓冲区关联起来。
Virtual用于高端内存区域中的页,无法直接映射到内核内存中的页,virtual用于存储该页的虚拟地址。
页的不同属性通过一系列页标志描述,存储为struct page的flags成员中的各个比特位。这些标志独立于使用的体系结构。各个标志是由page-types.h中的宏定义的,此外还有一些宏用于标志的设置、删除、查询。
/* * Various page->flags bits: * * PG_reserved is set for special pages, which can never be swapped out. Some * of them might not even exist (eg empty_bad_page)... * * The PG_private bitflag is set on pagecache pages if they contain filesystem * specific data (which is normally at page->private). It can be used by * private allocations for its own usage. * * During initiation of disk I/O, PG_locked is set. This bit is set before I/O * and cleared when writeback _starts_ or when read _completes_. PG_writeback * is set before writeback starts and cleared when it finishes. * * PG_locked also pins a page in pagecache, and blocks truncation of the file * while it is held. * * page_waitqueue(page) is a wait queue of all tasks waiting for the page * to become unlocked. * * PG_uptodate tells whether the page's contents is valid. When a read * completes, the page becomes uptodate, unless a disk I/O error happened. * * PG_referenced, PG_reclaim are used for page reclaim for anonymous and * file-backed pagecache (see mm/vmscan.c). * * PG_error is set to indicate that an I/O error occurred on this page. * * PG_arch_1 is an architecture specific page state bit. The generic code * guarantees that this bit is cleared for a page when it first is entered into * the page cache. * * PG_highmem pages are not permanently mapped into the kernel virtual address * space, they need to be kmapped separately for doing IO on the pages. The * struct page (these bits with information) are always mapped into kernel * address space... * * PG_hwpoison indicates that a page got corrupted in hardware and contains * data with incorrect ECC bits that triggered a machine check. Accessing is * not safe since it may cause another machine check. Don't touch! */ /* * Don't use the *_dontuse flags. Use the macros. Otherwise you'll break * locked- and dirty-page accounting. * * The page flags field is split into two parts, the main flags area * which extends from the low bits upwards, and the fields area which * extends from the high bits downwards. * * | FIELD | ... | FLAGS | * N-1 ^ 0 * (NR_PAGEFLAGS) * * The fields area is reserved for fields mapping zone, node (for NUMA) and * SPARSEMEM section (for variants of SPARSEMEM that require section ids like * SPARSEMEM_EXTREME with !SPARSEMEM_VMEMMAP). */ enum pageflags { PG_locked, /* Page is locked. Don't touch. */ PG_error, PG_referenced, PG_uptodate, PG_dirty, PG_lru, PG_active, PG_slab, PG_owner_priv_1, /* Owner use. If pagecache, fs may use*/ PG_arch_1, PG_reserved, PG_private, /* If pagecache, has fs-private data */ PG_private_2, /* If pagecache, has fs aux data */ PG_writeback, /* Page is under writeback */ #ifdef CONFIG_PAGEFLAGS_EXTENDED PG_head, /* A head page */ PG_tail, /* A tail page */ #else PG_compound, /* A compound page */ #endif PG_swapcache, /* Swap page: swp_entry_t in private */ PG_mappedtodisk, /* Has blocks allocated on-disk */ PG_reclaim, /* To be reclaimed asap */ PG_swapbacked, /* Page is backed by RAM/swap */ PG_unevictable, /* Page is "unevictable" */ #ifdef CONFIG_MMU PG_mlocked, /* Page is vma mlocked */ #endif #ifdef CONFIG_ARCH_USES_PG_UNCACHED PG_uncached, /* Page has been mapped as uncached */ #endif #ifdef CONFIG_MEMORY_FAILURE PG_hwpoison, /* hardware poisoned page. Don't touch */ #endif #ifdef CONFIG_TRANSPARENT_HUGEPAGE PG_compound_lock, #endif __NR_PAGEFLAGS, /* Filesystems */ PG_checked = PG_owner_priv_1, /* Two page bits are conscripted by FS-Cache to maintain local caching * state. These bits are set on pages belonging to the netfs's inodes * when those inodes are being locally cached. */ PG_fscache = PG_private_2, /* page backed by cache */ /* XEN */ PG_pinned = PG_owner_priv_1, PG_savepinned = PG_dirty, /* SLOB */ PG_slob_free = PG_private, };
内核定义了一些标准宏,用于检查页是否设置了某个特定的比特位,或者操作某个比特位。这些宏的名称有一定的模式,如下所述:
PageXXX(page)会检查页是否设置了PG_XXX位,比如PageDirty检查PG_dirty位,而PageActive检查PG_active位等等。
SetPageXXX在某个比特位没有设置的情况下,设置该Bit
ClearPageXXX无条件清除某个谁知的Bit
这些操作的试下是原子的。很多情况下,需要等待页的状态改变,然后才能恢复工作。内核提供了两个辅助函数等待状态的改变:
/* * Wait for a page to be unlocked. * * This must be called with the caller "holding" the page, * ie with increased "page->count" so that the page won't * go away during the wait.. */ static inline void wait_on_page_locked(struct page *page) { if (PageLocked(page)) wait_on_page_bit(page, PG_locked); } /* * Wait for a page to complete writeback */ static inline void wait_on_page_writeback(struct page *page) { if (PageWriteback(page)) wait_on_page_bit(page, PG_writeback); }
假定内核的一部分在等待一个被锁定的页面,直至页面解锁。wait_on_page_locked提供了该功能。在页面锁定的情况下调用该函数,内核将进入睡眠,在页面解锁之后,睡眠进程被自动唤醒并继续工作。
wait_on_page_writeback会等待到与页面相关的所有待决回写操作结束,将页面包含的数据同步到块设备为止。
总结:本文主要描述了内存管理相关的数据结构:结点pg_data_t、内存域struct zone以及页帧(物理页):struct page ,以及该结构相关的一些基本概念。
linux内核探索之内存管理(二):linux系统中的内存组织--结点、内存域和页帧