mirror of
https://github.com/gatieme/LDD-LinuxDeviceDrivers.git
synced 2026-09-25 06:43:35 +08:00
...
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+85
-12
@@ -154,23 +154,61 @@ void __init setup_arch(char **cmdline_p)
|
||||
|
||||
这个阶段的内存分配其实很简单, 因此我们往往称之为内存分配器(而不是内存管理器), 早期的内核中内存分配器使用的**bootmem引导分配器**, 它基于一个内存位图bitmap, 使用最优适配算法来查找内存, 但是这个分配器有很大的缺陷, 最严重的就是内存碎片的问题, 因此在后来的内核中将其舍弃《而使用了**新的memblock机制**. memblock机制的初始化在arm64上是通过[arm64_memblock_init](http://lxr.free-electrons.com/source/arch/arm64/kernel/setup.c?v=4.7#L229)函数来实现的
|
||||
|
||||
```cpp
|
||||
```cpp
|
||||
start_kernel()
|
||||
|---->page_address_init()
|
||||
| 考虑支持高端内存
|
||||
| 业务:初始化page_address_pool链表;
|
||||
| 将page_address_maps数组元素按索引降序插入
|
||||
| page_address_pool链表;
|
||||
| 初始化page_address_htable数组.
|
||||
|
|
||||
|---->setup_arch(&command_line);
|
||||
| 初始化特定体系结构的内容
|
||||
|
|
||||
|---->arm64_memblock_init( );
|
||||
| 初始化引导阶段的内存分配器memblock
|
||||
|
|
||||
|---->paging_init();
|
||||
| 分页机制初始化
|
||||
|
|
||||
|---->bootmem_init();
|
||||
| 始化内存数据结构包括内存节点, 内存域和页帧page
|
||||
|
|
||||
|---->arm64_numa_init();
|
||||
| 支持numa架构
|
||||
|
|
||||
|---->zone_sizes_init(min, max);
|
||||
来初始化节点和管理区的一些数据项
|
||||
|
|
||||
|---->free_area_init_node
|
||||
| 初始化内存节点
|
||||
|
|
||||
|---->free_area_init_core
|
||||
| 初始化zone
|
||||
|
|
||||
|---->memmap_init
|
||||
| 初始化page页面
|
||||
|
|
||||
|---->memblock_dump_all();
|
||||
| 初始化完成, 显示memblock的保留的所有内存信息
|
||||
|
|
||||
|---->build_all_zonelist() [当前位置]
|
||||
| 为系统中的zone建立后备zone的列表.
|
||||
| 所有zone的后备列表都在
|
||||
| pglist_data->node_zonelists[0]中;
|
||||
|
|
||||
| 期间也对per-CPU变量boot_pageset做了初始化.
|
||||
|
|
||||
```
|
||||
|
||||
|
||||
|
||||
##1.6 今日内容(第二阶段--初始化buddy内存管理)
|
||||
##1.6 今日内容(第二阶段(二)--初始化备用内存域列表zonelists)
|
||||
-------
|
||||
|
||||
|
||||
**初始化内存分页机制**
|
||||
|
||||
|
||||
在初始化内存的结点和内存区域之前, 内核先通过pagging_init初始化了内核的分页机制, 这样我们的虚拟运行空间就初步建立, 并可以完成物理地址到虚拟地址空间的映射工作.
|
||||
|
||||
|
||||
在arm64架构下, 内核在start_kernel()->setup_arch()中通过arm64_memblock_init( )完成了memblock的初始化之后, 接着通过setup_arch()->paging_init()开始初始化分页机制
|
||||
|
||||
|
||||
paging_init负责建立只能用于内核的页表, 用户空间是无法访问的. 这对管理普通应用程序和内核访问内存的方式,有深远的影响
|
||||
我们之前讲了在memblock完成之后, 内存初始化开始进入第二阶段, 第二阶段是一个漫长的过程, 它执行了一系列复杂的操作, 从体系结构相关信息的初始化慢慢向上层展开, 其主要执行了如下操作
|
||||
|
||||
|
||||
|
||||
@@ -179,6 +217,41 @@ paging_init负责建立只能用于内核的页表, 用户空间是无法访问
|
||||
|
||||
在完成了基础的内存结点和内存域的初始化工作以后, 我们必须克服一些硬件的特殊设置
|
||||
|
||||
* 在初始化内存的结点和内存区域之前, 内核先通过pagging_init初始化了内核的分页机制, 这样我们的虚拟运行空间就初步建立, 并可以完成物理地址到虚拟地址空间的映射工作.
|
||||
|
||||
在arm64架构下, 内核在start_kernel()->setup_arch()中通过arm64_memblock_init( )完成了memblock的初始化之后, 接着通过setup_arch()->paging_init()开始初始化分页机制
|
||||
|
||||
|
||||
paging_init负责建立只能用于内核的页表, 用户空间是无法访问的. 这对管理普通应用程序和内核访问内存的方式,有深远的影响
|
||||
|
||||
|
||||
* 在分页机制完成后, 内核通过setup_arch()->bootmem_init开始进行内存基本数据结构(内存结点pg_data_t, 内存域zone和页帧)的初始化工作, 就是在这个函数中, 内核开始从体系结构相关的部分逐渐展开到体系结构无关的部分, 在zone_sizes_init->free_area_init_node中开始, 内核开始进行内存基本数据结构的初始化, 也不再依赖于特定体系结构无关的层次
|
||||
|
||||
```cpp
|
||||
bootmem_init
|
||||
始化内存数据结构包括内存节点, 内存域和页帧page
|
||||
|
|
||||
|---->arm64_numa_init();
|
||||
| 支持numa架构
|
||||
|
|
||||
|---->zone_sizes_init(min, max);
|
||||
来初始化节点和管理区的一些数据项
|
||||
|
|
||||
|---->free_area_init_node
|
||||
| 初始化内存节点
|
||||
|
|
||||
|---->free_area_init_core
|
||||
| 初始化zone
|
||||
|
|
||||
|---->memmap_init
|
||||
| 初始化page页面
|
||||
|
|
||||
|---->memblock_dump_all();
|
||||
| 初始化完成, 显示memblock的保留的所有内存信息
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
**建立内存管理的数据结构**
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@@ -10,6 +10,16 @@
|
||||
|
||||
在内核初始化完成之后, 内存管理的责任就由伙伴系统来承担. 伙伴系统基于一种相对简单然而令人吃惊的强大算法.
|
||||
|
||||
Linux内核使用二进制伙伴算法来管理和分配物理内存页面, 该算法由Knowlton设计, 后来Knuth又进行了更深刻的描述.
|
||||
|
||||
伙伴系统是一个结合了2的方幂个分配器和空闲缓冲区合并计技术的内存分配方案, 其基本思想很简单. 内存被分成含有很多页面的大块, 每一块都是2个页面大小的方幂. 如果找不到想要的块, 一个大块会被分成两部分, 这两部分彼此就成为伙伴. 其中一半被用来分配, 而另一半则空闲. 这些块在以后分配的过程中会继续被二分直至产生一个所需大小的块. 当一个块被最终释放时, 其伙伴将被检测出来, 如果伙伴也空闲则合并两者.
|
||||
|
||||
* 内核如何记住哪些内存块是空闲的
|
||||
* 分配空闲页面的方法
|
||||
* 影响分配器行为的众多标识位
|
||||
* 内存碎片的问题和分配器如何处理碎片
|
||||
|
||||
|
||||
|
||||
|
||||
#2 伙伴系统的结构
|
||||
@@ -28,7 +38,7 @@ struct zone
|
||||
};
|
||||
```
|
||||
|
||||
struct free_area是一个辅助数据结构, 它定义在[include/linux/mmzone.h?v=4.7, line 88](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L88)
|
||||
struct free_area是一个伙伴系统的辅助数据结构, 它定义在[include/linux/mmzone.h?v=4.7, line 88](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L88)
|
||||
|
||||
```cpp
|
||||
struct free_area {
|
||||
@@ -43,7 +53,30 @@ unsigned long nr_free;
|
||||
| nr_free | 指定了当前内存区中空闲页块的数目(对0阶内存区逐页计算,对1阶内存区计算页对的数目,对2阶内存区计算4页集合的数目,依次类推 |
|
||||
|
||||
|
||||
阶是伙伴系统中一个非常重要的术语. 它描述了内存分配的数量单位. 内存块的长度是2order,其中order的范围从0到MAX_ORDER, 参见[include/linux/mmzone.h?v=4.7, line 22](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L22)
|
||||
|
||||
伙伴系统的分配器维护空闲页面所组成的块, 这里每一块都是2的方幂个页面, 方幂的指数称为**阶**.
|
||||
|
||||
|
||||
阶是伙伴系统中一个非常重要的术语. 它描述了内存分配的数量单位. 内存块的长度是$2^order$, 其中order的范围从0到MAX_ORDER
|
||||
|
||||
|
||||
zone->free_area[MAX_ORDER]数组中阶作为各个元素的索引, 用于指定对应链表中的连续内存区包含多少个页帧.
|
||||
|
||||
* 数组中第0个元素的阶为0, 它的free_list链表域指向具有包含区为单页($2^0=1$)的内存页面链表
|
||||
|
||||
* 数组中第1个元素的free_list域管理的内存区为两页($2^1=2$)
|
||||
|
||||
* 第3个管理的内存区为4页, 依次类推.
|
||||
|
||||
* 直到$2^{MAX_ORDER-1}$个页面大小的块
|
||||
|
||||

|
||||
|
||||
|
||||
##MAX_ORDER与FORCE_MAX_ZONEORDER配置选项
|
||||
-------
|
||||
|
||||
一般来说`MAX_ORDER`默认定义为11, 这意味着一次分配可以请求的页数最大是2^11=2048, 参见[include/linux/mmzone.h?v=4.7, line 22](http://lxr.free-electrons.com/source/include/linux/mmzone.h?v=4.7#L22)
|
||||
|
||||
```cpp
|
||||
/* Free memory management - zoned buddy allocator. */
|
||||
@@ -55,7 +88,7 @@ unsigned long nr_free;
|
||||
#define MAX_ORDER_NR_PAGES (1 << (MAX_ORDER - 1))
|
||||
```
|
||||
|
||||
该常数通常设置为11,这意味着一次分配可以请求的页数最大是2^11=2048
|
||||
|
||||
|
||||
但如果特定于体系结构的代码设置了`FORCE_MAX_ZONEORDER`配置选项, 该值也可以手工改变
|
||||
|
||||
@@ -82,16 +115,10 @@ default "11"`
|
||||
|
||||
|
||||
|
||||
free_area[]数组中各个元素的索引也解释为阶, 用于指定对应链表中的连续内存区包含多少个页帧.
|
||||
|
||||
* 第0个链表包含的内存区为单页($2^0=1$)
|
||||
|
||||
* 第1个链表管理的内存区为两页($2^1=2$)
|
||||
|
||||
* 第3个管理的内存区为4页, 依次类推.
|
||||
|
||||
|
||||
内存区是如何连接的?
|
||||
##内存区是如何连接的
|
||||
-------
|
||||
|
||||
内存区中第1页内的链表元素, 可用于将内存区维持在链表中。因此,也不必引入新的数据结构来管理物理上连续的页,否则这些页不可能在同一内存区中. 如下图所示
|
||||
|
||||
@@ -112,7 +139,8 @@ free_area[]数组中各个元素的索引也解释为阶, 用于指定对应链
|
||||
|
||||
最后要注意, 有关伙伴系统和当前状态的信息可以在/proc/buddyinfo中获取
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
上述输出给出了各个内存域中每个分配阶中空闲项的数目, 从左至右, 阶依次升高. 上面给出的信息取自4 GiB物理内存的AMD64系统.
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
服务器体系与共享存储器架构
|
||||
=======
|
||||
|
||||
| 日期 | 内核版本 | 架构| 作者 | 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 启动阶段的内存初始化
|
||||
-------
|
||||
|
||||
之前我们讲解了系统内存管理初始化的第二阶段(buddy的初始化), 但是我们讲解的很粗糙, 我们仅仅讲解了内存管理的主要流程创建
|
||||
|
||||
|
||||
```cpp
|
||||
start_kernel()
|
||||
|---->page_address_init()
|
||||
| 考虑支持高端内存
|
||||
| 业务:初始化page_address_pool链表;
|
||||
| 将page_address_maps数组元素按索引降序插入
|
||||
| page_address_pool链表;
|
||||
| 初始化page_address_htable数组.
|
||||
|
|
||||
|---->setup_arch(&command_line);
|
||||
| 初始化特定体系结构的内容
|
||||
|
|
||||
|---->arm64_memblock_init( );
|
||||
| 初始化引导阶段的内存分配器memblock
|
||||
|
|
||||
|---->paging_init();
|
||||
| 分页机制初始化
|
||||
|
|
||||
|---->bootmem_init();
|
||||
| 始化内存数据结构包括内存节点, 内存域和页帧page
|
||||
|
|
||||
|---->arm64_numa_init();
|
||||
| 支持numa架构
|
||||
|
|
||||
|---->zone_sizes_init(min, max);
|
||||
来初始化节点和管理区的一些数据项
|
||||
|
|
||||
|---->free_area_init_node
|
||||
| 初始化内存节点
|
||||
|
|
||||
|---->free_area_init_core
|
||||
| 初始化zone
|
||||
|
|
||||
|---->memmap_init
|
||||
| 初始化page页面
|
||||
|
|
||||
|---->memblock_dump_all();
|
||||
| 初始化完成, 显示memblock的保留的所有内存信息
|
||||
|
|
||||
|---->build_all_zonelist()
|
||||
| 为系统中的zone建立后备zone的列表.
|
||||
| 所有zone的后备列表都在
|
||||
| pglist_data->node_zonelists[0]中;
|
||||
|
|
||||
| 期间也对per-CPU变量boot_pageset做了初始化.
|
||||
|
|
||||
```
|
||||
|
||||
|
||||
```cpp
|
||||
|---->page_alloc_init()
|
||||
|---->hotcpu_notifier(page_alloc_cpu_notifier, 0);
|
||||
| 不考虑热插拔CPU
|
||||
|
|
||||
|---->pidhash_init()
|
||||
| 详见下文.
|
||||
| 根据低端内存页数和散列度,分配hash空间,并赋予pid_hash
|
||||
|
|
||||
|---->vfs_caches_init_early()
|
||||
|---->dcache_init_early()
|
||||
| dentry_hashtable空间,d_hash_shift, h_hash_mask赋值;
|
||||
| 同pidhash_init();
|
||||
| 区别:
|
||||
| 散列度变化了(13 - PAGE_SHIFT);
|
||||
| 传入alloc_large_system_hash的最后参数值为0;
|
||||
|
|
||||
|---->inode_init_early()
|
||||
| inode_hashtable空间,i_hash_shift, i_hash_mask赋值;
|
||||
| 同pidhash_init();
|
||||
| 区别:
|
||||
| 散列度变化了(14 - PAGE_SHIFT);
|
||||
| 传入alloc_large_system_hash的最后参数值为0;
|
||||
|解buddy的内部机理,
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
我们只在特定于体系结构的代码中看到了内核如何检测系统中的可用内存。与高层数据结构(如内存域和结点)的关联, 则需要根据该信息构建。我们知道,体系结构相关代码需要在启动期间建立以下信息:
|
||||
|
||||
* 系统中各个内存域的页帧边界,保存在max_zone_pfn数组
|
||||
|
||||
* 各结点页帧的分配情况,保存在全局变量early_node_map中
|
||||
|
||||
|
||||
##1.2 内存节点的初始化
|
||||
-------
|
||||
|
||||
|
||||
|
||||
##1.3 今日内容(buddy的初始化)
|
||||
-------
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
服务器体系与共享存储器架构
|
||||
=======
|
||||
|
||||
| 日期 | 内核版本 | 架构| 作者 | 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 目录
|
||||
-------
|
||||
|
||||
| CSDN | GitHub |
|
||||
|:-------:|:-------:|
|
||||
| 描述物理内存 |
|
||||
| 页表管理 |
|
||||
| 初始化内存挂历 |
|
||||
| 物理内存的管理 |
|
||||
| slab分配器 |
|
||||
|
||||
| 非连续内存分配 |
|
||||
| 高端内存管理 |
|
||||
| 页面帧回收 |
|
||||
| 交换管理 |
|
||||
| 进程虚拟地址空间 |
|
||||
| 共享内存虚拟文件系统 |
|
||||
| 内存溢出管理 |
|
||||
|
||||
|
||||
#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) |
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Reference in New Issue
Block a user