Memblock – (1)

특징

  • 부트업타임에만 사용하는 기본적인 메모리 관리자로 하이 레벨 코드(본격적인 buddy/slab등의 memory allocator)로 전환전에 사용한다.
    • 기존 bootmem으로부터 커널 v2.6.35에서 memblock이 추가되었고 많은 아키텍처들이 memblock으로 이전하였다.
    • 부트업 프로세스가 끝나면 memblock을 사용하지 않았었는데 memory hotplug 옵션을 사용하는 경우에는 계속사용하게 한다.
  • LMB(Logical Memory Block)라고 알려져 있다.
  • Peter Bergner, IBM Corp. (June 2001)
  • 다음 3가지 memblock 타입으로 나뉘어 있다.
    • memory memblock type 128개
      • 사용(할당) 가능한 메모리 영역
    • reserved memblock type 128개
      • 용도가 정해져서 사용(할당)할 수  없는 영역
    • physmem memblock type 4개
      • 물리 메모리 영역

CONFIG_HAVE_MEMBLOCK_PHYS_MAP

  • 2014년 1월 kernel 3.16-rc1에서 추가된 옵션
  • 사용가능한 물리메모리의 영역을 4개(INIT_PHYSMEM_REGIONS)까지 추가할 수 있으며 입력된 영역은 수정되지 않는다.
  • 다른 memblock 구조체와 다르게 메모리의 전체 range를 담고 있다.

CONFIG_ARCH_DISCARD_MEMBLOCK

  • 커널 메모리 절약을 위해 memblock을 부트 프로세스 이후 메모리에서 제거(DISCARD)시킬 수 있게 vmlinux.lds.h 에서 제어할 수 있는 옵션
  • 이 옵션이 사용되면 __initdata_memblock 섹션 매크로는 .meminit.data 섹션을 사용하게 컴파일러에게 지시한다.
  • 이 옵션을 사용하지 않으면 memblock 섹션은 default인 .data에 섹션에 위치하므로 메모리에 항상 상주한다.

include/linux/memblock.h

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

 

CONFIG_MEMORY_HOTPLUG

  • CONFIG_ARCH_DISCARD_MEMBLOCK 옵션을 사용하는 경우에만 다음과 같이 동작한다.
  • CONFIG_MEMORY_HOTPLUG 옵션을 사용하지 않는 경우 부트 프로세스가 끝나고 나서 memblock을 더 이상 사용하지 않도록 한다.
    • .init.data 섹션의 처음 부분에 .meminit.data를 위치하여 삭제되게 한다.
  • CONFIG_MEMORY_HOTPLUG를 사용하는 경우 부트 프로세스가 끝나더라도 계속 사용할 수 있도록 보존 한다.
    • .data 섹션에 .meminit.data가 위치하도록하여 삭제되지 않고 계속 사용될 수 있도록 한다.

include/linux/memblock.h

#if defined(CONFIG_MEMORY_HOTPLUG)
#define MEM_KEEP(sec)    *(.mem##sec)
#define MEM_DISCARD(sec)
#else
#define MEM_KEEP(sec)
#define MEM_DISCARD(sec) *(.mem##sec)
#endif
  • .meminit.data는 부트 후에도 삭제되지 않는 DATA_DATA에 들어간다.
/* .data section */
#define DATA_DATA                                                       \
        *(.data)                                                        \
        *(.ref.data)                                                    \
        *(.data..shared_aligned) /* percpu related */                   \
        MEM_KEEP(init.data)                                             \
        MEM_KEEP(exit.data)                                             \
        *(.data.unlikely) 
  • .meminit.data는 부트 후 삭제되는 영역인 INIT_DATA에 들어간다.
/* init and exit section handling */
#define INIT_DATA                                                       \
        *(.init.data)                                                   \
        MEM_DISCARD(init.data)                                          \
        KERNEL_CTORS()

