Memblock – (2)

Early Memory Allocation

 

memblock_alloc()

  • memory memblock에 등록된 영역중에서 reserved memblock 영역을 제외한 공간중에서 size 만큼 영역을 할당한다.
  • memblock allocation은 buddy등의 memory allocator가 활성화되기 전까지 사용할 수 있는 수단으로 커널에서 early memory allocator로 자리잡았다.
  • 요청 영역의 사이즈는 지정된 align 바이트만큼 정렬(align) 된다.
    • align 조정된 사이즈만큼 크기가 작아질 수 있다.
  • free 메모리 할당받는 방향에 따라 align 방향이 결정된다.
    • bottom up인 경우 메모리 할당을 아래에서 부터 위로 할당하여야 하므로 영역의 align은 round down 되어야 한다.
    • top down인 경우 메모리 할당을 위에서 부터 아래로 할당하여야 하므로 영역의 align은 round up 되어야 한다.
      • ARM은 cpu hotplug 기능이 적용되지 않았고, 따라서 모든 노드가 non-movable 속성이다. 이에 항상 top down 방식을 사용한다.

memblock-9b

mm/memblock.c

phys_addr_t __init memblock_alloc(phys_addr_t size, phys_addr_t align)
{
        return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
}

 

memblock_alloc_base()

phys_addr_t __init memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr) 
{
        phys_addr_t alloc;

        alloc = __memblock_alloc_base(size, align, max_addr);

        if (alloc == 0)
                panic("ERROR: Failed to allocate 0x%llx bytes below 0x%llx.\n",
                      (unsigned long long) size, (unsigned long long) max_addr);

        return alloc;
}

 

__memblock_alloc_base()

  • NUMA_NO_NODE: 노드 영역에 관계 없이 할당한다.
phys_addr_t __init __memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr) 
{
        return memblock_alloc_base_nid(size, align, max_addr, NUMA_NO_NODE);
}

 

memblock_alloc_base_nid()

static phys_addr_t __init memblock_alloc_base_nid(phys_addr_t size,
                                        phys_addr_t align, phys_addr_t max_addr,
                                        int nid)
{
        return memblock_alloc_range_nid(size, align, 0, max_addr, nid);
}

 

memblock_alloc_range_nid()

  • memblock_find_in_range_node()
    • 주어진 범위내에서 free 영역을 찾는다.
  • memblock_reserve()
    • 해당 영역을 reserve한다.
  • kmemleak_alloc()
    • register a newly allocated object

mm/memblock.c

static phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
                                        phys_addr_t align, phys_addr_t start,
                                        phys_addr_t end, int nid)
{
        phys_addr_t found;

        if (!align)
                align = SMP_CACHE_BYTES;

        found = memblock_find_in_range_node(size, align, start, end, nid);
        if (found && !memblock_reserve(found, size)) {
                /*
                 * The min_count is set to 0 so that memblock allocations are
                 * never reported as leaks.
                 */
                kmemleak_alloc(__va(found), size, 0, 0);
                return found;
        }
        return 0;
}

 

memblock_reserve()

  • reserved 영역에 주어진 범위를 추가한다.
int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
        return memblock_reserve_region(base, size, MAX_NUMNODES, 0);
}

 

memblock_reserve_region()

static int __init_memblock memblock_reserve_region(phys_addr_t base,
                                                   phys_addr_t size,
                                                   int nid,
                                                   unsigned long flags)
{
        struct memblock_type *_rgn = &memblock.reserved;

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

        return memblock_add_range(_rgn, base, size, nid, flags);
}

 

memblock_find_in_range()

  • 커널 버전 3.13-rc1부터 bottom up 메모리 할당 기능이 추가되었다.
  • 기존 버전의 커널은 항상 메모리 할당을 top down으로 free 영역을 검색하여 할당하였었다.
  • 리눅스에 메모리 hotplug 기능이 추가되면서 메모리 노드에 movable 속성 필드가 새롭게 생겨났다.
  • movable 가능한 노드들은 hotplug 기능으로 인해 탈착 기능을 수행하는 동안 해당 노드 전체의 사용중인 페이지들이 다른 노드로 이주(migration)를 하게 하는 코드가 추가되었다.
  • 커널과 커널이 사용하는 메모리는 hotplug 기능을 지원하지 않는 노드에 설치가 되며 이 노드는 항상 non-movable 속성을 갖는다.
  • 현재 x86 64비트 대용량 서버들이 hotplug 기능을 지원하는데 이러한 노드들은 최소 16G 용량씩을 가지고 보통 커널이미지가 있는 non-moveable 속성의 노드의 위쪽으로 떨어져 있다. 이러한 조건에서 커널이 메모리를 할당하게 되면 메모리의 최 상단 movable 노드부터 할당을 하게된다. 이러한 경우 moveable 노드를 탈착하게 되면 그 동안 할당하여 사용한 많은 메모리들이 movable 노드에서 대량으로 다른 노드로 이주(migration)이 되어야 하는 경우가 발생한다.
  • hotplug 기능으로 인하여 데이터 페이지의 이주가 발생하는 빈도를 낮추기 위해 메모리 할당을 bottom up으로(낮은 주소부터 높은 주소 방향으로) 할당할 수 있는 기능이 필요하게 되었고 커널이 있는 곳에서 즉, 커널의 끝(_end)부터 상단으로 메모리 할당이 가능할 수 있도록 코드가 추가되었다. 이렇게 하여 할당된 메모리는 커널이 있는 non-movable 노드에 같이 있게될 확률이 매우 높다.
  • ARM 아키텍처는 현재(커널 v4.4)까지 CPU hotplug 기능과는 달리 메모리 hotplug 기능이 적용되지 않았다. 따라서 메모리 할당은 top down 방식 을 사용한다.
    • CONFIG_MEMORY_HOTPLUG, CONFIG_HOTPLUG_CPU
  • 참고: mm/memblock.c: introduce bottom-up allocation mode

