本文主要是介绍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 |
| | | |
+---------------------------+ +---------------------------+
这三种结构:memblock
,memblock_type
并memblock_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 memory
,memblock_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,并增加了由内核保留的内存区域memblock
与memblock_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
调整size
是base + 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
给定区域数组的大小加倍。然后我们设置insert
为true
并转到repeat
标签。在第二步中,从repeat
标签开始,我们经历相同的循环,并使用以下memblock_insert_region
函数将当前存储区域插入到存储块中:
if (base < end) {nr_new++;if (insert)memblock_insert_region(type, i, base, end - base,nid, flags);}
由于我们在第一步中设置insert
为true
,因此现在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
。
在第二种情况下,新的存储区域可以与已经存储的区域重叠。例如,我们已经region1
在memblock
:
0 0x1000
+-----------------------+
| |
| |
| region1 |
| |
| |
+-----------------------+
现在,我们要添加region2
到memblock
用下面的基地址和大小:
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不仅用于添加memory
和reserved
存储区域,还提供:
- 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_dbg
memblock实现中有许多调用。如果将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(引导期间管理内存区域)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!