초기화

  • 전역 구조체명 memblock은 컴파일 타임에 아래와 같이 초기화된다.
    • cnt 변수는 각 영역의 갯수로 초기 1로 설정된다.
      • 영역 데이터가 하나도 없어도 기본 1로 설정되어 있으며, 영역 데이터를 처음 하나 추가하는 경우 cnt는 변동되지 않는다. 그 후 부터는 추가할 때마다 1씩 증가한다.
    • bottom_up 변수는 false로 되어 있어 allocation 요청 시 상단에서 하단으로 검색하여 free 공간을 찾는 방향성을 갖는다.
    • current_limit은 초기에 MEMBLOCK_ALLOC_ANYWHERE (~(phys_addr_t) 0)로 설정하고 설정될 수 있는 값은 다음과 같다.
      • MEMBLOCK_ALLOC_ANYWHERE (~(phys_addr_t) 0):
        • 물리 주소의 최대치
      • MEMBLOCK_ALLOC_ACCESSBLE (0):
        • 물리 메모리 주소의 최대치
      • 입력한 주소 값으로 최대 한계 제한

mm/memblock.c

struct memblock memblock __initdata_memblock = {
        .memory.regions         = memblock_memory_init_regions,
        .memory.cnt             = 1,    /* empty dummy entry */
        .memory.max             = INIT_MEMBLOCK_REGIONS,

        .reserved.regions       = memblock_reserved_init_regions,
        .reserved.cnt           = 1,    /* empty dummy entry */
        .reserved.max           = INIT_MEMBLOCK_REGIONS,

#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
        .physmem.regions        = memblock_physmem_init_regions,
        .physmem.cnt            = 1,    /* empty dummy entry */
        .physmem.max            = INIT_PHYSMEM_REGIONS,
#endif

        .bottom_up              = false,
        .current_limit          = MEMBLOCK_ALLOC_ANYWHERE,
};
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

 

주요 구조체

struct memblock

include/linux/memblock.h

struct memblock {
        bool bottom_up;  	/* is bottom up direction? */
        phys_addr_t current_limit;
        struct memblock_type memory;
        struct memblock_type reserved;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
        struct memblock_type physmem;
#endif
};

 

struct memblock_type

struct memblock_type {
        unsigned long cnt;      /* number of regions */
        unsigned long max;      /* size of the allocated array */
        phys_addr_t total_size; /* size of all regions */
        struct memblock_region *regions;
};

 

struct memblock_region

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

 

  • 초기에 memblock 영역은 아래의 타입과 영역 배열로 등록되어 있다.

 

memblock1

memblock_add()

memblock2

memblock 추가 6개 예)
  • 추가할 영역이 기존 memblock 영역과 겹치는 경우 일단 겹쳐지지 않는 영역으로 쪼개어 추가한 후 나중에 인접 블럭들끼리 최종 merge한다.
  • 시나리오 6개는 총 6개의 memblock 영역에 insert되는 memblock 수와 최종 merge된 후 남게되는 memblock 수가 아래에 나타냈다.

memblock3

  • NUMA 시스템은 노드가 하나 이상이므로 MAX_NUMNODES를 사용하는데 UMA 시스템인 경우 노드는 하나이므로 MAX_NUMNODES는 1 이 된다. (라즈베리파이2: MAX_NUMNODES=1)

mm/memblock.c

int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
        return memblock_add_range(&memblock.memory, base, size, 
                                   MAX_NUMNODES, 0);
}

 