memblock4

 

__memblock_find_range_top_down()
/**
 * __memblock_find_range_top_down - find free area utility, in top-down
 * @start: start of candidate range
 * @end: end of candidate range, can be %MEMBLOCK_ALLOC_{ANYWHERE|ACCESSIBLE}
 * @size: size of free area to find
 * @align: alignment of free area to find
 * @nid: nid of the free area to find, %NUMA_NO_NODE for any node
 *
 * Utility called from memblock_find_in_range_node(), find free area top-down.
 *
 * RETURNS:
 * Found address on success, 0 on failure.
 */
static phys_addr_t __init_memblock
__memblock_find_range_top_down(phys_addr_t start, phys_addr_t end,
                               phys_addr_t size, phys_addr_t align, int nid)
{
        phys_addr_t this_start, this_end, cand;
        u64 i;

        for_each_free_mem_range_reverse(i, nid, &this_start, &this_end, NULL) {

                this_start = clamp(this_start, start, end);
                this_end = clamp(this_end, start, end);

                if (this_end < size)
                        continue;

                cand = round_down(this_end - size, align);
                if (cand >= this_start)
                        return cand;
        }

        return 0;
}
  • for_each_free_mem_range_reverse()를 사용하여 free 공간을 루프를 돌며 하나씩 알아온다.
  • if (this_end < size) continue;
    • 알아온 free 영역의 끝 주소가 사이즈보다 작으면 next
      this_end – size를 하여 마이너스 값이 나오면서 오류(원하지 않는 round_down 값)가 나올 수 있으므로 이를 방지
  • cand = round_down(this_end – size, align)
    • 요청 사이즈의 비교는 align된 크기로 한다.
  • if (cand >= this_start) return cand;
    • 알아온 free 영역의 범위에 size가 포함될 수 있으면 성공적으로 cand 주소를 리턴한다.

아래의 그림에서 for_each_free_mem_range_reverse()를 통해 7개의 free 공간이 얻어지며, start 에서 end까지의 범위에 포함되는 케이스가 4개로 압축이 되고 1번 부터 4번 까지 우선 순위로 요청 사이즈가 포함될 수 있는 경우 이 공간의 주소를 리턴한다.

__memblock_find_range_top_down-1b

 

for_each_memblock()

  • 지정한 타입의 memblock 영역 전체를 처음부터 끝까지 loop를 돌며 region 변수에 각각의 memblock 구조체 포인터 값을 담는다.

include/linux/memblock.h

#define for_each_memblock(memblock_type, region)                \
        for (region = memblock.memblock_type.regions;           \
             region < (memblock.memblock_type.regions + 	\
	     memblock.memblock_type.cnt); 			\
             region++)

 

동작 과정)
struct memblock_region *r;
for_each_memblock(reserved, r)
{
	print_info(“base=0x%x, size=0x%x\n”, r.base, r.size);
}

 

  • reserved 영역이 아래와 같이 4개가 있을 때 r 값은 가장 하단의 memblock region부터 최상단 memblock region까지 반복하여 memblock_region 구조체 포인터를 지정한다.

memblock10

for_each_mem_pfn_range()

include/linux/memblock.h

/**
 * for_each_mem_pfn_range - early memory pfn range iterator
 * @i: an integer used as loop variable
 * @nid: node selector, %MAX_NUMNODES for all nodes
 * @p_start: ptr to ulong for start pfn of the range, can be %NULL
 * @p_end: ptr to ulong for end pfn of the range, can be %NULL
 * @p_nid: ptr to int for nid of the range, can be %NULL
 *
 * Walks over configured memory ranges.
 */
#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))
  • 노드 번호에 해당하는 memory memblock가 파편화된 페이지를 제외한 즉 온전한 페이지가 1개 이상인 경우의 시작 pfn과 끝 pfn 값을 루프를 돌며 하나씩 알아온다.

