Linux内存管理:memblock(引导期间管理内存区域)

2023-10-14 07:18

本文主要是介绍Linux内存管理:memblock(引导期间管理内存区域),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

介绍

内存块

内存块初始化

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(引导期间管理内存区域)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/208959

相关文章

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

SpringBoot集成XXL-JOB实现任务管理全流程

《SpringBoot集成XXL-JOB实现任务管理全流程》XXL-JOB是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过SpringBoot项目,使用RestTempl... 目录一、前言二、项目结构简述三、Maven 依赖四、Controller 代码详解五、Service

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

Python内存优化的实战技巧分享

《Python内存优化的实战技巧分享》Python作为一门解释型语言,虽然在开发效率上有着显著优势,但在执行效率方面往往被诟病,然而,通过合理的内存优化策略,我们可以让Python程序的运行速度提升3... 目录前言python内存管理机制引用计数机制垃圾回收机制内存泄漏的常见原因1. 循环引用2. 全局变