memblock_add_range()

  • 새로운 memblock 영역을 추가하는데 기존 memblock 영역들과 중복되는 곳은 중복되지 않게 사이 사이에 끼워 넣는다. 마지막에 경계가 붙어 있는 memblock 들을 merge 한다. return 값은 항상 0이다.
  • 지정된 memblock 타입(memory, reserved, physmem)에 memblock 영역을 추가하는 경우 예)
    • 기존 memblock 4개 (base=0x0, size=0x1000), (base=0x2000, size=0x1000), (base=0x4000, size=0x1000), (base=0x6000, size=0x1000)
    • 추가 memblock (base=0x500, size=0x5000)
    • 최종 memblock 2개 (base=0x0, size=0x5500), (base=0x6000, size=0x1000)
  • memblock_cap_size()
    • 추가할 memblock 영역이 시스템의 최대 주소를 넘어가는 경우 overflow 된 만큼 잘라낸다.
  • 사이즈가 0인 경우 함수를 종료한다.
  • if (type->regions[0].size == 0)
    • 처음 이 함수가 호출된 경우 비어 있기 때문에 중복 체크 없이 첫 memblock 영역에 대한 설정을 하고 함수를 종료한다.
  • repeat: 레이블
    • repeat: 레이블을 통해 한 번 반복되는데 첫 번째 수행 시 first round라고 하고 다음 수행 시 second 라운드라고 할 때
    • insert 라는 플래그를 사용하여 insert가 true인 경우 second round를 의미한다.
    • first round에서는 루프를 돌때 끼워 넣어야 할 memblock 수를 체크할 목적으로 카운터(nr_new)만 증가시킨다.
    • second round에서는 루프를 돌며 실제로 memblock을 곳곳에 인접 memblock과 중복되지 않게 끼워넣는다.
  • 다음은 루프내에서 인접한 memblock을 체크하는 조건 등에 대한 설명이다.
    • 조건 A) if (rbase >= end)
      • new memblock 영역이 비교하는 memblock 영역의 하단에 위치하여 겹치지 않으므로 더 위의 memblock 루프를 진행할 필요가 없어서 루프를 빠져나간다.
    • 조건 B) if (rend <= base)
      • new memblock 영역이 비교하는 memblock 영역의 상부에 위치하여 겹치지 않으므로 더 위의 memblock 루프로 continue 한다.
    • 조건 C) if (rbase > base)
      • new memblock 영역이 비교하는 memblock 영역의 하단과 겹치므로 끼워 넣는데 그 영역은 비교하는 memblock 영역과 겹치지 않는 영역의 크기만큼이다.
    • base = 끼워 넣어야 할 때를 대비하여 끼워 넣을 memblock 영역의 시작 주소를 미리 대입해 놓는다.
  • 조건 D) if (base < end)
    • 루프 종료 후 new memblock 영역의 끝 부분이 상부로 돌출되어 남아 있는 경우 그 돌출된 부분만을 memblock으로 추가한다.
  • if (!insert)
    • first round의 경우 기존 memblock 들과 끼워 넣은 memblock 들의 합이 총 최대 관리 갯수를 넘어가는 경우 만큼
      • memblock 공간(배열 수)을 두 배 더 크게 넗히기 위해 다음 함수를 반복 호출한다.
    • memblock_double_array()
      • 관리 영역의 크기(초기 타입별로 각각 128/128/4개)를 2배로 확장시킨다.
      • 초기 관리 영역은 처음에  전역 static 공간에 만들어져 있기 때문에 그 영역을 확장 할 수 없다. 따라서 새로운 할당 영역을 memblock_add() 함수가 요청한 할당 영역을 피해서 검색하여 찾은 곳에 관리 영역을 배치한다.
      • 새로운 관리 영역을 사용하게 되면 그 전에 사용했던 영역을 free 시킨다.
        • 처음 static 공간에 할당된 관리 영역은 free 시키지 못하므로 그냥 버린다.
        • 두 번째 할당한 공간이 memblock이면 memblock_free() 시키고 slab 등의 정규 메모리 할당자를 사용하였으면 kfree()를 사용하여 사용하지 않게된 기존 관리 영역을 free 시킨다.
      • second round에서 끼워 넣은 memblock들과 인접한 memblock 들이 붙어 있는 경우 다음 명령으로 merge 한다.
        • memblock_double_array()

mm/memblock.c

/**
 * memblock_add_range - 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.
 */
int __init_memblock memblock_add_range(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_insert_region()

  • new 영역을 지정된 타입의 지정된 index memblock 위치에 끼워(insert) 넣는다.
  • memmove()
    • 지정된 index의 memblock부터 마지막 memblock 까지 한 칸씩 뒤로 복사한다.
  • memblock_set_region_node()
    • NUMA 아키텍처를 위해 노드 id를 지정한다. (CONFIG_HAVE_MEMBLOCK_NODE_MAP 옵션)
  • memblock 타입 정보에는 카운터를 증가 시키고 증가된 size도 더한다.

mm/memblock.c()

/**
 * memblock_insert_region - insert new memblock region
 * @type:       memblock type to insert into
 * @idx:        index for the insertion point
 * @base:       base address of the new region
 * @size:       size of the new region
 * @nid:        node id of the new region
 * @flags:      flags of the new region
 *
 * Insert new memblock region [@base,@base+@size) into @type at @idx.
 * @type must already have extra room to accomodate the new region.
 */
static void __init_memblock memblock_insert_region(struct memblock_type *type,
                                                   int idx, phys_addr_t base,
                                                   phys_addr_t size,
                                                   int nid, unsigned long flags)
{
        struct memblock_region *rgn = &type->regions[idx];

        BUG_ON(type->cnt >= type->max);
        memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));
        rgn->base = base;
        rgn->size = size;
        rgn->flags = flags;
        memblock_set_region_node(rgn, nid);
        type->cnt++;
        type->total_size += size;
}

 

