From 43ccb8447aa2c319c36b2434f67e64344dadda83 Mon Sep 17 00:00:00 2001 From: gatieme Date: Fri, 5 Aug 2016 20:51:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E5=86=85=E5=AD=98?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E4=B9=8B=E5=86=85=E5=AD=98=E6=8F=8F=E8=BF=B0?= =?UTF-8?q?(=E5=9B=9B)--=E9=A1=B5=E5=B8=A7struct=20page=E8=AF=A6=E8=A7=A3.?= =?UTF-8?q?..?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 2 +- .../01-description/01-memory/README.md | 253 ++---------------- .../01-description/03-zone/README.md | 13 +- .../02-memory/01-description/04-page/1.c | 20 ++ .../01-description/04-page/README.md | 147 ++++++++-- .../kernel/02-memory/01-description/README.md | 33 +++ 6 files changed, 212 insertions(+), 256 deletions(-) create mode 100644 study/kernel/02-memory/01-description/04-page/1.c create mode 100644 study/kernel/02-memory/01-description/README.md diff --git a/Makefile b/Makefile index 444b097..c8c9eec 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ GITHUB_COMMIT := $(COMMIT) ifeq ($(COMMIT), ) - GITHUB_COMMIT="完善了README.md..." + GITHUB_COMMIT="更新了内存管理之内存描述(四)--页帧struct page详解..." endif diff --git a/study/kernel/02-memory/01-description/01-memory/README.md b/study/kernel/02-memory/01-description/01-memory/README.md index 20b90e9..aa207c0 100644 --- a/study/kernel/02-memory/01-description/01-memory/README.md +++ b/study/kernel/02-memory/01-description/01-memory/README.md @@ -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_DMA,ZONE_NORMAL,ZONE_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->lock,zone->lru_lock,zone->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 -------- - - -#页表 -------- diff --git a/study/kernel/02-memory/01-description/03-zone/README.md b/study/kernel/02-memory/01-description/03-zone/README.md index 673f986..276a259 100644 --- a/study/kernel/02-memory/01-description/03-zone/README.md +++ b/study/kernel/02-memory/01-description/03-zone/README.md @@ -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中,内核也不是对所有物理内存都一视同仁,内核而是把页分为不同的区, 使用区来对具有相似特性的页进行分组. diff --git a/study/kernel/02-memory/01-description/04-page/1.c b/study/kernel/02-memory/01-description/04-page/1.c new file mode 100644 index 0000000..c835950 --- /dev/null +++ b/study/kernel/02-memory/01-description/04-page/1.c @@ -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; +} \ No newline at end of file diff --git a/study/kernel/02-memory/01-description/04-page/README.md b/study/kernel/02-memory/01-description/04-page/README.md index 9211d03..76e3d2f 100644 --- a/study/kernel/02-memory/01-description/04-page/README.md +++ b/study/kernel/02-memory/01-description/04-page/README.md @@ -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 页面映射到管理区 -------- - - - - diff --git a/study/kernel/02-memory/01-description/README.md b/study/kernel/02-memory/01-description/README.md new file mode 100644 index 0000000..3ae7485 --- /dev/null +++ b/study/kernel/02-memory/01-description/README.md @@ -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) | +