【linux 内存管理】memblock算法简单梳理

最近看了一份博客介绍memblock的算法实现,整理下温故而知新。

引:Linux-3.14.12内存管理笔记【系统启动阶段的memblock算法(1)】-Jean_Leo-ChinaUnix博客

memblock算法是linux内核初始化阶段的一个内存分配器(它取代了原来的bootmem算法),实现较为简单。负责page allocator初始化之前的内存管理和分配请求。

    分析memblock算法,可以从几点入手:

1、 memblock算法初始化;

2、 memblock算法管理内存的申请和释放;

    memblock算法前的准备:

    前面已经分析了linux系统在初始化的过程中,使用int 15中断探知了机器的内存分布图(e820图),其数据是存储在boot_params.e820_map里面,这里面是没有经过整理的数据,杂乱无章,毕竟BIOS没有责任做整理内存的事情,所以这部分事情由系统来实现。那么看一下linux如何实现这部分功能的,这部分功能是在setup_memory_map里面实现的。主要就是为了把通过BIOS中断探测到的内存布局信息boot_params.e820_map做整合处理,完了转存到变量e820中。

先来看打印出所有e820探测的物理内存:

start_kernel()->setup_arch->setup_memory_map(),打印信息如下:

【linux 内存管理】memblock算法简单梳理_第1张图片

这里的地址范围是物理地址空间的范围,并不是实际上的内存条上的物理地址,这里是把物理内存条上的地址映射到这个地址空间,也就是我们访问的物理地址 physical addrsss。

  • Usable:已经被映射到物理地址空间的物理地址。
  • Reserved:这些区间是没有被映射到任何地方,不能当作RAM来使用,但是kernel可以决定将这些区间映射到其他地方,比如PCI设备。通过检查/proc/iomem这个虚拟文件,就可以知道这些reserved的空间,是如何进一步分配给不同的设备来使用了。
  • ACPI data:映射到用来存放ACPI数据的RAM空间,操作系统应该将ACPI Table读入到这个区间内。
  • ACPI NVS:映射到用来存放ACPI数据的非易失性存储空间,操作系统不能使用。
  • Unusable:表示检测到发生错误的物理内存。这个在上面例子里没有,因为比较少见。

设备启动后除了一些特殊的地址范围和一些reserverd的内存,其余的usable是我们linux系统可直接使用的,也就是start_kernel()可使用的内存大小是usable的内存。

这个是设备时16G物理内存大小,以上usable计算出来的物理内存大小大概为15.902G,也就是有100M内存被其他占用,但是占用不多。

以上是memblock初始化的准备工作。

一、初始化

#define INIT_MEMBLOCK_REGIONS    128

【linux 内存管理】memblock算法简单梳理_第2张图片

它将所有状态都保存在一个全局变量memblock中,算法的初始化以及内存的申请释放都是在将内存块的状态做变更。

先介绍结构体 struct memblock 

【linux 内存管理】memblock算法简单梳理_第3张图片

bottom_up:用来表示分配器分配内存是自低地址(低地址指的是内核映像尾部,下同)向高地址还是自高地址向低地址来分配的;

current_limit:用来表示用来限制memblock_alloc()和memblock_alloc_base(..., MEMBLOCK_ALLOC_ACCESSIBLE)的内存申请;

memory:表示可用可分配的内存;

reserved:表示已经分配出去了的内存;                              

memory和reserved是很关键的一个数据结构,memblock算法的内存初始化和申请释放都是围绕着它们转。

往下看看memory和reserved的结构体struct memblock_type定义:

【linux 内存管理】memblock算法简单梳理_第4张图片

cnt和max分别表示当前状态(memory/reserved)的内存块可用数和可支持的最大数,total_size则表示当前状态(memory/reserved)的空间大小(也就是可用的内存块信息大小总和),而regions则是用于保存内存块信息的结构(包括基址、大小和标记等):

【linux 内存管理】memblock算法简单梳理_第5张图片

总的来说以上的关系图:

【linux 内存管理】memblock算法简单梳理_第6张图片

    第一个图片可以看出全局变量memblock已经对部分数据进行了初始化:它初始化了部分成员,表示内存申请自高地址向低地址,且current_limit设为~0,即0xFFFFFFFF,同时通过全局变量定义为memblock的算法管理中的memory和reserved准备了内存空间。

    接下来分析一下memblock算法初始化,其初始化函数为memblock_x86_fill(),初始化调用栈位置:

start_kernel()                                 #/init/main.c

    └->setup_arch()                        #/arch/x86/kernel/setup.c

        └->memblock_x86_fill() 

【linux 内存管理】memblock算法简单梳理_第7张图片

【linux 内存管理】memblock算法简单梳理_第8张图片

遍历e820中的每一个内存entry块,将E820_RAM即usable的内存调用memblock_add添加。

接下来看memblock_add()函数:

【linux 内存管理】memblock算法简单梳理_第9张图片

【file:/mm/memblock.c】
/**
 * memblock_add_region - add new memblock region
 * @type: memblock type to add new region into
 * @base: base address of the new region
 * @size: size of the new region
 * @nid: nid of the new region
 * @flags: flags of the new region
 *
 * Add new memblock region [@base,@base+@size) into @type. The new region
 * is allowed to overlap with existing ones - overlaps don't affect already
 * existing regions. @type is guaranteed to be minimal (all neighbouring
 * compatible regions are merged) after the addition.
 *
 * RETURNS:
 * 0 on success, -errno on failure.
 */
static int __init_memblock memblock_add_region(struct memblock_type *type,
                phys_addr_t base, phys_addr_t size,
                int nid, unsigned long flags)
{
    bool insert = false;
    phys_addr_t obase = base;
    phys_addr_t end = base + memblock_cap_size(base, &size);
    int i, nr_new;
 
    if (!size)
        return 0;
 
    /* special case for empty array */
    if (type->regions[0].size == 0) {
        WARN_ON(type->cnt != 1 || type->total_size);
        type->regions[0].base = base;
        type->regions[0].size = size;
        type->regions[0].flags = flags;
        memblock_set_region_node(&type->regions[0], nid);
        type->total_size = size;
        return 0;
    }
repeat:
    /*
     * The following is executed twice. Once with %false @insert and
     * then with %true. The first counts the number of regions needed
     * to accomodate the new area. The second actually inserts them.
     */
    base = obase;
    nr_new = 0;
 
    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;
        /*
         * @rgn overlaps. If it separates the lower part of new
         * area, insert that portion.
         */
        if (rbase > base) {
            nr_new++;
            if (insert)
                memblock_insert_region(type, i++, base,
                               rbase - base, nid,
                               flags);
        }
        /* area below @rend is dealt with, forget about it */
        base = min(rend, end);
    }
 
    /* insert the remaining portion */
    if (base < end) {
        nr_new++;
        if (insert)
            memblock_insert_region(type, i, base, end - base,
                           nid, flags);
    }
 
    /*
     * If this was the first round, resize array and repeat for actual
     * insertions; otherwise, merge and return.
     */
    if (!insert) {
        while (type->cnt + nr_new > type->max)
            if (memblock_double_array(type, obase, size) < 0)
                return -ENOMEM;
        insert = true;
        goto repeat;
    } else {
        memblock_merge_regions(type);
        return 0;
    }
}

分析一下memblock_add_region()函数的行为流程:

1、 如果memblock算法管理内存为空的时候,则将当前空间添加进去;

2、 不为空的情况下,则先检查是否存在内存重叠的情况,如果有的话,则剔除重叠部分,然后将其余非重叠的部分添加进去;

3、 如果出现region[]数组空间不够的情况,则通过memblock_double_array()添加新的region[]空间;

4、 最后通过memblock_merge_regions()把紧挨着的内存合并了。

现在很明了,可以看到其功能作用是把e820图里面的内存布局转换到memblock管理算法当中的memblock.memory进行管理,表示该内存可用,e820探测到多少个usable内存块,就对应多少个region,最大128个,region地址由低到高排序,各region没有重叠