아래의 그림은 nid=1 값으로 for_each_mem_pfn_range() 매크로 루프를 동작시킬 때 조건에 매치되는 경우를 붉은 박스로 표현하였고 각각의 값은 다음과 같다.

  • 다음과 같이 2번의 매치 건이 발생한다.
    • i 값이 1로 p_start=3, p_end=3, p_nid=1을 얻어온다.
    • i값이 2로 p_start=5, p_end=7, p_nid=1을 얻어온다.
  • 실제 memory memblock 들은 아래 그림과 다르게 매우 큰 페이지들로 이루어졌고 거의 대부분 align 된 상태이므로 아래 그림과는 많이 다르지만 계산 방법에 대해 이해를 돕는 것으로 활용해야 한다.

for_each_mem_pfn_range-1a

 

__next_mem_pfn_range()

mm/memblock.c

/*
 * Common iterator interface used to define for_each_mem_range().
 */                         
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;
        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;
}
  • while (++*idx < type->cnt) {
    • 증가시킨 인덱스가 memory memblock 수 보다 작은동안 루프를 돈다.
  • if (PFN_UP(r->base) >= PFN_DOWN(r->base + r->size))
    • memblock 영역안에 온전한 1개 이상의 4K 페이지가 포함되지 않은 경우 다음 memblock을 위해 continue를 수행하고 포함된 경우
  • if (nid == MAX_NUMNODES || nid == r->nid)
    • 요청 노드 번호와 매치가 되거나 MAX_NUMNODES인 경우 루프를 탈출한다.
  • if (*idx >= type->cnt) {
    • 루프가 끝날 때까지 매치되지 않았으면 idx에 -1을 담고 리턴한다.
  • if (out_start_pfn) *out_start_pfn = PFN_UP(r->base);
    • out_start_pfn이 null이 아니면 out_start_pfn에 4K 페이지가 파편화되지 않고 온전히 포함된 시작 페이지 번호를 담는다.
  • if (out_end_pfn) *out_end_pfn = PFN_DOWN(r->base + r->size);
    • out_end_pfn이 null이 아니면 out_end_pfn에 4K 페이지가 파편화되지 않고 온전히 포함된 끝 페이지 번호를 담는다.
  • if (out_nid) *out_nid = r->nid;
    • out_nid가 null이 아니면 out_nid에 memblock의 nid 값을 담는다.

아래 그림은 7개의 case 별로 __next_mem_pfn_range() 함수가 실행될 때 출력되는 인수에 대해 표현하였다.

  • 아래 그림 역시도 실제 memory memblock 들의 사이즈보다 훨씬 축소를 해놨기 때문에 계산 방법에 대해 이해를 돕는 것으로 활용해야 한다

__next_mem_pfn_range-1a

 

for_each_free_mem_range()

  • 루프를 돌며 지정된 노드 id의 memory 영역에서 reserved 영역을 제외한 영역이 free 메모리이며 이 영역을 순서대로 p_start, p_end, p_nid 인수에 지정한다.

include/linux/memblock.h

/**
 * for_each_free_mem_range - iterate through free memblock areas
 * @i: u64 used as loop variable
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @p_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @p_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @p_nid: ptr to int for nid of the range, can be %NULL
 *
 * Walks over free (memory && !reserved) areas of memblock.  Available as
 * soon as memblock is initialized.
 */
#define for_each_free_mem_range(i, nid, p_start, p_end, p_nid)          \
        for_each_mem_range(i, &memblock.memory, &memblock.reserved,     \
                           nid, p_start, p_end, p_nid)

 

for_each_mem_range()

  • 루프를 돌며 지정된 노드 id의 A 타입 영역에서 B 타입 영역을 제외한 영역이 주어질 때 이 영역을 순서대로 p_start, p_end, p_nid 인수에 지정한다.
  • B 타입 영역이 null일 수도 있다.

include/linux/memblock.h

/**
 * for_each_mem_range - iterate through memblock areas from type_a and not
 * included in type_b. Or just type_a if type_b is NULL.
 * @i: u64 used as loop variable
 * @type_a: ptr to memblock_type to iterate
 * @type_b: ptr to memblock_type which excludes from the iteration
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @p_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @p_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @p_nid: ptr to int for nid of the range, can be %NULL
 */
#define for_each_mem_range(i, type_a, type_b, nid,                      \
                           p_start, p_end, p_nid)                       \
        for (i = 0, __next_mem_range(&i, nid, type_a, type_b,           \
                                     p_start, p_end, p_nid);            \
             i != (u64)ULLONG_MAX;                                      \
             __next_mem_range(&i, nid, type_a, type_b,                  \
                              p_start, p_end, p_nid))

 

  • 아래와 같이 memory 영역내에서 reserved 영역을 제외한 영역이 free 영역이고 2개의 영역이 루프로 제공된다.

memblock11

 

__next_mem_range()

mm/memblock.c

/**
 * __next__mem_range - next function for for_each_free_mem_range() etc.
 * @idx: pointer to u64 loop variable
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @type_a: pointer to memblock_type from where the range is taken
 * @type_b: pointer to memblock_type which excludes memory from being taken
 * @out_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @out_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @out_nid: ptr to int for nid of the range, can be %NULL
 *
 * Find the first area from *@idx which matches @nid, fill the out
 * parameters, and update *@idx for the next iteration.  The lower 32bit of
 * *@idx contains index into type_a and the upper 32bit indexes the
 * areas before each region in type_b.  For example, if type_b regions
 * look like the following,
 *
 *      0:[0-16), 1:[32-48), 2:[128-130)
 *
 * The upper 32bit indexes the following regions.
 *
 *      0:[0-0), 1:[16-32), 2:[48-128), 3:[130-MAX)
 *
 * As both region arrays are sorted, the function advances the two indices
 * in lockstep and returns each intersection.
 */
void __init_memblock __next_mem_range(u64 *idx, int nid,
                                      struct memblock_type *type_a,
                                      struct memblock_type *type_b,
                                      phys_addr_t *out_start,
                                      phys_addr_t *out_end, int *out_nid)

 

  • 노드 id 인수 값으로 deprecated된 MAX_NUMNODES를 사용하면 경고문을 출력한다.
  • type_a는 memory 타입, type_b는 reserved 타입으로 한다.
  • memory memblock에 대한 1차 루프는 idx_a가 등록된 갯수보다 작을 때까지 하나씩 증가한다.
  • m_start와 m_end는 현재 1차 루프 인덱스의 memblock 영역의 시작 주소와 끝 주소이다.
  • 주어진 nid가 NUMA_NO_NODE가 아니면서 해당 인덱스 memblock의 노드도 아니면 skip 한다.
  • 현재 1차 루프 인덱스의 memblock이 hotplug 영역으로(flag: bit0=MEMBLOCK_HOTPLUG)되어 있는 경우이면서 노드가 이동가능한 상태라면 이 영역을 배정하지 않도록 skip 한다.
{
        int idx_a = *idx & 0xffffffff;
        int idx_b = *idx >> 32;

        if (WARN_ONCE(nid == MAX_NUMNODES,
        "Usage of MAX_NUMNODES is deprecated. Use NUMA_NO_NODE instead\n"))
                nid = NUMA_NO_NODE;

        for (; idx_a < type_a->cnt; idx_a++) {
                struct memblock_region *m = &type_a->regions[idx_a];

                phys_addr_t m_start = m->base;
                phys_addr_t m_end = m->base + m->size;
                int         m_nid = memblock_get_region_node(m);

                /* only memory regions are associated with nodes, check it */
                if (nid != NUMA_NO_NODE && nid != m_nid)
                        continue;

                /* skip hotpluggable memory regions if needed */
                if (movable_node_is_enabled() && memblock_is_hotpluggable(m))
                        continue;
  • type_b에 대한 영역이 지정되지 않으면(null) 현재 1차 루프 인덱스의 memblock에 대한 영역으로 out_start와 out_end를 결정하고 idx_a 만을 1 증가 시키고 함수를 성공리에 빠져나간다.
  • type_b에 대한 2차 루프는 idx_b가 등록된 갯수+1보다 작을때 까지 하나씩 증가한다.
  • r 영역은 현재 2차 루프 인덱스가 가리킨 곳의 영역을 가리킨다.
  • r_start는 idx_b가 0보다 크면 현재 이전 memblock의 끝 주소를 가리키고 idx_b가 0이면 0번 주소를 지정한다.
  • r_end는 idx_b가 등록된 갯수보다 작은 경우 r 영역의 시작주소를 지정하고 아니면 시스템 최대 주소를 지정한다.
                if (!type_b) {
                        if (out_start)
                                *out_start = m_start;
                        if (out_end)
                                *out_end = m_end;
                        if (out_nid)
                                *out_nid = m_nid;
                        idx_a++;
                        *idx = (u32)idx_a | (u64)idx_b << 32;
                        return;
                }

                /* scan areas before each reservation */
                for (; idx_b < type_b->cnt + 1; idx_b++) {
                        struct memblock_region *r;
                        phys_addr_t r_start;
                        phys_addr_t r_end;

                        r = &type_b->regions[idx_b];
                        r_start = idx_b ? r[-1].base + r[-1].size : 0;
                        r_end = idx_b < type_b->cnt ?
                                r->base : ULLONG_MAX;

 

  • 조건 A) r_start >= m_end
    • reserve memblock 영역이 memory memblock 영역을 벗어난 경우 2차 루프를 빠져나가서 다음 memory memblock을 준비하도록 한다.
  • 조건 B) m_start < r_end
    • 두 영역이 교차하는 경우이다.
    • out_start에 하단 reserve 영역값의 끝 주소나 memory 영역값의 시작 주소중 가장 큰 주소를 담는다.
    • out_end에 상단 reserve 영역값의 시작 주소나 memory 영역값의 끝 주소중에 가장 작은 주소를 담는다.
  • 조건 B-1) m_end < r_end
    • reserve 영역의 끝 주소가 memory 영역의 끝주소와 비교하여 큰 경우 idx_a를 증가시키고 다음 memory block을 준비하기 위해 빠져나가고, 크지 않은 경우 idx_b를 증가시키고 계속하여 다음 reserve 영역을 준비하기 위해 빠져나간다.
  • 1차 루프를 끝까지 완료하면 더 이상 처리할 수 없으므로 idx에 ULLONG_MAX(시스템 최대 주소) 값을 부여하고 빠져나간다.
                        /*
                         * if idx_b advanced past idx_a,
                         * break out to advance idx_a
                         */
                        if (r_start >= m_end)
                                break;
                        /* if the two regions intersect, we're done */
                        if (m_start < r_end) {
                                if (out_start)
                                        *out_start =
                                                max(m_start, r_start);
                                if (out_end)
                                        *out_end = min(m_end, r_end);
                                if (out_nid)
                                        *out_nid = m_nid;
                                /*
                                 * The region which ends first is
                                 * advanced for the next iteration.
                                 */
                                if (m_end <= r_end)
                                        idx_a++;
                                else
                                        idx_b++;
                                *idx = (u32)idx_a | (u64)idx_b << 32;
                                return;
                        }
                }
        }
        /* signal end of iteration */
        *idx = ULLONG_MAX;
}

 