memblock_double_array()

  • memblock이 위치한 배열이 작아서 확장이 필요할 때 호출되면 2배를 키울 수 있다.
  • 단 memblock_allow_resize()가 호출되어 전역변수 memblock_can_resize에 1이 설정된 후 부터 가능 리사이즈가 가능하다.
  • 새로 관리할 영역을 기존 관리 영역보다 2배큰 크기를 할당받는다.
    • 정식 memory allocator인 slab을 사용할 수 있는 단계에서는 kmalloc()으로 메모리를 할당받는다.
    • slab을 사용하지 못하는 경우에는 memblock_add() 함수에서 사용 요청한 영역을 피해서 memblock_find_in_range()로 새로 만들 관리 영역을 사용할 수 있는 공간을 찾아온다.
  • 새로 할당 받은 관리 영역 시작 주소에 기존 memblock 영역들을 모두 복사하고 복사되지 않은 빈곳은 0으로 초기화한다. 또한 max_cnt를 기존 값의 2배로 변경한다.
  • 기존 관리 영역을 해제한다.
    • 초기 관리 영역은 전역 static 영역에 있으므로 삭제할 수 없어서 그냥 버린다.
    • 삭제할 관리 영역이 초기 영역이 아니면 할당자 종류에 따라 처리한다.
      • 기존에 slab을 사용중이었으면 kfree()로 기존 영역을 해제한다.
      • slab을 사용하지 않은 경우 memblock_free()로 기존 영역을 해제한다.

mm/memblock.c

/**
 * memblock_double_array - double the size of the memblock regions array
 * @type: memblock type of the regions array being doubled
 * @new_area_start: starting address of memory range to avoid overlap with
 * @new_area_size: size of memory range to avoid overlap with
 *
 * Double the size of the @type regions array. If memblock is being used to
 * allocate memory for a new reserved regions array and there is a previously
 * allocated memory range [@new_area_start,@new_area_start+@new_area_size]
 * waiting to be reserved, ensure the memory used by the new array does
 * not overlap.
 *
 * RETURNS:
 * 0 on success, -1 on failure.
 */
