目录

介绍

内存块

内存块初始化

Memblock API

获取有关内存区域的信息

Memblock调试

链接

相关阅读


看原文:《Linux内存管理:memblock》

介绍

内存管理是操作系统内核中最复杂的部分(我认为它是最复杂的)。在内核入口点部分之前的最后准备中,我们在调用start_kernel函数之前就停了下来。在内核运行第一个init进程之前,此函数将初始化所有内核功能(包括与体系结构相关的功能)。您可能还记得在引导时我们建立了早期页面表,标识页面表和Fixmap页面表的情况。尚无复杂的内存管理功能。当。。。的时候start_kernel调用函数,我们将看到向内存管理更复杂的数据结构和技术的过渡。为了更好地了解linux内核中的初始化过程,我们需要对这些技术有一个清晰的了解。本章将从开始,概述Linux内核内存管理框架及其API的不同部分memblock

内存块

Memblock是早期引导期间管理内存区域的方法之一,而通常的内核内存分配器尚未启动并运行。以前叫它Logical Memory Block,但是在Yinghai Lu的补丁程序中,将其重命名为memblock。由于Linux内核用于x86_64体系结构使用此方法。我们已经memblock在内核入口点部分的最后准备中见过。现在是时候更加熟悉它了。我们将看到它是如何实现的。

我们将开始memblock从数据结构中学习。所有与逻辑内存块相关的数据结构的定义都可以在include / linux / memblock.h头文件中找到。

第一个结构与此部分具有相同的名称,它是:

struct memblock {bool bottom_up;phys_addr_t current_limit;struct memblock_type memory;   --> array of memblock_regionstruct memblock_type reserved; --> array of memblock_region
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAPstruct memblock_type physmem;
#endif
};

该结构包含五个字段。首先是bottom_up当它为时,允许以自下而上的模式分配内存true。下一个字段是current_limit。该字段描述了存储块的限制大小。接下来的三个字段描述了存储块的类型。它可以是:保留,内存和物理内存(如果CONFIG_HAVE_MEMBLOCK_PHYS_MAP启用了配置选项,则可以使用物理内存)。现在我们看到了另一个数据结构- memblock_type。让我们看一下它的定义:

struct memblock_type {unsigned long cnt;unsigned long max;phys_addr_t total_size;struct memblock_region *regions;
};

此结构提供有关内存类型的信息。它包含一些字段,这些字段描述了当前内存块内的内存区域数量,所有内存区域的大小,内存区域分配的数组的大小以及指向memblock_region结构数组的指针。memblock_region是描述存储区域的结构。它的定义是:

struct memblock_region {phys_addr_t base;phys_addr_t size;unsigned long flags;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAPint nid;
#endif
};

memblock_region 提供内存区域的基地址和大小以及一个flags字段,该字段可以具有以下值:

enum {MEMBLOCK_NONE    = 0x0,    /* No special request */MEMBLOCK_HOTPLUG    = 0x1,    /* hotpluggable region */MEMBLOCK_MIRROR    = 0x2,    /* mirrored region */MEMBLOCK_NOMAP    = 0x4,    /* don't add to kernel direct mapping */
};

memblock_region提供了一个整数字段-numa节点选择器(如果CONFIG_HAVE_MEMBLOCK_NODE_MAP启用了配置选项)。

从示意图上我们可以将其想象为:

+---------------------------+   +---------------------------+
|         memblock          |   |                           |
|  _______________________  |   |                           |
| |        memory         | |   |       Array of the        |
| |      memblock_type    |-|-->|      memblock_region      |
| |_______________________| |   |                           |
|                           |   +---------------------------+
|  _______________________  |   +---------------------------+
| |       reserved        | |   |                           |
| |      memblock_type    |-|-->|       Array of the        |
| |_______________________| |   |      memblock_region      |
|                           |   |                           |
+---------------------------+   +---------------------------+

这三种结构:memblockmemblock_typememblock_region在主Memblock。现在我们知道了,可以看一下Memblock的初始化过程。

内存块初始化

由于头文件include / linux / memblock.hmemblock中描述了所有API,因此这些功能的所有实现都在mm / memblock.c源代码文件中。让我们看一下源代码文件的顶部,我们将看到结构的初始化:memblock