memblock12

  • 아래 1개의 meory memblock과 2개의 reserve memblock가 등록되어 있는 상태에서 __next__mem_range(0x1UUL) 매크로를 사용한 경우의 예이다.
  • 루프 1의 idx_a 인덱스는 0으로 memory.regions[0]의 정보 사용
  • 루프 2의 idx_b 인덱스는 1로 reserve.regions[1]과 이전 reserve memblock의 영역 정보를 사용하는 중이다.
  • 확정된 free 영역으로 r_start는 reserve.regions[0]의 끝 주소로 지정되고 r_end는 reserve.regions[1]의 시작 주소로 지정된다.

memblock13

 

  • 아래 2개의 meory memblock과 4개의 reserve memblock이 등록되어 있는 상태에서 __next__mem_range(0x1UUL) 매크로를 사용하여 free 영역이 검색되는 범위를 표현한다.

memblock14a

 

for_each_free_mem_range_reverse()

  • 루프를 역순으로 돌며 지정된 노드 id의 memory 영역에서 reserved 영역을 제외한 영역이 free 메모리이며 이 영역을 순서대로 p_start, p_end, p_nid 인수에 지정한다.

mm/memblock.c

/**
 * for_each_free_mem_range_reverse - rev-iterate through free memblock areas
 * @i: u64 used as loop variable
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @p_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @p_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @p_nid: ptr to int for nid of the range, can be %NULL
 *
 * Walks over free (memory && !reserved) areas of memblock in reverse
 * order.  Available as soon as memblock is initialized.
 */