static int __init_memblock memblock_double_array(struct memblock_type *type,
                                                phys_addr_t new_area_start,
                                                phys_addr_t new_area_size)
{
        struct memblock_region *new_array, *old_array;
        phys_addr_t old_alloc_size, new_alloc_size;
        phys_addr_t old_size, new_size, addr;
        int use_slab = slab_is_available();
        int *in_slab;
        /* We don't allow resizing until we know about the reserved regions
         * of memory that aren't suitable for allocation
         */
        if (!memblock_can_resize)
                return -1;

        /* Calculate new doubled size */
        old_size = type->max * sizeof(struct memblock_region);
        new_size = old_size << 1;
        /*
         * We need to allocated new one align to PAGE_SIZE,
         *   so we can free them completely later.
         */
        old_alloc_size = PAGE_ALIGN(old_size);
        new_alloc_size = PAGE_ALIGN(new_size);

        /* Retrieve the slab flag */
        if (type == &memblock.memory)
                in_slab = &memblock_memory_in_slab;
        else
                in_slab = &memblock_reserved_in_slab;

        /* Try to find some space for it.
         *
         * WARNING: We assume that either slab_is_available() and we use it or
         * we use MEMBLOCK for allocations. That means that this is unsafe to
         * use when bootmem is currently active (unless bootmem itself is
         * implemented on top of MEMBLOCK which isn't the case yet)
         *
         * This should however not be an issue for now, as we currently only
         * call into MEMBLOCK while it's still active, or much later when slab
         * is active for memory hotplug operations
         */
        if (use_slab) {
                new_array = kmalloc(new_size, GFP_KERNEL);
                addr = new_array ? __pa(new_array) : 0;
        } else {
                /* only exclude range when trying to double reserved.regions */
                if (type != &memblock.reserved)
                        new_area_start = new_area_size = 0;

                addr = memblock_find_in_range(new_area_start + new_area_size,
                                                memblock.current_limit,
                                                new_alloc_size, PAGE_SIZE);
                if (!addr && new_area_size)
                        addr = memblock_find_in_range(0,
                                min(new_area_start, memblock.current_limit),
                                new_alloc_size, PAGE_SIZE);

                new_array = addr ? __va(addr) : NULL;
        }
        if (!addr) {
                pr_err("memblock: Failed to double %s array from %ld to %ld entries !\n",
                       memblock_type_name(type), type->max, type->max * 2);
                return -1;
        }

        memblock_dbg("memblock: %s is doubled to %ld at [%#010llx-%#010llx]",
                        memblock_type_name(type), type->max * 2, (u64)addr,
                        (u64)addr + new_size - 1);

        /*
         * Found space, we now need to move the array over before we add the
         * reserved region since it may be our reserved array itself that is
         * full.
         */
        memcpy(new_array, type->regions, old_size);
        memset(new_array + type->max, 0, old_size);
        old_array = type->regions;
        type->regions = new_array;
        type->max <<= 1;

        /* Free old array. We needn't free it if the array is the static one */
        if (*in_slab)
                kfree(old_array);
        else if (old_array != memblock_memory_init_regions &&
                 old_array != memblock_reserved_init_regions)
                memblock_free(__pa(old_array), old_alloc_size);

        /*
         * Reserve the new array if that comes from the memblock.  Otherwise, we
         * needn't do it
         */
        if (!use_slab)
                BUG_ON(memblock_reserve(addr, new_alloc_size));

        /* Update slab flag */
        *in_slab = use_slab;

        return 0;
}
  • int use_slab = slab_is_available();
    • slab 메모리 할당자가 동작하는지 알아온다.
  • if (!memblock_can_resize)
    • 메모리 리사이징을 할 수 있도록 요청 받지 않은 경우에는 이 함수가 동작되지 않도록 한다.
    • bootmem_init() -> memblock_allow_resize()가 호출되어 전역변수 memblock_can_resize에 1이 설정된 후 부터 memblock 관리 영역의 리사이즈가 가능하다.
  • if (type == &memblock.memory) in_slab = &memblock_memory_in_slab;
    • 요청 타입이 memory인 경우 memblock 관리 영역이 slab 할당자를 사용하였는지 확인한다.
  •  else in_slab = &memblock_reserved_in_slab;
    • 요청 타입이 reserved인 경우 memblock 관리 영역이 slab 할당자를 사용하였는지 확인한다.
  • if (use_slab) { new_array = kmalloc(new_size, GFP_KERNEL);
    • slab 메모리 할당자가 동작하는 경우 새 관리 영역을 위해 kmalloc() 함수를 사용한다. 그렇지 않은 경우 memblock을 사용하여 할당 한다.
  • if (type != &memblock.reserved) new_area_start = new_area_size = 0;
    • 요청 타입이 reserved 타입이 아닌 경우 즉 memory 타입인 경우 할당 영역 검색을 0부터 시작하게 한다.
    • memory 타입을 등록하는 중에 관리 영역이 확장이 되는 경우 그 새로 할당 받아야 하는 영역은 그 전에 등록한 모든 memory 영역내에서 빈 자리를 검색하면 된다. 어짜피 새로 추가될 memory 공간은 기존 메모리 공간과 간섭이 없는 영역일 것이다.
  •  addr = memblock_find_in_range(new_area_start + new_area_size, memblock.current_limit, new_alloc_size, PAGE_SIZE);
    • 첫 번째 새로 관리 영역을 할당 받을 공간은 추가 요청 영역을 피해야 하므로 요청 영역의 상부를 먼저 검색한다.
  • if (!addr && new_area_size) addr = memblock_find_in_range(0, min(new_area_start, memblock.current_limit), new_alloc_size, PAGE_SIZE);
    • 만일 첫 번째 검색에서 할당 받지 못했거나 요청 타입이 reserved인 경우 추가 요청 영역을 피해 하부를 검색한다.
  • 할당 받은 공간에 기존 영역을 모두 복사하고 남은 공간을 0으로 채운다. 요청 영역의 max 값을 기존보다 두 배로 키운다.
  • if (*in_slab) kfree(old_array);
    • 기존 관리 공간이 slab에 의해 만들어 졌었으면 kfree() 함수를 사용하여 기존 사용하였던 관리 영역의 할당을 해제한다.
  • else if (old_array != memblock_memory_init_regions && old_array != memblock_reserved_init_regions) memblock_free(__pa(old_array), old_alloc_size);
    • 기존 관리 공간이 slab으로 만들어지지 않았으면서 초기 사용한 전역 static 공간이 아니면 memblock_free() 함수를 사용하여 기존 사용하였던 관리 영역의 할당을 해제한다.

 

memblock_merge_regions()

  • memblock 배열을 거꾸로 감소시키며 루프를 돌면서 경계가 붙어 있는 경우 memblock을 합친다.

mm/memblock.c

/**
 * memblock_merge_regions - merge neighboring compatible regions
 * @type: memblock type to scan
 *
 * Scan @type and merge neighboring compatible regions.
 */
static void __init_memblock memblock_merge_regions(struct memblock_type *type)
{
        int i = 0;

        /* cnt never goes below 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;
                /* move forward from next + 1, index of which is i + 2 */
                memmove(next, next + 1, (type->cnt - (i + 2)) * sizeof(*next));
                type->cnt--;
        }
}

 

