更新了内存管理之内存描述(四)--页帧struct page详解...

This commit is contained in:
gatieme
2016-08-05 20:51:45 +08:00
parent 94ddf93346
commit 43ccb8447a
6 changed files with 212 additions and 256 deletions
@@ -5,39 +5,27 @@
| ------- |:-------:|:-------:|:-------:|:-------:|:-------:|
| 2016-06-14 | [Linux-4.7](http://lxr.free-electrons.com/source/?v=4.7) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux内存管理](http://blog.csdn.net/gatieme/article/category/6225543) |
http://www.cnblogs.com/plinx/archive/2013/01/15/2860520.html
http://blog.chinaunix.net/uid-30282771-id-5176971.html
#好的收藏
-------
##参照
-------
>参照
>
>[内存管理(一)内存模型之Node](http://biancheng.dnbcw.info/linux/387391.html)
>
> [Linux 内存管理 重要结构体](http://blog.chinaunix.net/uid-26009500-id-3078986.html)
>
>[Bootmem机制](http://blog.csdn.net/samssm/article/details/25064897)
>
>[Linux-2.6.32 NUMA架构之内存和调度](http://www.cnblogs.com/zhenjing/archive/2012/03/21/linux_numa.html)
>
>[Linux 用户空间与内核空间——高端内存详解](http://blog.csdn.net/tommy_wxie/article/details/17122923)
>
>[探索 Linux 内存模型](http://www.ibm.com/developerworks/cn/linux/l-memmod/)
>
>[Linux内存管理](http://blog.chinaunix.net/uid/21718047/cid-151509-list-2.html)
>
>[内存管理-之内核内存管理-基于linux3.10](http://blog.csdn.net/shichaog/article/details/45509917)
>
>[内存管理(一)](http://www.cnblogs.com/openix/p/3334026.html)
>
>[Linux内存管理原理](http://www.cnblogs.com/zhaoyl/p/3695517.html)
>
>[第 15 章 内存映射和 DMA](http://www.embeddedlinux.org.cn/ldd3/ch15.html)
| 链接 |
|:-------:|
| [内存管理(一)内存模型之Node](http://biancheng.dnbcw.info/linux/387391.html) |
| [Linux 内存管理 重要结构体](http://blog.chinaunix.net/uid-26009500-id-3078986.html) |
| [Bootmem机制](http://blog.csdn.net/samssm/article/details/25064897) |
| [Linux-2.6.32 NUMA架构之内存和调度](http://www.cnblogs.com/zhenjing/archive/2012/03/21/linux_numa.html) |
| [Linux 用户空间与内核空间——高端内存详解](http://blog.csdn.net/tommy_wxie/article/details/17122923) |
| [探索 Linux 内存模型](http://www.ibm.com/developerworks/cn/linux/l-memmod/) |
| [Linux内存管理](http://blog.chinaunix.net/uid/21718047/cid-151509-list-2.html) |
| [内存管理-之内核内存管理-基于linux3.10](http://blog.csdn.net/shichaog/article/details/45509917) |
| [内存管理(一)](http://www.cnblogs.com/openix/p/3334026.html) |
| [Linux内存管理原理](http://www.cnblogs.com/zhaoyl/p/3695517.html) |
| [第 15 章 内存映射和 DMA](http://www.embeddedlinux.org.cn/ldd3/ch15.html) |
| [ 内存管理(二)struct page ](http://blog.chinaunix.net/uid-30282771-id-5176971.html) |
#1 前景回顾
@@ -187,214 +175,3 @@ x86体系结构中,page的大小为4096个字节。
-------
由于能够被Linux内核直接访问的ZONE_NORMAL区域的内存空间也是有限的,所以LINUX提出了高端内存(High memory)的概念,并且允许对高端内存的访问
#内存节点node
-------
>参照
>
>[内存管理(一)内存模型之Node](http://biancheng.dnbcw.info/linux/387391.html)
>
> [Linux 内存管理 重要结构体](http://blog.chinaunix.net/uid-26009500-id-3078986.html)
>
>[Bootmem机制](http://blog.csdn.net/samssm/article/details/25064897)
>
>[Linux-2.6.32 NUMA架构之内存和调度](http://www.cnblogs.com/zhenjing/archive/2012/03/21/linux_numa.html)
表示node的数据结构为[`typedef struct pglist_data pg_data_t`](http://lxr.free-electrons.com/source/include/linux/mmzone.h#L630) 这个结构定义在[include/linux/mmzone.h, line 615](http://lxr.free-electrons.com/source/include/linux/mmzone.h#L615)中,结构体的内容如下
```c
```c
/*
* The pg_data_t structure is used in machines with CONFIG_DISCONTIGMEM
* (mostly NUMA machines?) to denote a higher-level memory zone than the
* zone denotes.
*
* On NUMA machines, each NUMA node would have a pg_data_t to describe
* it's memory layout.
*
* Memory statistics and page replacement data structures are maintained on a
* per-zone basis.
*/
struct bootmem_data;
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[MAX_ZONELISTS];
int nr_zones;
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
struct page *node_mem_map;
#ifdef CONFIG_PAGE_EXTENSION
struct page_ext *node_page_ext;
#endif
#endif
#ifndef CONFIG_NO_BOOTMEM
struct bootmem_data *bdata;
#endif
#ifdef CONFIG_MEMORY_HOTPLUG
/*
* Must be held any time you expect node_start_pfn, node_present_pages
* or node_spanned_pages stay constant. Holding this will also
* guarantee that any pfn_valid() stays that way.
*
* pgdat_resize_lock() and pgdat_resize_unlock() are provided to
* manipulate node_size_lock without checking for CONFIG_MEMORY_HOTPLUG.
*
* 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;
struct task_struct *kswapd; /* Protected by
mem_hotplug_begin/end() */
int kswapd_max_order;
enum zone_type classzone_idx;
#ifdef CONFIG_COMPACTION
int kcompactd_max_order;
enum zone_type kcompactd_classzone_idx;
wait_queue_head_t kcompactd_wait;
struct task_struct *kcompactd;
#endif
#ifdef CONFIG_NUMA_BALANCING
/* Lock serializing the migrate rate limiting window */
spinlock_t numabalancing_migrate_lock;
/* Rate limiting time interval */
unsigned long numabalancing_migrate_next_window;
/* Number of pages migrated during the rate limiting time interval */
unsigned long numabalancing_migrate_nr_pages;
#endif
#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
spinlock_t split_queue_lock;
struct list_head split_queue;
unsigned long split_queue_len;
#endif
} pg_data_t;
```
| 字段| 描述 |
| :------- | ----: |
|node_zones | 每个Node划分为不同的zone,分别为ZONE_DMAZONE_NORMALZONE_HIGHMEM |
|node_zonelists | 这个是备用节点及其内存域的列表,当当前节点的内存不够分配时,会选取访问代价最低的内存进行分配。分配内存操作时的区域顺序,当调用free_area_init_core()时,由mm/page_alloc.c文件中的build_zonelists()函数设置 |
|nr_zones | 当前节点中不同内存域zone的数量,1到3个之间。并不是所有的node都有3个zone的,比如一个CPU簇就可能没有ZONE_DMA区域 |
| node_mem_map | node中的第一个page,它可以指向mem_map中的任何一个page,指向page实例数组的指针,用于描述该节点所拥有的的物理内存页,它包含了该页面所有的内存页,被放置在全局mem_map数组中 |
| bdata | 这个仅用于引导程序boot 的内存分配,内存在启动时,也需要使用内存,在这里内存使用了自举内存分配器,这里bdata是指向内存自举分配器的数据结构的实例 |
| node_start_pfn | pfn是page frame number的缩写。这个成员是用于表示node中的开始那个page在物理内存中的位置的。是当前NUMA节点的第一个页帧的编号,系统中所有的页帧是依次进行编号的,这个字段代表的是当前节点的页帧的起始值,对于UMA系统,只有一个节点,所以该值总是0 |
|node_present_pages | node中的真正可以使用的page数量 |
|node_spanned_pages | 该节点以页帧为单位的总长度,这个不等于前面的node_present_pages,因为这里面包含空洞内存 |
|node_id | node的NODE ID 当前节点在系统中的编号,从0开始 |
| kswapd_wait | node的等待队列,交换守护列队进程的等待列表|
| kswapd_max_order | 需要释放的区域的长度,以页阶为单位 |
| classzone_idx | 这个字段暂时没弄明白,不过其中的zone_type是对ZONE_DMA,ZONE_DMA32,ZONE_NORMAL,ZONE_HIGH,ZONE_MOVABLE,__MAX_NR_ZONES的枚举 |
在新的linux3.x~linux4.x的内核中,Linux定义了一个大小为[MAX_NUMNODES](http://lxr.free-electrons.com/source/include/linux/numa.h#L11)类型为[`pgdat_list`](http://lxr.free-electrons.com/source/arch/ia64/mm/discontig.c#L50)数组,数组的大小根据[CONFIG_NODES_SHIFT](http://lxr.free-electrons.com/source/include/linux/numa.h#L6)的配置决定。对于UMA来说,NODES_SHIFT为0,所以MAX_NUMNODES的值为1。内核提供了[for_each_online_pgdat(pgdat)](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L871)来遍历节点
而在linux-2.4.x之前的内核中所有的节点,都由一个被称为[pgdat_list](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=2.4.37#L169)的链表维护。这些节点都放在该链表中,均由函数[init_bootmem_core()](http://lxr.free-electrons.com/source/mm/bootmem.c#L96)初始化结点。内核提供了[宏for_each_pgdat(pgdat)]http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=2.4.37#L169)来遍历节点链表。
对于单一node的系统,contig_page_data 是系统唯一的node数据结构对象。查看contig_page_data的定义[linux-4.5](http://lxr.free-electrons.com/source/mm/bootmem.c#L27)[linux-2.4.37](http://lxr.free-electrons.com/source/mm/numa.c?v=2.4.37#L15)
#管理区Zone
-------
linux系统中,内存中的每个簇所对应的node又被分成的称为管理区(zone)的块,
>一个管理区(zone)由[struct zone](http://lxr.free-electrons.com/source/include/linux/mmzone.h#L326)结构体来描述(linux-3.8~目前linux4.5),而在linux-2.4.37之前的内核中是用[`typedef struct zone_struct zone_t `](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=2.4.37#L47)数据结构来描述)
zone对象用于跟踪诸如页面使用情况的统计数,空闲区域信息和锁信息
>里面保存着内存使用状态信息,如page使用统计,未使用的内存区域,互斥访问的锁(LOCKS)等。
`struct zone``linux/mmzone.h`中定义,在linux-4.7的内核中可以使用[include/linux/mmzone.h](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L324)来查看其定义
```cpp
```
| 字段| 描述 |
| :------- | ----: |
| free_pages | 未分配使用的page的数量 |
| lowmem_reserve[MAX_NR_ZONES] | 为了防止一些代码必须运行在低地址区域,所以事先保留一些低地址区域的内存 |
| pageset | page管理的数据结构对象,内部有一个page的列表(list)来管理。每个CPU维护一个page list,避免自旋锁的冲突。这个数组的大小和NR_CPUS(CPU的数量)有关,这个值是编译的时候确定的 |
| lock | 对zone并发访问的保护的自旋锁 |
| free_area[MAX_ORDER] | 页面使用状态的信息,以每个bit标识对应的page是否可以分配 |
| lru_lock | LRU(最近最少使用算法)的自旋锁 |
| reclaim_in_progress | 回收操作的原子锁 |
| active_list | 活跃的page的list |
| inactive_list | 不活跃的page的list |
| refill_counter | 从活跃的page list中移除的page的数量
| nr_active | 活跃的page的数量 |
| nr_inactive | 不活跃的page的数量 |
| pressure | 检查回收page的指标 |
| all_unreclaimable | 如果检测2次还是不能回收zone的page的话,则设置为1 |
| pages_scanned | 上次回收page后,扫描过的page的数量 |
| wait_table:等待一个page释放的等待队列哈希表。它会被| wait_on_page()unlock_page()函数使用. 用哈希表,而不用一个等待队列的原因,防止进程长期等待资源。
| wait_table_hash_nr_entries | 哈希表中的等待队列的数量
| zone_pgdat | 指向这个zone所在的pglist_data对象 |
| zone_start_pfn | 和node_start_pfn的含义一样。这个成员是用于表示zone中的开始那个page在物理内存中的位置的present_pages spanned_pages: 和node中的类似的成员含义一样 |
| name | zone的名字,字符串表示: "DMA""Normal" 和"HighMem" |
| ZONE_PADDING | 由于自旋锁频繁的被使用,因此为了性能上的考虑,将某些成员对齐到cache line中,有助于提高执行的性能。使用这个宏,可以确定zone->lockzone->lru_lockzone->pageset这些成员使用不同的cache line. |
Zone的管理调度的一些参数: Zone watermarks)
英文直译为zone的水平,打个比喻,就像一个水库,水存量很小的时候加大进水量,水存量达到一个标准的时候,减小进水量,当快要满的时候,可能就关闭了进水口。
pages_min pages_low and pages_high就类似与这个标准
当系统中可用内存很少的时候,系统代码kswapd被唤醒,开始回收释放page
pages_min pages_low and pages_high这些参数影响着这个代码的行为。
每个zone有三个水`平标准:pages_min pages_low and pages_high,帮助确定zone中内存分配使用的压力状态。kswapd和这3个参数的互动关系如下图:
page_min中所表示的page的数量值,是在内存初始化的过程中调用free_area_init_core()中计算的。这个数值是根据zone中的page的数量除以一个>1的系数来确定的。通常是这样初始化的ZoneSizeInPages/128。
page_low: 当空闲页面的数量达到page_low所标定的数量的时候,kswapd线程将被唤醒,并开始释放回收页面。这个值默认是page_min的2倍。
page_min: 当空闲页面的数量达到page_min所标定的数量的时候, 分配页面的动作和kswapd线程同步运行
page_high: 当空闲页面的数量达到page_high所标定的数量的时候, kswapd线程将重新休眠,通常这个数值是page_min的3倍。
zone的大小的计算
setup_memory()函数计算每个zone的大小:
PFN是物理内存以Page为单位的偏移量。系统可用的第一个PFN是min_low_pfn变量,开始与_end标号的后面,也就是kernel结束的地方。在文件mm/bootmem.c中对这个变量作初始化。系统可用的最后一个PFN是max_pfn变量,这个变量的初始化完全依赖与硬件的体系结构。x86的系统中,find_max_pfn()函数通过读取e820表获得最高的page frame的数值。同样在文件mm/bootmem.c中对这个变量作初始化。e820表是由BIOS创建的。
x86中,max_low_pfn变量是由find_max_low_pfn()函数计算并且初始化的,它被初始化成ZONE_NORMAL的最后一个page的位置。这个位置是kernel直接访问的物理内存,也是关系到kernel/userspace通过“PAGE_OFFSET宏”把线性地址内存空间分开的内存地址位置。(原文:This is the physical memory directly accessible by the kernel and is related to the kernel/userspace split in the linear address space marked by PAGE OFFSET.)我理解为这段地址kernel可以直接访问,可以通过PAGE_OFFSET宏直接将kernel所用的虚拟地址转换成物理地址的区段。在文件mm/bootmem.c中对这个变量作初始化。在内存比较小的系统中max_pfn和max_low_pfn的值相同
min_low_pfn max_pfn和max_low_pfn这3个值,也要用于对高端内存(high memory)的起止位置的计算。在arch/i386/mm/init.c文件中会对类似的highstart_pfn和highend_pfn变量作初始化。这些变量用于对高端内存页面的分配。后面将描述。
Zone等待队列表(zone wait queue table)
当对一个page做I/O操作的时候,I/O操作需要被锁住,防止不正确的数据被访问。进程在访问page前,调用wait_on_page()函数,使进程加入一个等待队列。访问完成后,UnlockPage()函数解锁其他进程对page的访问。其他正在等待队列中的进程被唤醒。每个page都可以有一个等待队列,但是太多的分离的等待队列使得花费太多的内存访问周期。替代的解决方法,就是将所有的队列放在struct zone数据结构中。
也可以有一种可能,就是struct zone中只有一个队列,但是这就意味着,当一个page unlock的时候,访问这个zone里内存page的所有休眠的进程将都被唤醒,这样就会出现拥堵(thundering herd)的问题。建立一个哈希表管理多个等待队列,能解决这个问题,zone->wait_table就是这个哈希表。哈希表的方法可能还是会造成一些进程不必要的唤醒。但是这种事情发生的机率不是很频繁的。下面这个图就是进程及等待队列的运行关系:
等待队列的哈希表的分配和建立在free_area_init_core()函数中进行。哈希表的表项的数量在wait_table_size() 函数中计算,并且保持在zone->wait_table_size成员中。最大4096个等待队列。最小是NoPages / PAGES_PER_WAITQUEUE的2次方,NoPages是zone管理的page的数量,PAGES_PER_WAITQUEUE被定义256。(原文:For smaller tables the size of the table is the minimum power of 2 required to store NoPages / PAGES PER WAITQUEUE number of queues where NoPages is the number of pages in the zone and PAGE PER WAITQUEUE is defined to be 256.
下面这个公式可以用于计算这个值:
zone->wait_table_bits用于计算:根据page 地址得到需要使用的等待队列在哈希表中的索引的算法因子。page_waitqueue()函数负责返回zone中page所对应等待队列。它用一个基于struct page虚拟地址的简单的乘法哈希算法来确定等待队列的。
page_waitqueue()函数用GOLDEN_RATIO_PRIME的地址和“右移zone→wait_table_bits一个索引值”的一个乘积来确定等待队列在哈希表中的索引的。
Zone的初始化
在kernel page table通过paging_init()函数完全建立起z来以后,zone被初始化。下面章节将描述这个。当然不同的体系结构这个过程肯定也是不一样的,但它们的目的却是相同的:确定什么参数需要传递给free_area_init()函数(对于UMA体系结构)或者free_area_init_node()函数(对于NUMA体系结构)。这里省略掉NUMA体系结构的说明。
free_area_init()函数的参数:
unsigned long *zones_sizes: 系统中每个zone所管理的page的数量的数组。这个时候,还没能确定zone中那些page是可以分配使用的(free)。这个信息知道boot memory allocator完成之前还无法知道。
来源: http://www.uml.org.cn/embeded/201208071.asp
#页面page
-------
#页表
-------
@@ -741,7 +741,7 @@ free_area_init()函数的参数:
unsigned long *zones_sizes: 系统中每个zone所管理的page的数量的数组。这个时候,还没能确定zone中那些page是可以分配使用的(free)。这个信息知道boot memory allocator完成之前还无法知道。
来源: http://www.uml.org.cn/embeded/201208071.asp
##4.6 冷热页
##4.6 冷热页与Per-CPU上的页面链表
-------
@@ -797,6 +797,15 @@ struct per_cpu_pages {
| list | 一个双链表, 保存了当前CPU的冷页或热页, 可使用内核的标准方法处理 |
在内核中只有一个子系统会积极的尝试为任何对象维护per-cpu上的list链表, 这个子系统就是slab分配器.
* struct per_cpu_pageset具有一个字段, 该字段
* struct per_cpu_pages则维护了链表中目前已有的一系列页面, 高极值和低极值决定了何时填充该集合或者释放一批页面, 变量决定了一个块中应该分配多少个页面, 并最后决定在页面前的实际链表中分配多少各页面
#4.7 内存域的第一个页帧zone_start_pfn
-------
@@ -835,7 +844,7 @@ min_low_pfn max_pfn和max_low_pfn这3个值,也要用于对高端内存(h
#
#总结
-------
在linux中,内核也不是对所有物理内存都一视同仁,内核而是把页分为不同的区, 使用区来对具有相似特性的页进行分组.
@@ -0,0 +1,20 @@
/*
* The zone field is never updated after free_area_init_core()
* sets it, so none of the operations on it need to be atomic.
*/
#define NODE_SHIFT 4
#define ZONE_SHIFT (BITS_PER_LONG - 8)
struct zone_struct;
extern struct zone_struct *zone_table[];
static inline zone_t *page_zone(struct page *page)
{
return zone_table[page->flags >> ZONE_SHIFT];
}
static inline void set_page_zone(struct page *page, unsigned long zone_num)
{
page->flags &= ~(~0UL << ZONE_SHIFT);
page->flags |= zone_num << ZONE_SHIFT;
}
@@ -6,8 +6,8 @@
| 2016-06-14 | [Linux-4.7](http://lxr.free-electrons.com/source/?v=4.7) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux内存管理](http://blog.csdn.net/gatieme/article/category/6225543) |
http://blog.chinaunix.net/uid-30282771-id-5176971.html
http://www.cnblogs.com/hanyan225/archive/2011/07/28/2119628.html
#1 前景回顾
-------
@@ -381,14 +381,120 @@ struct page {
```
这些标识是独立于体系结构的, 因而无法通过特定于CPU或计算机的信息(该信息保存在页表中)
如下如所示
![page的flags标识](./images/flags.png)
主要分为4部分,其中标志位flag向高位增长, 其余位字段向低位增长,中间存在空闲位
##3.1 页面到管理区和节点的映射
-------
在**早期的linux-2.4.18的内核**中, [struct page存储有一个指向对应管理区的指针page->zone](http://lxr.linux.no/linux-old+v2.4.18/include/linux/mm.h#L167), 但是该这hi真在吼吼被认为是一种浪费, 因为如果有成千上万的这样的struct page存在, 那么即使是很小的指针也会消耗大量的内存空间.
因此在**后来linux-2.4.x的更新**中, 删除了这个字段, 取而代之的是page->flags的最高[ZONE_SHIFT位](http://lxr.free-electrons.com/source/include/linux/mm.h?v=2.4.37#L340)和NODE_SHIFT位, 存储了其所在zone和node在内存区域表zone_table的编号索引.
那么内核在初始化内存管理区时, 首先建立管理区表zone_table. 参见[mm/page_alloc.c?v=2.4.37, line 38](http://lxr.free-electrons.com/source/mm/page_alloc.c?v=2.4.37#L38)
```cpp
/*
*
* The zone_table array is used to look up the address of the
* struct zone corresponding to a given zone number (ZONE_DMA,
* ZONE_NORMAL, or ZONE_HIGHMEM).
*/
zone_t *zone_table[MAX_NR_ZONES*MAX_NR_NODES];
EXPORT_SYMBOL(zone_table);
```
MAX_NR_ZONES是一个节点中所能包容纳的管理区的最大数, 如3个, 定义在[include/linux/mmzone.h?v=2.4.37, line 25](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=2.4.37#L25), 与zone区域的类型(ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM)定义在一起. 当然这时候我们这些标识都是通过宏的方式来实现的, 而不是如今的枚举类型
MAX_NR_NODES是可以存在的节点的最大数.
函数EXPORT_SYMBOL使得内核的变量或者函数可以被载入的模块(比如我们的驱动模块)所访问.
该表处理起来就像一个多维数组, 在函数free_area_init_core中, 一个节点的所有页面都会被初始化.
内核提供了page_zone通过页面查找其对应的内存区域zone_t, 页提供了set_page_zone接口, 而查找到了zone后, 可以通过 其`struct pglist_data *zone_pgdat`直接获取其所在node信息
```cpp
/*
* The zone field is never updated after free_area_init_core()
* sets it, so none of the operations on it need to be atomic.
*/
#define NODE_SHIFT 4
#define ZONE_SHIFT (BITS_PER_LONG - 8)
struct zone_struct;
extern struct zone_struct *zone_table[];
static inline zone_t *page_zone(struct page *page)
{
return zone_table[page->flags >> ZONE_SHIFT];
}
static inline void set_page_zone(struct page *page, unsigned long zone_num)
{
page->flags &= ~(~0UL << ZONE_SHIFT);
page->flags |= zone_num << ZONE_SHIFT;
}
```
而**后来的内核(至今linux-4.7)**中, 这些必要的标识(ZONE_DMA等)都是通过枚举类型实现的(ZONE_DMA等用enum zone_type定义), 然后zone_table也被移除, 参照[[PATCH] zone table removal miss merge](https://lkml.org/lkml/2006/9/27/112)
因此内核提供了新的思路, 参见[include/linux/mm.h?v4.7, line 907](http://lxr.free-electrons.com/source/include/linux/mm.h?v4.7#L907)
```cpp
static inline struct zone *page_zone(const struct page *page)
{
return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)];
}
static inline void set_page_zone(struct page *page, enum zone_type zone)
{
page->flags &= ~(ZONES_MASK << ZONES_PGSHIFT);
page->flags |= (zone & ZONES_MASK) << ZONES_PGSHIFT;
}
static inline void set_page_node(struct page *page, unsigned long node)
{
page->flags &= ~(NODES_MASK << NODES_PGSHIFT);
page->flags |= (node & NODES_MASK) << NODES_PGSHIFT;
}
```
其中NODE_DATA使用了全局的node表进行索引.
在UMA结构的机器中, 只有一个node结点即contig_page_data, 此时NODE_DATA直接指向了全局的contig_page_data, 而与node的编号nid无关, 参照[include/linux/mmzone.h?v=4.7, line 858](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L858), 其中全局唯一的cnode结点ontig_page_data定义在[mm/nobootmem.c?v=4.7, line 27](http://lxr.free-electrons.com/source/mm/nobootmem.c?v=4.7#L27)
```cpp
#ifndef CONFIG_NEED_MULTIPLE_NODES
extern struct pglist_data contig_page_data;
#define NODE_DATA(nid) (&contig_page_data)
#define NODE_MEM_MAP(nid) mem_map
else
/* ...... */
#endif
```
而对于NUMA结构的系统中, 所有的node都存储在node_data数组中,
NODE_DATA直接通过node编号索引即可, 参见[NODE_DATA的定义](http://lxr.free-electrons.com/ident?v=4.7;i=NODE_DATA)
```cpp
extern struct pglist_data *node_data[];
#define NODE_DATA(nid) (node_data[(nid)])
```
那么page的flags标识主要分为4部分,其中标志位flag向高位增长, 其余位字段向低位增长,中间存在空闲位
| 字段 | 描述 |
|:----:|:---:|
@@ -397,9 +503,25 @@ struct page {
| zone | 内存域标志,标识该page属于哪一个zone |
| flag | page的状态标识 |
如下图所示
![page的flags标识](./images/flags.png)
##3.2 内存页标识pageflags
-------
其中最后一个flag用于标识page的状态, 这些状态由枚举常量[`enum pageflags`](http://lxr.free-electrons.com/source/include/linux/page-flags.h?v=4.7#L74)定义, 定义在[include/linux/page-flags.h?v=4.7, line 74](http://lxr.free-electrons.com/source/include/linux/page-flags.h?v=4.7#L74). 常用的有如下状态
```c
```cpp
enum pageflags {
PG_locked, /* Page is locked. Don't touch. */
PG_error,
@@ -511,7 +633,8 @@ enum pageflags {
* 其次标识的函数接口也变了, 早期的内核中, 针对每个宏标识都设置了一组test/set/clear, 参见[/include/linux/mm.h?v=2.4.37, line 324](http://lxr.free-electrons.com/source/include/linux/mm.h?v=2.4.37#L324)
形式如下
```c
```cpp
PageXXX(page)page是否设置了PG_XXX位
SetPageXXX(page)page的PG_XXX位
ClearPageXXX(page)page的PG_XXX位
@@ -522,7 +645,7 @@ TestClearPageXXX(page):清除page的PG_XXX位,并返回原值
很多情况下, 需要等待页的状态改变, 然后才能恢复工作. 因此内核提供了两个辅助函数
```c
```cpp
http://lxr.free-electrons.com/source/include/linux/pagemap.h?v=4.7#L495
/*
* Wait for a page to be unlocked.
@@ -545,10 +668,4 @@ static inline void wait_on_page_writeback(struct page *page)
wait_on_page_writeback的工作方式类似, 该函数会等待与页面相关的所有待决回写操作结束, 将页面包含的数据同步到块设备为止.
#4 页面映射到管理区
-------
@@ -0,0 +1,33 @@
服务器体系与共享存储器架构
=======
| 日期 | 内核版本 | 架构| 作者 | GitHub| CSDN |
| ------- |:-------:|:-------:|:-------:|:-------:|:-------:|
| 2016-06-14 | [Linux-4.7](http://lxr.free-electrons.com/source/?v=4.7) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux内存管理](http://blog.csdn.net/gatieme/article/category/6225543) |
#1 目录
-------
#2 参考内容
-------
| 链接 |
|:-------:|
| [内存管理(一)内存模型之Node](http://biancheng.dnbcw.info/linux/387391.html) |
| [Linux 内存管理 重要结构体](http://blog.chinaunix.net/uid-26009500-id-3078986.html) |
| [Bootmem机制](http://blog.csdn.net/samssm/article/details/25064897) |
| [Linux-2.6.32 NUMA架构之内存和调度](http://www.cnblogs.com/zhenjing/archive/2012/03/21/linux_numa.html) |
| [Linux 用户空间与内核空间——高端内存详解](http://blog.csdn.net/tommy_wxie/article/details/17122923) |
| [探索 Linux 内存模型](http://www.ibm.com/developerworks/cn/linux/l-memmod/) |
| [Linux内存管理](http://blog.chinaunix.net/uid/21718047/cid-151509-list-2.html) |
| [内存管理-之内核内存管理-基于linux3.10](http://blog.csdn.net/shichaog/article/details/45509917) |
| [内存管理(一)](http://www.cnblogs.com/openix/p/3334026.html) |
| [Linux内存管理原理](http://www.cnblogs.com/zhaoyl/p/3695517.html) |
| [第 15 章 内存映射和 DMA](http://www.embeddedlinux.org.cn/ldd3/ch15.html) |
| [ 内存管理(二)struct page ](http://blog.chinaunix.net/uid-30282771-id-5176971.html) |