#define for_each_free_mem_range_reverse(i, nid, p_start, p_end, p_nid)  \
        for_each_mem_range_rev(i, &memblock.memory, &memblock.reserved, \
                               nid, p_start, p_end, p_nid)

 

for_each_mem_range_rev()

  • 루프를 역순으로 돌며 지정된 노드 id의 A 타입 영역에서 B 타입 영역을 제외한 영역이 주어질 때 이 영역을 순서대로 p_start, p_end, p_nid 인수에 지정한다. B 타입 영역이 null일 수도 있다.
  • 인덱스는 64비트 값이며 절반씩 나누어 상위 32비트는 memory memblock 영역에 대한 인덱스를 가리키고, 하위 32비트는 reserved memblock 영역에 대한 인덱스를 가리킨다.
    • 처음 시작 시 역순으로 루프를 시작하므로 ~0UUL 값이 대입되어 시작한다.
  • ARM은 free 메모리를 할당하기 위해 검색시 이 top down 방식의 매크로를 사용한다.

mm/memblock.c

/**
 * for_each_mem_range_rev - reverse iterate through memblock areas from
 * type_a and not included in type_b. Or just type_a if type_b is NULL.
 * @i: u64 used as loop variable
 * @type_a: ptr to memblock_type to iterate
 * @type_b: ptr to memblock_type which excludes from the iteration
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @p_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @p_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @p_nid: ptr to int for nid of the range, can be %NULL
 */
#define for_each_mem_range_rev(i, type_a, type_b, nid,                  \
                               p_start, p_end, p_nid)                   \
        for (i = (u64)ULLONG_MAX,                                       \
                     __next_mem_range_rev(&i, nid, type_a, type_b,      \
                                         p_start, p_end, p_nid);        \
             i != (u64)ULLONG_MAX;                                      \
             __next_mem_range_rev(&i, nid, type_a, type_b,              \
                                  p_start, p_end, p_nid))

 

  • 아래와 같이 memory 영역내에서 reserved 영역을 제외한 영역이 free 영역이고 2개의 영역이 역순 루프로 제공된다.