struct memblock memblock __initdata_memblock = {.memory.regions        = memblock_memory_init_regions,.memory.cnt            = 1,.memory.max            = INIT_MEMBLOCK_REGIONS,.reserved.regions    = memblock_reserved_init_regions,.reserved.cnt        = 1,.reserved.max        = INIT_MEMBLOCK_REGIONS,#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP.physmem.regions    = memblock_physmem_init_regions,.physmem.cnt        = 1,.physmem.max        = INIT_PHYSMEM_REGIONS,
#endif.bottom_up            = false,.current_limit        = MEMBLOCK_ALLOC_ANYWHERE,
};

在这里,我们可以看到memblock结构的初始化,该结构的名称与structure-相同memblock。首先请注意__initdata_memblock。该宏的定义如下:

#ifdef CONFIG_ARCH_DISCARD_MEMBLOCK#define __init_memblock __meminit#define __initdata_memblock __meminitdata
#else#define __init_memblock#define __initdata_memblock
#endif

您可以看到它取决于CONFIG_ARCH_DISCARD_MEMBLOCK。如果启用此配置选项,则将在.init部分中放入内存块代码,并在启动内核后将其释放。

接下来,我们可以看到的初始化memblock_type memorymemblock_type reserved以及memblock_type physmem该领域memblock的结构。在这里,我们仅对memblock_type.regions初始化过程感兴趣。请注意,每个memblock_type字段均由的和数组初始化memblock_region

static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS] __initdata_memblock;
#endif

每个阵列包含128个内存区域。我们可以在INIT_MEMBLOCK_REGIONS宏定义中看到它:

#define INIT_MEMBLOCK_REGIONS   128

请注意,所有数组也都由__initdata_memblock我们在memblock结构初始化中已经看到的宏定义(如果您忘记了,请阅读上文)。

最后两个字段描述了bottom_up分配已禁用,当前内存块的限制为:

#define MEMBLOCK_ALLOC_ANYWHERE (~(phys_addr_t)0)

这是0xffffffffffffffff

在此步骤中,memblock结构的初始化已完成,我们可以看一下Memblock API。

Memblock API

好的,我们已经完成了memblock结构的初始化,现在我们可以看一下Memblock API及其实现。就像我上面说的,的实现memblock完全在mm / memblock.c中进行。要了解其memblock工作原理和实现方式,首先让我们看一下它的用法。在Linux内核中有几个地方使用了memblock。例如,让我们memblock_x86_fill从arch / x86 / kernel / e820.c中获取功能。该功能经过由提供的存储器映射E820,并增加了由内核保留的内存区域memblockmemblock_add功能。由于我们memblock_add首先遇到了函数,所以让我们从它开始。

此函数将物理基址和内存区域的大小作为参数,并将它们添加到中memblock。该memblock_add函数在其主体上没有做任何特殊的事情,只是调用了:

memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);

功能。我们传递存储块类型- memory,物理基地址和存储区域的大小,最大节点数(如果CONFIG_NODES_SHIFT未在配置文件1 << CONFIG_NODES_SHIFT中设置或已设置)和标记,这些节点的最大数目为1 。该memblock_add_range功能将新的存储区域添加到存储块。首先检查给定区域的大小,如果为零,则返回。之后,使用给定来memblock_add_range检查memblock结构中内存区域的存在memblock_type。如果没有内存区域,我们只memory_region用给定的值填充一个新值并返回(我们已经在linux内核内存管理器框架的第一篇文章中看到了它的实现)。如果memblock_type不为空,我们开始memblock使用给定的来向添加新的存储区域memblock_type

首先,我们使用以下内容获取内存区域的末尾:

phys_addr_t end = base + memblock_cap_size(base, &size);

memblock_cap_size调整sizebase + size不会溢出。它的实现非常简单:

static inline phys_addr_t memblock_cap_size(phys_addr_t base, phys_addr_t *size)
{return *size = min(*size, (phys_addr_t)ULLONG_MAX - base);
}

memblock_cap_size返回新的大小,它是给定大小和之间的最小值ULLONG_MAX - base