memblock_cap_size()

  • 영역이 unsigned long 값을 넘어가는 경우(overflow) 넘친 부분을 잘라낸다.
    • 64bit: min(0xffff_ffff_ffff_ffff – base, size)
    • 32bit: min(0xffff_ffff – base, size)
  • 영역이 넘치는 경우 사이즈가 재계산되는데 사이즈가 1만큼 작아져 시스템의 마지막 주소 바이트를 사용할 수 없게된다.
    • 예) 32bit: base=0xffff_0000, size=0xffff -> size=0xffff (이건 정상)
    • 예) 32bit: base=0xffff_0000, size=0x10000 -> size=0xffff (1이 작아져서 0xffff_ffff 주소는 사용불가능)

mm/memblock.c

/* adjust *@size so that (@base + *@size) doesn't overflow, return new 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_remove()

  • memblock_remove()
    • memory 타입 영역에서 요청 영역을 제거한다.
    • 만일 겹치는 부분이 있으면 겹치는 부분을 분리하여 남겨놓는다.
  • memblock_remove_range()
    • 지정한 타입 영역에서 요청 영역을 제거한다.
  • memblock_isolate_range()
    • 삭제할 영역의 상단과 하단 라인에 겹치는 memblock에 대해 그 라인을 기점으로 분리한다.
    • 삭제할 블럭번호가 인수의 start_rgn에 저장되고 끝번호+1 값이 end_rgn에 들어간다. 만일 삭제할 항목이 없으면 두 변수에 0이 저장된다.
  • 삭제할 끝 memblock index 부터 삭제할 시작 memblock index 까지 역순으로 루프를 돌며 memblock_remove_region() 함수를 호출하여 해당 memblock 영역을 삭제한다.
    • 메모리의 복사(이동)를 줄이기 위해 루프를 역순으로 돈다.
  • memblock_remove_region()
    • 지정된 타입의 memblock에서 삭제할 memblock의 상위 부터 끝까지 지정된 인덱스의 memblock 위치로 이동시키고 cnt 값을 1 줄이며 tot_size를 삭제한 블럭의 사이즈만큼 줄인다.

memblock5

memblock6

 

memblock_isolate_range()

memblock7

memblock isolation 6개 예)

memblock-8a

  • 주어진 영역의 하단 및 상단이 기존 memblock과 겹쳐있으면 주어진 영역의 상하단 기점으로 분리시킨다.
  • start_rgn과 end_rgn은 함수내에서 설정하는 out 인수로 주어진 영역에 포함되는 블럭의 시작 인덱스 번호와 끝 인덱스 번호+1 값이 저장된다.
    • 만일 start_rgn과 end_rgn 모두 0이 지정된 경우는 포함된 memblock 영역이 하나도 없다는 것을 뜻한다.
  • memblock_cap_size()
    • 영역이 최상단 주소를 초과하는 경우 넘어가는 부분을 제외할 수 있도록 size를 조절한다.
  • memblock_double_array()
    • 해당 타입의 최대 갯수를 사용하려할 때 관리 배열을 2배로 확장한다. 확장이 불가능하면 메모리가 부족하다는 -ENOMEM을 리턴한다.

mm/memblock.c

/**
 * memblock_isolate_range - isolate given range into disjoint memblocks
 * @type: memblock type to isolate range for
 * @base: base of range to isolate
 * @size: size of range to isolate
 * @start_rgn: out parameter for the start of isolated region
 * @end_rgn: out parameter for the end of isolated region
 *
 * Walk @type and ensure that regions don't cross the boundaries defined by
 * [@base,@base+@size).  Crossing regions are split at the boundaries,
 * which may create at most two more regions.  The index of the first
 * region inside the range is returned in *@start_rgn and end in *@end_rgn.
 *
 * RETURNS:
 * 0 on success, -errno on failure.
 */