memblock15

  • 아래와 같이 memory region이 여러 개일 때 i 값의 추적을 표현하였다.
    •  i 값의 최초 시작은 0xffff_ffff_ffff_ffff로 시작하고 free 영역을 리턴할 때 마다 아래와 같이 변화가 됨을 알 수 있다.
    • for each 루프를 종료하는 순간 i 값은 다시 0xffff_ffff_ffff_ffff로 바뀐다.

for_each_free_mem_range_reverse-1d

 

__next_mem_range_rev()

mm/memblock.c

  • idx 값이 ~0UUL로 진입한 경우 idx_a에는 type_a 형태의 memblock 갯수에서 -1을 대입하고, idx_b에는 type_b 형태의 memblock 갯수 값을 대입한다.
  • 더 이상 매치되는 값이 없어서 루프를 다 돌고  함수를 종료하는 경우에는 idx 값이 다시 ~0UUL로 지정된다. (no more data)
/**
 * __next_mem_range_rev - generic next function for for_each_*_range_rev()
 *
 * Finds the next range from type_a which is not marked as unsuitable
 * in type_b.
 *
 * @idx: pointer to u64 loop variable
 * @nid: nid: node selector, %NUMA_NO_NODE for all nodes
 * @type_a: pointer to memblock_type from where the range is taken
 * @type_b: pointer to memblock_type which excludes memory from being taken
 * @out_start: ptr to phys_addr_t for start address of the range, can be %NULL
 * @out_end: ptr to phys_addr_t for end address of the range, can be %NULL
 * @out_nid: ptr to int for nid of the range, can be %NULL
 *
 * Reverse of __next_mem_range().
 */
void __init_memblock __next_mem_range_rev(u64 *idx, int nid,
                                          struct memblock_type *type_a,
                                          struct memblock_type *type_b,
                                          phys_addr_t *out_start,
                                          phys_addr_t *out_end, int *out_nid)
{
        int idx_a = *idx & 0xffffffff;
        int idx_b = *idx >> 32;

        if (WARN_ONCE(nid == MAX_NUMNODES, "Usage of MAX_NUMNODES is deprecated. Use NUMA_NO_NODE instead\n"))
                nid = NUMA_NO_NODE;

        if (*idx == (u64)ULLONG_MAX) {
                idx_a = type_a->cnt - 1;
                idx_b = type_b->cnt;
        }

        for (; idx_a >= 0; idx_a--) {
                struct memblock_region *m = &type_a->regions[idx_a];

                phys_addr_t m_start = m->base;
                phys_addr_t m_end = m->base + m->size;
                int m_nid = memblock_get_region_node(m);

                /* only memory regions are associated with nodes, check it */
                if (nid != NUMA_NO_NODE && nid != m_nid)
                        continue;

                /* skip hotpluggable memory regions if needed */
                if (movable_node_is_enabled() && memblock_is_hotpluggable(m))
                        continue;

 

                if (!type_b) {
                        if (out_start)
                                *out_start = m_start;
                        if (out_end)
                                *out_end = m_end;
                        if (out_nid)
                                *out_nid = m_nid;
                        idx_a++;
                        *idx = (u32)idx_a | (u64)idx_b << 32;
                        return;
                }

                /* scan areas before each reservation */
                for (; idx_b >= 0; idx_b--) {
                        struct memblock_region *r;
                        phys_addr_t r_start;
                        phys_addr_t r_end;

                        r = &type_b->regions[idx_b];
                        r_start = idx_b ? r[-1].base + r[-1].size : 0;
                        r_end = idx_b < type_b->cnt ?
                                r->base : ULLONG_MAX;
                        /*
                         * if idx_b advanced past idx_a,
                         * break out to advance idx_a
                         */

                        if (r_end <= m_start)
                                break;
                        /* if the two regions intersect, we're done */
                        if (m_end > r_start) {
                                if (out_start)
                                        *out_start = max(m_start, r_start);
                                if (out_end)
                                        *out_end = min(m_end, r_end);
                                if (out_nid)
                                        *out_nid = m_nid;
                                if (m_start >= r_start)
                                        idx_a--;
                                else
                                        idx_b--;
                                *idx = (u32)idx_a | (u64)idx_b << 32;
                                return;
                        }
                }
        }
        /* signal end of iteration */
        *idx = ULLONG_MAX;
}

 