memblock_trim_memory()对各region的start和end修建成PAGE_SIZE对齐   。

 memblock_dump_all()打印memblock信息。

二、membloack申请

memblock_alloc函数:

【linux 内存管理】memblock算法简单梳理_第10张图片

【linux 内存管理】memblock算法简单梳理_第11张图片

这里两个重要的函数显现出来,一个是memblock_find_in_range_node()一个memblock_reserve()。

memblock_find_in_range_node()->__memblock_find_range_top_down()函数:

    __memblock_find_range_top_down()通过使用for_each_free_mem_range_reverse宏封装调用__next_free_mem_range_rev()函数,此函数逐一将memblock.memory里面的内存块信息提取出来与memblock.reserved的各项信息进行检验,确保返回的this_start和this_end不会与reserved的内存存在交叉重叠的情况。然后通过clamp取中间值,判断大小是否满足,满足的情况下,将自末端向前(因为这是top-down申请方式)的size大小的空间的起始地址(前提该地址不会超出this_start)返回回去。至此满足要求的内存块算是找到了。

    接下来是另一重要好函数memblock_reserve():

【linux 内存管理】memblock算法简单梳理_第12张图片

可以看到memblock_reserve_region()是通过memblock_add_region()函数往memblock.reserved里面添加内存块信息。

三、释放

memblock_free()实现:

【linux 内存管理】memblock算法简单梳理_第13张图片

其主要功能是将指定下标索引的内存项从memblock.reserved管理结构中移除。

memblock管理算法将可用可分配的内存在memblock.memory进行管理起来,已分配的内存在memblock.reserved进行管理,只要内存块加入到memblock.reserved里面就表示该内存已经被申请占用了。所以有个关键点需要注意,内存申请的时候,仅是把被申请到的内存加入到memblock.reserved中,并不会在memblock.memory里面有相关的删除或改动的操作,这也就是为什么申请和释放的操作都集中在memblock.reserved的原因了。这个算法效率并不高,但是这是合理的,毕竟在初始化阶段没有那么多复杂的内存操作场景,甚至很多地方都是申请了内存做永久使用的。

   也就是说申请memblock.memory永远都是管理设备所有可识别的内存也就是usable对应的内存。尽管添加memblock.reserved中也不会从memblock.memory清除。

三、一些宏的简单介绍

for_each_mem_pfn_range,遍历memblock的memory帧的范围。

#define for_each_mem_pfn_range(i, nid, p_start, p_end, p_nid)		\
	for (i = -1, __next_mem_pfn_range(&i, nid, p_start, p_end, p_nid); \
	     i >= 0; __next_mem_pfn_range(&i, nid, p_start, p_end, p_nid))
void __init_memblock __next_mem_pfn_range(int *idx, int nid,
				unsigned long *out_start_pfn,
				unsigned long *out_end_pfn, int *out_nid)
{
	struct memblock_type *type = &memblock.memory; //遍历memblock的memory的帧
	struct memblock_region *r;

	while (++*idx < type->cnt) {
		r = &type->regions[*idx];

		if (PFN_UP(r->base) >= PFN_DOWN(r->base + r->size))
			continue;
		if (nid == MAX_NUMNODES || nid == r->nid)
			break;
	}
	if (*idx >= type->cnt) {
		*idx = -1;
		return;
	}

	if (out_start_pfn)
		*out_start_pfn = PFN_UP(r->base);
	if (out_end_pfn)
		*out_end_pfn = PFN_DOWN(r->base + r->size);
	if (out_nid)
		*out_nid = r->nid;
}

for_each_free_mem_range,从注解中可以看到,memory-reserved的帧,即未被分配出去的帧

 /* Walks over free (memory && !reserved) areas of memblock.  Available as
 * soon as memblock is initialized.
 */
#define for_each_free_mem_range(i, nid, flags, p_start, p_end, p_nid)	\
	for_each_mem_range(i, &memblock.memory, &memblock.reserved,	\
			   nid, flags, p_start, p_end, p_nid)

你可能感兴趣的:(内存管理,linux,算法,运维)