linux内核 内存页,linux内核探索之内存管理(2):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实例以及所需的管理数据。

各个内存节点保存在一个单链表中,供内核遍历。

102400742.png

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处理,该函数由内核在启动期间调用,无需显式调用。

102400743.png

__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,以及该结构相关的一些基本概念。

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值