기타 Memblock API

  • memblock_type_name()
    • memblock 타입명(문자열)을 알아온다.
  • memblock_cap_size()
    • 영역이 시스템의 최대 주소를 넘어가는 경우 overflow 된 만큼 잘라낸다.
  • memblock_addrs_overlap()
    • 영역이 서로 겹치는지 검사한다.
  • memblock_overlaps_region()
    • 등록된 memblock 영역들과 인수로 주어진 영역이 겹치는지 검사하여 겹치는 영역의 index를 리턴하고 아니면 -1 리턴
  • get_allocated_memblock_reserved_regions_info()
    • reserved 타입의 영역이 초기화 시 할당 받은 주소가 아니고 새롭게 buddy/slab memory 할당자에 의해 재 할당 받은 경우라면 해당 할당 영역의 시작주소를 리턴하고 아니면 0을 리턴한다. 이 함수는 free_low_memory_core_early() 함수를 통하여 bootmem 영역의 사용을 종료하고 buddy allocator로 전환할 때 호출된다.
  • get_allocated_memblock_memory_regions_info()
    • memory 타입의 영역이 초기화 시 할당 받은 주소가 아니고 새롭게 buddy/slab memory 할당자에 의해 재 할당 받은 경우라면 해당 할당 영역의 시작주소를 리턴하고 아니면 0을 리턴한다.이 함수는 free_low_memory_core_early() 함수를 통하여 bootmem 영역의 사용을 종료하고 buddy allocator로 전환할 때 호출된다.
  • memblock_double_array()
    • 주어진 타입의 memblock_region 배열을 2배 크기로 확장한다.
  • memblock_free()
    • reserved 영역에서 주어진 영역을 삭제한다.
  • memblock_set_node()
    • 주어진 영역에 해당하는 모든 memblock의 노드id를 설정한다. 영역이 memblock의 중간에 걸치면 분리하여 노드id를 설정한다.
  • memblock_phys_mem_size()
    • memory 영역에 등록된 memblock의 전체 사이즈
  • memblock_mem_size()
    • 주어진 limit_pfn까지 총 등록된 memory memblock 페이지 수를 알아온다.
  • memblock_start_of_DRAM()
    • memory 영역에 등록된 첫 memblock의 시작 주소(DRAM 시작 주소)
  • memblock_end_of_DRAM()
    • memory 영역에 등록된 마지막 memblock의 끝 주소(DRAM 끝 주소)
  • memblock_enforce_memory_limit()
    • 메모리 영역이 limit를 초과하는 경우 truncate ?
  • memblock_search()
    • 주어진 type의 memblock에서 주어진 주소로 2진 탐색 알고리즘을 사용하여 memblock을 찾고 해당 인덱스 값을 얻는다.
  • memblock_is_reserved()
    • reserved 영역에 주소가 포함되어 있는지 reserved 영역을 검색하여 판단한다.
  • memblock_is_memory()
    • memory 영역에 주소가 포함되어 있는지 memory 영역을 검색하여 판단한다.
  • memblock_search_pfn_nid()
    • memory 영역에서 주어진 주소로 2진 탐색 알고리즘으로 memblock을 찾은 후, 찾은 영역의 시작 페이지와 끝 페이지를 알아온다.
  • memblock_is_region_reserved()
    • 주어진 영역이 reserved memblock 영역과 겹치는지 확인한다.
  • memblock_trim_memory()
    • memory memblock들 모두 지정된 align 바이트로 정렬한다. 사이즈가 align 크기보다 작은 경우 삭제한다.
  • memblock_set_current_limit()
    • memblock의 limit를 설정한다.
  • memblock_dump()
    • 주어진 타입의 memblock들 정보를 출력한다.
  • memblock_dump_all()
    • memory와 reserved 타입의 memblock들 정보를 출력한다.
  • memblock_allow_resize()
    • 전역변수 memblock_can_resize=1로 설정(resize 허용)
  • early_memblock()
    • early_param()으로 등록한 함수이며 인수로 “debug” 문자열을 받게되면 전역 변수 memblock_debug=1로 설정하여 memblock_dbg() 함수를 동작하게 한다.
  • memblock_debug_show()
    • DEBUG_FS가 동작중이면 debug 출력할 수 있다.

 

디버그 출력

CONFIG_DEBUG_FS 커널 옵션을 사용한 경우 다음과 같이 memblock 등록 상황을 확인할 수 있다.

  • 예) rpi2의 등록된 memory와 reserved된 메모리 블럭들을 보여준다.
# cat /sys/kernel/debug/memblock/memory
   0: 0x00000000..0x3affffff

# cat /sys/kernel/debug/memblock/reserved
   0: 0x00004000..0x00007fff   <- 페이지 테이블
   1: 0x00008240..0x0098c1ab   <- 커널
   2: 0x2fffbf00..0x2fffff08
   3: 0x39e9e000..0x39f95fff
   4: 0x39f989c4..0x3a7fefff
   5: 0x3a7ff540..0x3a7ff583
   6: 0x3a7ff5c0..0x3a7ff603
   7: 0x3a7ff640..0x3a7ff6b7
   8: 0x3a7ff6c0..0x3a7ff6cf
   9: 0x3a7ff700..0x3a7ff70f
  10: 0x3a7ff740..0x3a7ff743
  11: 0x3a7ff780..0x3a7ff916
  12: 0x3a7ff940..0x3a7ffad6
  13: 0x3a7ffb00..0x3a7ffc96
  14: 0x3a7ffc9c..0x3a7ffd14
  15: 0x3a7ffd18..0x3a7ffd60
  16: 0x3a7ffd64..0x3a7ffd7e
  17: 0x3a7ffd80..0x3a7ffd9b
  18: 0x3a7ffda4..0x3a7ffdbe
  19: 0x3a7ffdc0..0x3a7ffe37
  20: 0x3a7ffe40..0x3a7ffe43
  21: 0x3a7ffe48..0x3affffff

 

