diff --git a/study/kernel/02-memory/01-description/03-zone/1.c b/study/kernel/02-memory/01-description/03-zone/1.c new file mode 100644 index 0000000..041f89a --- /dev/null +++ b/study/kernel/02-memory/01-description/03-zone/1.c @@ -0,0 +1,19 @@ +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]; +}; + +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 +}; \ No newline at end of file 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 8adf661..00af446 100644 --- a/study/kernel/02-memory/01-description/03-zone/README.md +++ b/study/kernel/02-memory/01-description/03-zone/README.md @@ -744,6 +744,7 @@ unsigned long *zones_sizes: 系统中每个zone所管理的page的数量的数 ##4.6 冷热页 ------- + `struct zone`的pageset成员用于实现冷热分配器(hot-n-cold allocator) ```cpp @@ -756,36 +757,80 @@ struct zone >尽管内存域可能属于一个特定的NUMA结点, 因而关联到某个特定的CPU。 但其他CPU的告诉缓存仍然可以包含该内存域中的页面. 最终的效果是, 每个处理器都可以访问系统中的所有页, 尽管速度不同. 因而, 特定于内存域的数据结构不仅要考虑到所属NUMA结点相关的CPU, 还必须照顾到系统中其他的CPU. -```c -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 */ +pageset是一个指针, 其容量与系统能够容纳的CPU的数目的最大值相同. - /* Lists of pages, one per migrate type stored on the pcp-lists */ - struct list_head lists[MIGRATE_PCPTYPES]; -}; +数组元素类型为per_cpu_pageset, 定义在[include/linux/mmzone.h?v4.7, line 254](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v4.7#L254), 如下所示 +```cpp struct per_cpu_pageset { - struct per_cpu_pages pcp; + struct per_cpu_pages pcp; #ifdef CONFIG_NUMA - s8 expire; + s8 expire; #endif #ifdef CONFIG_SMP - s8 stat_threshold; - s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS]; + s8 stat_threshold; + s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS]; #endif }; ``` -#5 内存域水印的计算 +该结构由一个per_cpu_pages pcp变量组成, 该数据结构定义如下, 位于[include/linux/mmzone.h?v4.7, line 245](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v4.7#L245) + + + +```cpp +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, 则表明列表中的页太多了 | +| batch | 如果可能, CPU的高速缓存不是用单个页来填充的, 而是欧诺个多个页组成的块, batch作为每次添加/删除页时多个页组成的块大小的一个参考值 | +| list | 一个双链表, 保存了当前CPU的冷页或热页, 可使用内核的标准方法处理 | + + +#4.7 内存域的第一个页帧zone_start_pfn ------- +struct zone中通过zone_start_pfn成员标记了内存管理区的页面地址. + + +然后内核也通过一些全局变量标记了物理内存所在页面的偏移, 这些变量定义在[mm/nobootmem.c?v4.7, line 31](http://lxr.free-electrons.com/source/mm/nobootmem.c?v4.7#L31) + +```cpp +unsigned long max_low_pfn; +unsigned long min_low_pfn; +unsigned long max_pfn; +unsigned long long max_possible_pfn; +``` + +PFN是物理内存以Page为单位的偏移量 + +| 变量 | 描述 | +|:----:|:---:| +| max_low_pfn | x86中,max_low_pfn变量是由find_max_low_pfn函数计算并且初始化的,它被初始化成ZONE_NORMAL的最后一个page的位置。这个位置是kernel直接访问的物理内存, 也是关系到kernel/userspace通过“PAGE_OFFSET宏”把线性地址内存空间分开的内存地址位置 | +| min_low_pfn | 系统可用的第一个pfn是[min_low_pfn变量](http://lxr.free-electrons.com/source/include/linux/bootmem.h?v4.7#L16), 开始与_end标号的后面, 也就是kernel结束的地方.在文件mm/bootmem.c中对这个变量作初始化 +| max_pfn | 系统可用的最后一个PFN是[max_pfn变量](http://lxr.free-electrons.com/source/include/linux/bootmem.h?v4.7#L21), 这个变量的初始化完全依赖与硬件的体系结构. | +| max_possible_pfn | -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的值相同 + + + +x86的系统中, find_max_pfn函数通过读取e820表获得最高的page frame的数值, 同样在文件mm/bootmem.c中对这个变量作初始化。e820表是由BIOS创建的 + +>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变量作初始化。这些变量用于对高端内存页面的分配。后面将描述。 diff --git a/study/kernel/02-memory/01-description/04-page/README.md b/study/kernel/02-memory/01-description/04-page/README.md new file mode 100644 index 0000000..4281349 --- /dev/null +++ b/study/kernel/02-memory/01-description/04-page/README.md @@ -0,0 +1,74 @@ + 服务器体系与共享存储器架构 +======= + +| 日期 | 内核版本 | 架构| 作者 | 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 前景回顾 +------- + +#1.1 UMA和NUMA两种模型 +------- + +共享存储型多处理机有两种模型 + +* 均匀存储器存取(Uniform-Memory-Access,简称UMA)模型 + + +* 非均匀存储器存取(Nonuniform-Memory-Access,简称NUMA)模型 + +#1.2 (N)UMA模型中linux内存的机构 +------- + +非一致存储器访问(NUMA)模式下 + +* 处理器被划分成多个"节点"(node), 每个节点被分配有的本地存储器空间. 所有节点中的处理器都可以访问全部的系统物理存储器,但是访问本节点内的存储器所需要的时间,比访问某些远程节点内的存储器所花的时间要少得多 + + +* 内存被分割成多个区域(BANK,也叫"簇"),依据簇与处理器的"距离"不同, 访问不同簇的代码也会不同. + + +##1.3 Linux如何描述物理内存 +------- + +Linux把物理内存划分为三个层次来管理 + +| 层次 | 描述 | +|:----:|:----:| +| 存储节点(Node) | CPU被划分为多个节点(node), 内存则被分簇, 每个CPU对应一个本地物理内存, 即一个CPU-node对应一个内存簇bank,即每个内存簇被认为是一个节点 | +| 管理区(Zone) | 每个物理内存节点node被划分为多个内存管理区域, 用于表示不同范围的内存, 内核可以使用不同的映射方式映射物理内存 | +| 页面(Page) | 内存被细分为多个页面帧, 页面是最基本的页面分配的单位 | + + +* 首先内存被划分为结点. 内存中的每个节点都是由pg_data_t描述,而pg_data_t由struct pglist_data定义而来, 该数据结构定义在[include/linux/mmzone.h, line 615](http://lxr.free-electrons.com/source/include/linux/mmzone.h#L615), 每个结点关联到系统中的一个处理器, 内核中表示为`pg_data_t`的实例. 系统中每个节点被链接到一个以NULL结尾的`pgdat_list`链表中<而其中的每个节点利用`pg_data_tnode_next`字段链接到下一节.而对于PC这种UMA结构的机器来说, 只使用了一个成为contig_page_data的静态pg_data_t结构. + + +* 接着各个节点又被划分为内存管理区域, 一个管理区域通过struct zone_struct描述, 其被定义为zone_t, 用以表示内存的某个范围, 低端范围的16MB被描述为ZONE_DMA, 某些工业标准体系结构中的(ISA)设备需要用到它, 然后是可直接映射到内核的普通内存域ZONE_NORMAL,最后是超出了内核段的物理地址域ZONE_HIGHMEM, 被称为高端内存. 是系统中预留的可用内存空间, 不能被内核直接映射. + +* 最后页帧(page frame)代表了系统内存的最小单位, 堆内存中的每个页都会创建一个struct page的一个实例. 传统上,把内存视为连续的字节,即内存为字节数组,内存单元的编号(地址)可作为字节数组的索引. 分页管理时,将若干字节视为一页,比如4K byte. 此时,内存变成了连续的页,即内存为页数组,每一页物理内存叫页帧,以页为单位对内存进行编号,该编号可作为页数组的索引,又称为页帧号. + + +##1.4 今日内容(页帧struct page) +------- + + +页帧代表了系统内存的最小单位, 对内存中的每个页都会创建struct page的一个实例. 内核必须要保证page结构体足够的小,否则仅struct page就要占用大量的内存. + +因为即使在中等程序的内存配置下, 系统的内存同样会分解为大量的页. 例如, IA-32系统中标准页长度为4KB, 在内存大小为384MB时, 大约有100000页. 就当今的标准而言, 这个容量算不上很大, 但页的数目已经非常可观了 + +因而出于节省内存的考虑,内核要尽力保持struct page尽可能的小. 在典型的系统中, 由于页的数目巨大, 因此对page结构的小改动, 也可能导致保存所有page实例所需的物理内存暴涨. + +页的广泛使用, 增加了保持结构长度的难度 : 内存管理的许多部分都使用页, 用于各种不同的用途. 内核的一部分可能完全依赖于struct page提供的特定信息, 而这部分信息堆内核的其他部分页可能是完全无用的. 等等. + + +我们今天就来详细讲解一下linux下物理页帧的描述 + + + + + +因而struct page中使用了大量的联合体union. +