之后,我们有了新内存区域的结束地址,memblock_add_range检查与之前添加的内存区域的重叠和合并条件。将新的内存区域插入memblock包含两个步骤:

  • 将新存储区的不重叠部分添加为单独的区域;
  • 合并所有邻近地区。

我们将遍历所有已经存储的内存区域,并检查是否与新区域重叠:

    for (i = 0; i < type->cnt; i++) {struct memblock_region *rgn = &type->regions[i];phys_addr_t rbase = rgn->base;phys_addr_t rend = rbase + rgn->size;if (rbase >= end)break;if (rend <= base)continue;.........}

如果新的内存区域与已经存储在中的区域不重叠,则使用memblock将该区域插入到内存块中,这是第一步,我们将检查新区域是否适合该内存块并memblock_double_array以另一种方式调用:

while (type->cnt + nr_new > type->max)if (memblock_double_array(type, obase, size) < 0)return -ENOMEM;insert = true;goto repeat;

memblock_double_array给定区域数组的大小加倍。然后我们设置inserttrue并转到repeat标签。在第二步中,从repeat标签开始,我们经历相同的循环,并使用以下memblock_insert_region函数将当前存储区域插入到存储块中:

    if (base < end) {nr_new++;if (insert)memblock_insert_region(type, i, base, end - base,nid, flags);}

由于我们在第一步中设置inserttrue,因此现在memblock_insert_region将被调用。memblock_insert_region具有与将新区域插入空白区域时所见的几乎相同的实现memblock_type(请参见上文)。此函数获取最后一个内存区域:

struct memblock_region *rgn = &type->regions[idx];

并使用以下命令复制存储区memmove

memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));

之后,填充memblock_region新存储区域的字段基数,大小等,并增加的大小memblock_type。在执行结束时,在第二步中合并相邻兼容区域的memblock_add_range调用memblock_merge_regions

在第二种情况下,新的存储区域可以与已经存储的区域重叠。例如,我们已经region1memblock

0                    0x1000
+-----------------------+
|                       |
|                       |
|        region1        |
|                       |
|                       |
+-----------------------+

现在,我们要添加region2memblock用下面的基地址和大小:

0x100                 0x2000
+-----------------------+
|                       |
|                       |
|        region2        |
|                       |
|                       |
+-----------------------+

在这种情况下,使用以下命令将新存储区域的基地址设置为重叠区域的结束地址:

base = min(rend, end);

因此,0x1000在我们的情况下。并像在第二步中所做的那样插入它:

if (base < end) {nr_new++;if (insert)memblock_insert_region(type, i, base, end - base, nid, flags);
}

在这种情况下,我们插入overlapping portion(我们只插入较高的部分,因为较低的部分已经在重叠的内存区域中),然后插入其余部分,并用合并这些部分memblock_merge_regions。正如我上面所说的,memblock_merge_regions功能合并了相邻的兼容区域。它遍历给定的所有内存区域memblock_type,获取两个相邻的内存区域- type->regions[i]type->regions[i + 1]并检查这些区域具有相同的标志,属于同一节点并且第一区域的结束地址不等于该区域的基址。第二区域:

while (i < type->cnt - 1) {struct memblock_region *this = &type->regions[i];struct memblock_region *next = &type->regions[i + 1];if (this->base + this->size != next->base ||memblock_get_region_node(this) !=memblock_get_region_node(next) ||this->flags != next->flags) {BUG_ON(this->base + this->size > next->base);i++;continue;}

如果这些条件都不成立,我们将用下一个区域的大小更新第一个区域的大小:

this->size += next->size;

当我们用下一个存储区的大小更新第一个存储区的大小时,我们next使用以下memmove函数将在()存储区之后的所有存储区向后移一个索引:

memmove(next, next + 1, (type->cnt - (i + 2)) * sizeof(*next));

memmove这里移动分别位于后各地区next区域的基址next区域。最后,我们只减少属于的内存区域的数量memblock_type

type->cnt--;

之后,我们将两个内存区域合并为一个:

0                                             0x2000
+------------------------------------------------+
|                                                |
|                                                |
|                   region1                      |
|                                                |
|                                                |
+------------------------------------------------+

随着我们减少具有某种类型的内存块中的this区域数,该区域的大小增加,并且将位于该区域之后的所有next区域移至其位置。

就这样。这是memblock_add_range功能工作的整体原理。

还有一个memblock_reserve功能与相同memblock_add,但有一个区别。它存储memblock_type.reserved在内存块中而不是memblock_type.memory

当然,这不是完整的API。Memblock提供的API不仅用于添加memoryreserved存储区域,还提供:

  • memblock_remove-从内存块中删除内存区域;
  • memblock_find_in_range-查找给定范围内的空闲区域;
  • memblock_free-释放内存块中的内存区域;
  • for_each_mem_range-遍历内存块区域。

还有很多....

获取有关内存区域的信息

Memblock还提供了一个API,用于获取有关中的已分配内存区域的信息memblock。它分为两部分:

  • get_allocated_memblock_memory_regions_info-获取有关内存区域的信息;
  • get_allocated_memblock_reserved_regions_info-获取有关保留区域的信息。

这些功能的实现很容易。让我们来看get_allocated_memblock_reserved_regions_info例如:

phys_addr_t __init_memblock get_allocated_memblock_reserved_regions_info(phys_addr_t *addr)
{if (memblock.reserved.regions == memblock_reserved_init_regions)return 0;*addr = __pa(memblock.reserved.regions);return PAGE_ALIGN(sizeof(struct memblock_region) *memblock.reserved.max);
}

首先,此功能检查memblock包含保留的内存区域。如果memblock不包含保留的内存区域,则仅返回零。否则,我们将保留内存区域数组的物理地址写入给定地址,并返回分配的数组的对齐大小。请注意,有PAGE_ALIGN用于对齐的宏。实际上,这取决于页面的大小:

#define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)

实现的get_allocated_memblock_memory_regions_info功能是相同的。它只有一个区别,memblock_type.memory用来代替memblock_type.reserved

Memblock调试

memblock_dbgmemblock实现中有许多调用。如果将memblock=debug选项传递给内核命令行,则将调用此函数。实际上memblock_dbg只是一个宏,它扩展为printk

#define memblock_dbg(fmt, ...) \if (memblock_debug) printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

例如,您可以在memblock_reserve函数中看到此宏的调用:

memblock_dbg("memblock_reserve: [%#016llx-%#016llx] flags %#02lx %pF\n",(unsigned long long)base,(unsigned long long)base + size - 1,flags, (void *)_RET_IP_);

您将看到类似以下内容:

Memblock在debugfs中也有支持。如果您在其他架构上运行内核,则X86无法访问:

  • / sys /内核/调试/内存块/内存
  • / sys /内核/调试/内存块/保留
  • / sys /内核/调试/内存块/ physmem

获取memblock内容转储。

链接

  • e820
  • 努玛
  • 调试文件
  • linux内核内存管理器框架的第一次接触

相关阅读

《Linux内存管理:memblock》

《Linux内存管理:memblock》

《linux内存管理:kmap、vmap、ioremap》

《Linux内存管理:Fixmaps(固定映射地址)和ioremap》

《ARM SMMU原理与IOMMU技术(“VT-d” DMA、I/O虚拟化、内存虚拟化)》

《Linux内存管理:分页机制》

《Linux内存管理:内存描述之内存节点node》

《Linux内存管理:内存描述之内存区域zone》

《Linux内存管理:内存描述之内存页面page》

《Linux内存管理:内存描述之高端内存》

《内存管理:Linux Memory Management:MMU、段、分页、PAE、Cache、TLB》

《Linux内存管理:MMU那些事儿》

Linux内存管理:memblock(引导期间管理内存区域)相关推荐

  1. 启动期间的内存管理之初始化过程概述----Linux内存管理(九)

    转载地址:https://blog.csdn.net/gatieme/article/details/52403148 日期 内核版本 架构 作者 GitHub CSDN 2016-09-01 Lin ...

  2. Linux内核内存管理(1):内存块 - memblock

    Linux内核内存管理 内存块 - memblock rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容. 1. 简介 内存管理是操作系统内核中最复杂的部分之 ...

  3. Linux 内存管理篇(2)内核初始化与内存管理启用

    前言 继内存寻址之后, 本篇开始介绍Linux内核地址空间初始化过程. 通过内存寻址篇我们知道, Linux 系统运行过程中位于保护模式,系统必须要是用MMU来完成地址寻址, 这就依赖于段表跟页表. ...

  4. Linux操作系统原理与应用04:内存管理

    目录 1. Linux内存管理概述 1.1 内存的层次结构 1.2 虚拟内存概述 1.2.1 虚拟内存基本思想 1.2.2 进程虚拟地址空间 1.3 内核空间到物理空间的映射 1.3.1 内核空间的线 ...

  5. 深入浅出内存管理-memblock

    memblock 介绍 memblock 内存管理机制主要用于Linux Kernel 启动阶段(kernel启动 -> kernel 通用内存管理初始化完成.) 或者可以认为free_init ...

  6. Linux学习总结02——内存管理——Linux在X86上的虚拟内存管理

    Linux内存管理之二:Linux在X86上的虚拟内存管理 本文档来自网络,并稍有改动. 前言 Linux支持很多硬件运行平台,常用的有:Intel X86,Alpha,Sparc等.对于不能够通用的 ...

  7. 【Linux 内核 内存管理】内存管理架构 ② ( 用户空间内存管理 | malloc | ptmalloc | 内核空间内存管理 | sys_brk | sys_mmap | sys_munmap)

    文章目录 一.用户空间内存管理 ( malloc / free / ptmalloc / jemalloc / tcmalloc ) 二.内核空间内存管理 1.内核内存管理系统调用 ( sys_brk ...

  8. Linux内核笔记--内存管理之用户态进程内存分配

    内核版本:linux-2.6.11 Linux在加载一个可执行程序的时候做了种种复杂的工作,内存分配是其中非常重要的一环,作为一个linux程序员必然会想要知道这个过程到底是怎么样的,内核源码会告诉你 ...

  9. Linux内存管理:CMA(连续内存分配)(DMA)

    目录 什么是CMA 数据结构 CMA区域 cma_areas 的创建 dts方式 command line方式 将CMA区域添加到Buddy System CMA分配 <Linux内存管理:什么 ...

最新文章

  1. Unicode 和 UTF-8 有何区别?
  2. 对比vector、deque、list的优缺点
  3. 前端那些年--npm
  4. 联通研究院处长王志军:Hadoop在电信业大数据的应用
  5. 小李飞刀:用python刷题ing....
  6. osm 搭建离线地图_使用离线OSM离线OpenLayers Web应用程序
  7. eclipse断点Source not found解决方案1,2,3
  8. 今天和朋友去参观一家做电商创业公司
  9. 小米靠着“便宜”在手机市场中占有一席之地
  10. 创建窗口,输入一个无符号整数,输出其对应的二进制数
  11. 【log4cpp_学习】2_log4cpp配置文件的使用
  12. 如何快速从入门到精通linux
  13. python中seek方法_python文件操作及seek偏移详解
  14. ffmpeg 命令行多视频轨/音频轨合并,播放
  15. 『Nginx』Nginx部署Https 443转发
  16. 喜欢Photoshop的来看看啊
  17. 【HTML5+CSS】美观的button设置
  18. 2022-02-11 学习记录:通过CSS3的clip-path实现多边形
  19. 范里安中级微观经济学(第9版)分析笔记和课后习题答案解析-完整版 范里安《微观经济学:现代观点》(第9版)笔记和课后习题详解!
  20. 用div来代替table

热门文章

  1. 错误记录(九)Could not obtain transaction-synchronized Session for current thread
  2. 用DataGrip生成导出整个库数据的方法
  3. 剑指offer48-最长不含重复字符的子字符串(双指针经典)
  4. JVM学习-垃圾回收调优
  5. MySQL学习-子查询及limit分页
  6. Spring框架----Spring的IOC
  7. 【共读Primer】52.[6.3]返回类型和return语句--返回数组指针 Page205
  8. appium启动APP配置参数:
  9. 面向对象思想----不看懊悔!
  10. ListView setOnItemClickListener无法响应点击事件解决