참고

 

이전 글 -> Memblock – (1)

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)

 

setup_dma_zone

DMA zone을 필요로하는 머신의 경우 DMA size 등의 정보를 설정한다.

  • CONFIG_ZONE_DMA 옵션 사용
    • 특정 메모리 영역을 ZONE_DMA에 구성해야 할 때 필요한 옵션이다.
    • CONFIG_DMADEVICES 옵션과 관련 없다.
  • ARM 아키텍처 중 다음 머신들이 DMA zone을 사용한다.
    • mach-prima2, Cortex-A9
    • mach-shmobile, Renesas ARM SoCs, LPAE
    • mach-mvenu, Marvell Engineering Business Unit (MVEBU) SoCs, LPAE
    • mach-imx, Freescale LS1021A, LPAE
    • mach-realview, RealView(R) Platform Baseboard Explore
    • mach-axxia, LSI Axxia platforms, LPAE
    • mach-highbank, Calxeda ECX-1000/2000 (Highbank/Midway), LPAE
    • mach-keystone, Texas Instruments Keystone Devices, LPAE
    • 기타: mach-ixp4xx, mach-pxa, mach-davinchi, mach-sa1100 등등
  • ARM 아키텍처에서 ZONE_DMA size는 보통 1M, 16M, 64M, 128M 또는 256M이다.
  • LPAE를 지원하는 시스템에서의 ZONE_DMA size는 보통 1G, 2G 또는 4G이다.
  • 라즈베리파이2: DMA zone을 사용하지 않는다.

 

CONFIG_ZONE_DMA

  • DMA를 사용하는 디바이스가 아키텍처가 사용하는 전체 물리주소 영역에 대해 대응하지 못하는 경우 DMA 디바이스의 사용 주소 범위를 좁혀줄 필요가 있다. 따라서 이러한 디바이스를 위해 영역을 제한한 메모리 영역을 지정하여 메모리의 할당 관리를 별도로 한다.
  • x86 시스템에서는 24비트 주소를 사용하는 ISA 버스 방식을 사용하는 디바이스가 있고 이러한 디바이스가 DMA 방식으로 RAM과 communication을 하기 위해 DMA 존 크기를 16M로 지정하여 사용해야 한다.
  • ARM 시스템에서는 머신 설계 마다 다르다. DMA 디바이스가 32bit address를 갖춘 경우에는 DMA 존을 별도로 운영할 필요가 없다. 일부 머신에서는 address가 제한된 DMA 디바이스를 사용하는 경우 그 머신에 맞추어 DMA 존 크기가 지정되어 운용되어야 한다.
  • 64bit 머신에서는 32bit용으로 설계된 DMA 디바이스를 위해 CONFIG_ZONE_DMA32를 운용한다.

 

setup_dma_zone()

  • 3가지의 DMA zone 관련 전역 변수를 설정한다.
    • arm_dma_ zone_size <- mdesc->dma_zone_size 대입
    • arm_dma_zone_limit <- 물리 RAM 주소 + dma_zone_size – 1
      • 예) dma_zone_size=32M이면 dma_zone_limit=0x01ff_ffff
      • dma_zone_size가 0인 경우 dma_zone_limit <- 0xffff_ffff
    • arm_dma_pfn_limit <- arm_dma_zone_limit를 12비트 우측으로 쉬프트시켜 물리주소의 PFN으로 변환한다.
arch/arm/mm/init.c
void __init setup_dma_zone(const struct machine_desc *mdesc)
{
#ifdef CONFIG_ZONE_DMA
        if (mdesc->dma_zone_size) {
                arm_dma_zone_size = mdesc->dma_zone_size;
                arm_dma_limit = PHYS_OFFSET + arm_dma_zone_size - 1;
        } else
                arm_dma_limit = 0xffffffff;
        arm_dma_pfn_limit = arm_dma_limit >> PAGE_SHIFT;
#endif
}

 

예) mach-realview/realview-dt.c

DT_MACHINE_START(REALVIEW_DT, "ARM RealView Machine (Device Tree Support)")
#ifdef CONFIG_ZONE_DMA
        .dma_zone_size  = SZ_256M,
#endif
        .dt_compat      = realview_dt_platform_compat,
        .l2c_aux_val = 0x0,
        .l2c_aux_mask = ~0x0,
MACHINE_END

 

setup_arch()

  • early_paging_init()의 경우 특별히 메모리 정보가 변경되는 머신의 경우만 메모리 재설정 루틴이 동작된다. LPAE 설정을 사용하는 경우 phisical to virtual transalation에 대한 추가 루틴도 수행된다.
    • 관련 머신: arch/arm/mach-keystone

setup_arch