static int __init_memblock memblock_isolate_range(struct memblock_type *type,
                                        phys_addr_t base, phys_addr_t size,
                                        int *start_rgn, int *end_rgn)
{
        phys_addr_t end = base + memblock_cap_size(base, &size);
        int i;
        *start_rgn = *end_rgn = 0;
        if (!size)
                return 0;

 

  • 첫 memblock 영역부터 마지막 영역에 대해 루프를 돌며 rbase와 rend에는 해당 루프 인덱스의 memblock 시작 주소와 끝 주소가 담긴다.
  • (A) 조건: rbase > end
    • 현재 인덱스 영역의 시작주소가 주어진 영역의 상단보다 크거나 같은 경우 더 이상 상위 인덱스에서 처리할 필요가 없으므로 루프를 빠져나가게 한다.
  • (B) 조건: rend ← base
    • 현재 인덱스 영역의 끝 주소보다 주어진 영역의 상단보다 같거나 큰 경우 아직 겹쳐지는 영역이 아니므로 다음 인덱스 memblock을 처리하기 위해 continue 한다.
  • (C) 조건: rbase < base
    • 현재 인덱스 영역의 시작 주소가 주어진 영역의 시작 주소보다 작은 경우, 즉 주어진 영역의 시작 주소가 현재 인덱스 memblock과 겹친 경우 다음과 같이 처리한다.
      • 현재 인덱스 memblock을 하단 겹친 라인을 기점으로 상부로 영역을 옮긴다.
      • 역시 인덱스 memblock의 하단 겹친 라인을 기점으로 새롭게 하부에 memblock을 insert 한다.
        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;
                if (rbase < base) {
                        /*
                         * @rgn intersects from below.  Split and continue
                         * to process the next region - the new top half.
                         */
                        rgn->base = base;
                        rgn->size -= base - rbase;
                        type->total_size -= base - rbase;
                        memblock_insert_region(type, i, rbase, base - rbase,
                                               memblock_get_region_node(rgn),
                                               rgn→flags);

 

    • (D) 조건: rend > end
      • 현재 인덱스 영역의 끝 주소가 주어진 영역의 끝 주소보다 큰 경우, 즉 주어진 영역의 끝 주소가 현재 인덱스 memblock과 겹친 경우 다음과 같이 처리한다.
      • 현재 인덱스 memblock을 상단 겹친 라인을 기점으로 상부로 영역을 옮긴다.
      • 역시 인덱스 memblock의 상단 겹친 라인을 기점으로 새롭게 하부에 memblock을 insert 한다.
  • (*) 아무조건에도 걸리지 않은 경우 현재 인덱스 영역은 주어진 영역에 포함된 경우이다. 이 때에는 start_rgn에 현재 인덱스 값을 지정하고 end_rgn에는 현재 인덱스 값 + 1을 지정한다.
                } else if (rend > end) {
                        /*
                         * @rgn intersects from above.  Split and redo the
                         * current region - the new bottom half.
                         */
                        rgn->base = end;
                        rgn->size -= end - rbase;
                        type->total_size -= end - rbase;
                        memblock_insert_region(type, i--, rbase, end - rbase,
                                               memblock_get_region_node(rgn),
                                               rgn->flags);
                } else {
                        /* @rgn is fully contained, record it */
                        if (!*end_rgn)
                                *start_rgn = i;
                        *end_rgn = i + 1;
                }
        }

        return 0;
}

 

계속  -> Memblock – (2)

 

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.