Memblock – (2)

<kernel v5.0>

Early 메모리 할당

early 메모리 할당자로서 커널 부트업 프로세스 및 메모리 핫스왑(hot-swap) 과정에서 memblock API를 사용하여 메모리의 할당과 해제를 요청하게 된다.

 

paging_init()이 완료되기 전 memblock 사용

paging_init()이 완료되기 전에도 memblock_add() 함수로 메모리를 추가할 수 있고, memblock_reserve() 함수로 reserve 공간을 등록할 수 있다. 그러나 커널 메모리에 대해 아직 리니어 매핑되지 않은 상태이므로 memblock_alloc()으로 메모리 할당을 할 수 없다. 따라서 paging_init()이 완료된 후에 memblock_alloc()을 사용하여야 한다.

 

다음 그림은 정규 메모리 할당자가 준비되기 전의 주요 메모리 매핑과 할당자들의 단계별 상태를 보여준다.

 

다음 그림은 memblock 할당과 관련한 API들의 흐름을 보여준다.

 

memblock_alloc()

mm/memblock.c

static inline void * __init memblock_alloc(phys_addr_t size,  phys_addr_t align)
{
        return memblock_alloc_try_nid(size, align, MEMBLOCK_LOW_LIMIT,
                                      MEMBLOCK_ALLOC_ACCESSIBLE, NUMA_NO_NODE);
}

@align 단위로 정렬된 @size만큼의 memblock을 할당 요청한다. 성공 시 할당된 메모리의 가상 주소를 반환하고, 실패 시 null 을 반환한다.

  • MEMBLOCK_LOW_LIMIT(0)
    • 할당 시 top down으로 할당할 메모리의 아래쪽 끝을 제한한다.
  • MEMBLOCK_ALLOC_ACCESSIBLE(0)
    • 할당 시 memblock의 current_limit 값으로 제한한다.
  • NUMA_NO_NODE(-1)
    • 어떠한 노드에서 할당하든 제한 없이 요청한다.

 

memblock_alloc_try_nid()

mm/memblock.c

/**
 * memblock_alloc_try_nid - allocate boot memory block with panicking
 * @size: size of memory block to be allocated in bytes
 * @align: alignment of the region and block's size
 * @min_addr: the lower bound of the memory region from where the allocation
 *        is preferred (phys address)
 * @max_addr: the upper bound of the memory region from where the allocation
 *            is preferred (phys address), or %MEMBLOCK_ALLOC_ACCESSIBLE to
 *            allocate only from memory limited by memblock.current_limit value
 * @nid: nid of the free area to find, %NUMA_NO_NODE for any node
 *
 * Public panicking version of memblock_alloc_try_nid_nopanic()
 * which provides debug information (including caller info), if enabled,
 * and panics if the request can not be satisfied.
 *
 * Return:
 * Virtual address of allocated memory block on success, NULL on failure.
 */
void * __init memblock_alloc_try_nid(
                        phys_addr_t size, phys_addr_t align,
                        phys_addr_t min_addr, phys_addr_t max_addr,
                        int nid)
{
        void *ptr;

        memblock_dbg("%s: %llu bytes align=0x%llx nid=%d from=%pa max_addr=%pa %pF\n",
                     __func__, (u64)size, (u64)align, nid, &min_addr,
                     &max_addr, (void *)_RET_IP_);
        ptr = memblock_alloc_internal(size, align,
                                           min_addr, max_addr, nid);
        if (ptr) {
                memset(ptr, 0, size);
                return ptr;
        }

        panic("%s: Failed to allocate %llu bytes align=0x%llx nid=%d from=%pa max_addr=%pa\n",
              __func__, (u64)size, (u64)align, nid, &min_addr, &max_addr);
        return NULL;
}

노드 @nid에서 @align 단위로 정렬된 @size만큼의 memblock 영역을 할당 요청한다. 성공 시 할당된 메모리의 가상 주소를 반환하고, 실패 시 null 을 반환한다.

  • 할당에 대한 디버그 정보를 출력하고 할당되지 않는 경우 panic() 함수를 호출한다.

 

memblock_alloc_internal()

mm/memblock.c

/**
 * memblock_alloc_internal - allocate boot memory block
 * @size: size of memory block to be allocated in bytes
 * @align: alignment of the region and block's size
 * @min_addr: the lower bound of the memory region to allocate (phys address)
 * @max_addr: the upper bound of the memory region to allocate (phys address)
 * @nid: nid of the free area to find, %NUMA_NO_NODE for any node
 *
 * The @min_addr limit is dropped if it can not be satisfied and the allocation
 * will fall back to memory below @min_addr. Also, allocation may fall back
 * to any node in the system if the specified node can not
 * hold the requested memory.
 *
 * The allocation is performed from memory region limited by
 * memblock.current_limit if @max_addr == %MEMBLOCK_ALLOC_ACCESSIBLE.
 *
 * The phys address of allocated boot memory block is converted to virtual and
 * allocated memory is reset to 0.
 *
 * In addition, function sets the min_count to 0 using kmemleak_alloc for
 * allocated boot memory block, so that it is never reported as leaks.
 *
 * Return:
 * Virtual address of allocated memory block on success, NULL on failure.
 */
static void * __init memblock_alloc_internal(
                                phys_addr_t size, phys_addr_t align,
                                phys_addr_t min_addr, phys_addr_t max_addr,
                                int nid)
{
        phys_addr_t alloc;
        void *ptr;
        enum memblock_flags flags = choose_memblock_flags();

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

        /*
         * Detect any accidental use of these APIs after slab is ready, as at
         * this moment memblock may be deinitialized already and its
         * internal data may be destroyed (after execution of memblock_free_all)
         */
        if (WARN_ON_ONCE(slab_is_available()))
                return kzalloc_node(size, GFP_NOWAIT, nid);

        if (!align) {
                dump_stack();
                align = SMP_CACHE_BYTES;
        }

        if (max_addr > memblock.current_limit)
                max_addr = memblock.current_limit;
again:
        alloc = memblock_find_in_range_node(size, align, min_addr, max_addr,
                                            nid, flags);
        if (alloc && !memblock_reserve(alloc, size))
                goto done;

        if (nid != NUMA_NO_NODE) {
                alloc = memblock_find_in_range_node(size, align, min_addr,
                                                    max_addr, NUMA_NO_NODE,
                                                    flags);
                if (alloc && !memblock_reserve(alloc, size))
                        goto done;
        }

        if (min_addr) {
                min_addr = 0;
                goto again;
        }

        if (flags & MEMBLOCK_MIRROR) {
                flags &= ~MEMBLOCK_MIRROR;
                pr_warn("Could not allocate %pap bytes of mirrored memory\n",
                        &size);
                goto again;
        }

        return NULL;
done:
        ptr = phys_to_virt(alloc);

        /* Skip kmemleak for kasan_init() due to high volume. */
        if (max_addr != MEMBLOCK_ALLOC_KASAN)
                /*
                 * The min_count is set to 0 so that bootmem allocated
                 * blocks are never reported as leaks. This is because many
                 * of these blocks are only referred via the physical
                 * address which is not looked up by kmemleak.
                 */
                kmemleak_alloc(ptr, size, 0, 0);

        return ptr;
}

요청한 노드 @nid에서 @align 단위로 정렬된 @size만큼의 memblock 영역을 할당 요청한다. 할당된 물리 주소를 가상 주소로 변환하여 반환한다.

  • 코드 라인 10~12에서 어떠한 노드에서든 가리지 말고 할당해달라는 요청을 하기 위해 노드 번호인 @nid가 MAX_NUMNODES로 지정할 때 이를 NUMA_NO_NODE(-1)로 요청하라고 경고한다. MAX_NUMNODES는 deprecated 상태다.
  • 코드 라인 19~20에서 이 함수를 요청한 순간에 이미 슬랩 할당자가 동작하는 경우 memblock이 아닌 슬랩을 사용하는 kzalloc_node() 함수를 사용하여 할당하도록 한다.
  • 코드 라인 22~25에서 @align이 지정되지 않은 경우 캐시 라인 바이트 수로 지정한다.
  • 코드 라인 27~28에서 @max_addr이 memblock의 current_limit을 초과하지 않도록 제한한다.
  • 코드 라인 30~33에서 첫 번째 시도로 요청한 @nid와 범위내에서 @size의 memblock 공간을 찾아 할당을 시도한다.
  • 코드 라인 35~41에서 지정한 @nid 노드에서 할당이 실패한 경우 두 번째 시도로 모든 노드에서 다시 할당을 시도한다.
  • 코드 라인 43~46에서 최소 제한이 있는 경우 again: 레이블로 이동하여 위의 과정을 다시 시도한다.
  • 코드 라인 48~53에서 미러 요청이 있는 경우 again: 레이블로 이동하여 위의 과정을 다시 시도한다.
  • 코드 라인 55에서 모든 시도가 실패한 경우 null을 반환한다.
  • 코드 라인 57~69에서 할당이 성공한 경우 해당 물리 주소를 가상 주소로 변환한 후 반환한다.

 

지정된 범위내에서 memblock 할당

시스템에 의해 CMA(Contiguous Memory Allocation) 영역이 지정되는데 해당 영역 범위내에서 memblock 영역을 할당하려할 때 주로 사용된다.

  • 예) cma_declare_contiguous()

 

memblock_alloc_range()

mm/memblock.c

phys_addr_t __init memblock_alloc_range(phys_addr_t size, phys_addr_t align,
                                        phys_addr_t start, phys_addr_t end,
                                        enum memblock_flags flags)
{
        return memblock_alloc_range_nid(size, align, start, end, NUMA_NO_NODE,
                                        flags);
}

@align 단위로 정렬된 @size만큼의 memblock 영역을 @start ~ @end 범위에서 할당 요청한다. 성공한 경우 할당된 가상 주소가 반환된다.

 

memblock_alloc_range_nid()

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,
                                        enum memblock_flags flags)
{
        phys_addr_t found;

        if (!align) {
                /* Can't use WARNs this early in boot on powerpc */
                dump_stack();
                align = SMP_CACHE_BYTES;
        }

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

요청한 노드 @nid에서 @align 단위로 정렬된 @size만큼의 memblock 영역을 @start ~ @end 범위에서 할당 요청한다. 성공한 경우 할당된 가상 주소가 반환된다.

  • 코드 라인 8~12에서 @align이 지정되지 않은 경우 캐시 라인 바이트 수로 지정한다.
  • 코드 라인 14~23에서 주어진 범위내에서 free 영역을 찾은 후 해당 영역을 reserve한다.

 

memblock_reserve()

mm/memblock.c

int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
        phys_addr_t end = base + size - 1;

        memblock_dbg("memblock_reserve: [%pa-%pa] %pF\n",
                     &base, &end, (void *)_RET_IP_);

        return memblock_add_range(&memblock.reserved, base, size, MAX_NUMNODES, 0);
}

물리 주소 @base 부터 @size 만큼의 영역을 memblock의 reserved 영역에 추가한다.

 


Memblock 빈 영역 찾기

memblock_find_in_range()

mm/memblock.c

/**
 * memblock_find_in_range - find free area in given range
 * @start: start of candidate range
 * @end: end of candidate range, can be %MEMBLOCK_ALLOC_ANYWHERE or
 *       %MEMBLOCK_ALLOC_ACCESSIBLE
 * @size: size of free area to find
 * @align: alignment of free area to find
 *
 * Find @size free area aligned to @align in the specified range.
 *
 * Return:
 * Found address on success, 0 on failure.
 */
phys_addr_t __init_memblock memblock_find_in_range(phys_addr_t start,
                                        phys_addr_t end, phys_addr_t size,
                                        phys_addr_t align)
{
        phys_addr_t ret;
        enum memblock_flags flags = choose_memblock_flags();

again:
        ret = memblock_find_in_range_node(size, align, start, end,
                                            NUMA_NO_NODE, flags);

        if (!ret && (flags & MEMBLOCK_MIRROR)) {
                pr_warn("Could not allocate %pap bytes of mirrored memory\n",
                        &size);
                flags &= ~MEMBLOCK_MIRROR;
                goto again;
        }

        return ret;
}

@start ~ @end 범위에서 @align 요청된 @size 만큼의 공간을 reserved memblock 타입에서 찾는다.

  • 미러 플래그가 요청된 상태에서 공간을 찾지 못한 경우 미러 플래그를 제거하고 다시 공간을 찾아본다.

 

memblock_find_in_range_node()

mm/memblock.c

/**
 * memblock_find_in_range_node - find free area in given range and node
 * @size: size of free area to find
 * @align: alignment of free area to find
 * @start: start of candidate range
 * @end: end of candidate range, can be %MEMBLOCK_ALLOC_ANYWHERE or
 *       %MEMBLOCK_ALLOC_ACCESSIBLE
 * @nid: nid of the free area to find, %NUMA_NO_NODE for any node
 * @flags: pick from blocks based on memory attributes
 *
 * Find @size free area aligned to @align in the specified range and node.
 *
 * When allocation direction is bottom-up, the @start should be greater
 * than the end of the kernel image. Otherwise, it will be trimmed. The
 * reason is that we want the bottom-up allocation just near the kernel
 * image so it is highly likely that the allocated memory and the kernel
 * will reside in the same node.
 *
 * If bottom-up allocation failed, will try to allocate memory top-down.
 *
 * Return:
 * Found address on success, 0 on failure.
 */
phys_addr_t __init_memblock memblock_find_in_range_node(phys_addr_t size,
                                        phys_addr_t align, phys_addr_t start,
                                        phys_addr_t end, int nid,
                                        enum memblock_flags flags)
{
        phys_addr_t kernel_end, ret;

        /* pump up @end */
        if (end == MEMBLOCK_ALLOC_ACCESSIBLE ||
            end == MEMBLOCK_ALLOC_KASAN)
                end = memblock.current_limit;

        /* avoid allocating the first page */
        start = max_t(phys_addr_t, start, PAGE_SIZE);
        end = max(start, end);
        kernel_end = __pa_symbol(_end);

        /*
         * try bottom-up allocation only when bottom-up mode
         * is set and @end is above the kernel image.
         */
        if (memblock_bottom_up() && end > kernel_end) {
                phys_addr_t bottom_up_start;

                /* make sure we will allocate above the kernel */
                bottom_up_start = max(start, kernel_end);

                /* ok, try bottom-up allocation first */
                ret = __memblock_find_range_bottom_up(bottom_up_start, end,
                                                      size, align, nid, flags);
                if (ret)
                        return ret;

                /*
                 * we always limit bottom-up allocation above the kernel,
                 * but top-down allocation doesn't have the limit, so
                 * retrying top-down allocation may succeed when bottom-up
                 * allocation failed.
                 *
                 * bottom-up allocation is expected to be fail very rarely,
                 * so we use WARN_ONCE() here to see the stack trace if
                 * fail happens.
                 */
                WARN_ONCE(IS_ENABLED(CONFIG_MEMORY_HOTREMOVE),
                          "memblock: bottom-up allocation failed, memory hotremove may be affected\nn
");
        }

        return __memblock_find_range_top_down(start, end, size, align, nid,
                                              flags);
}

요청한 노드 @nid에서 @start ~ @end 범위에서 @align 요청된 @size 만큼의 공간을 reserved memblock 타입에서 찾는다.

  • 코드 라인 9~11에서 limit 범위내에서 검색을 하라고 한 경우 @end를 memblock의 current_limit로 제한한다.
  • 코드 라인 14에서 @start는 0 페이지를 제외한다.
  • 코드 라인 22~48에서 아키텍처에 따라 bottom-up으로 검색을 시도한다. 이 때 @end가 커널 공간보다는 상위에 있어야 한다.
    • 커널 버전 3.13-rc1부터 bottom up 메모리 할당 기능이 추가되었다.
  • 코드 라인 49~50에서 top-down으로 검색을 시도한다.
    • ARM, ARM64, x86 NUMA, 등등에서 top-down을 사용한다.

 

MEMBLOCK_HOTPLUG
  • 리눅스에 메모리 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

 

MEMBLOCK_MIRROR
  • x86 XEON 서버 시스템 기능 중에 메모리를 dual로 mirror 설정하여 고신뢰성을 확보할 수 있다. 전체 메모리를 mirror 설정하는 경우에는 커널 소프트웨어가 개입할 필요가 없다. 그러나 전체 메모리가 아닌 일부 메모리만 mirror 설정하여 사용하여야 할 때에는 커널 메모리에 우선 순위를 두고 사용할 수 있도록 등록된 memory memblock 영역에 이 플래그를 설정하여 사용한다. 이러한 정보는 서버 시스템의 UEFI 펌웨어가 mirror 설정을 커널에 전달하여 구성한다.

참고: address range mirror (2016) | Taku Izumi – 다운로드 pdf

 

다음 그림은 memblock_find_in_range() 함수의 처리 과정을 보여준다.

memblock4

 

__memblock_find_range_top_down()

mm/memblock.c

/**
 * __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 or
 *       %MEMBLOCK_ALLOC_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
 * @flags: pick from blocks based on memory attributes
 *
 * Utility called from memblock_find_in_range_node(), find free area top-down.
 *
 * Return:
 * 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,
                               enum memblock_flags flags)
{
        phys_addr_t this_start, this_end, cand;
        u64 i;

        for_each_free_mem_range_reverse(i, nid, flags, &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;
}

메모리가 존재하는 구간의 reserved 안된 공간을 대상으로 위에서 아래로 검색하며 요청한 노드 @nid의 @start ~ @end 범위내에 @align된 @size 만큼의 공간이 발견되면 그 물리 주소의 시작주소를 반환한다.

  • 코드 라인 9~12에서 빈 memblock 공간을 루프를 돌며 하나씩 알아온다.
  • 코드 라인 14~15에서 아래 cand를 산출할 때 this_end – size를 수행해야 하는데 산출된 결과가 0보다 작아 under-flow 시 미리 skip을 하도록 한다.
  • 코드 라인 17~19에서 요청 사이즈의 비교는 align된 크기로 한다. 알아온 free 영역의 범위에 size가 포함될 수 있으면 성공적으로 cand 주소를 리턴한다.

아래의 그림에서 __memblock_find_range_top_down() 함수가 동작하면 for_each_free_mem_range_reverse()를 통해 7개의 free 공간이 얻어지며, start 에서 end까지의 범위에 포함되는 케이스가 4개로 압축이 되고 1번 부터 4번 까지 순서로 요청 사이즈가 포함되는 매치 조건에 적합하면 이 공간의 주소를 리턴한다.

__memblock_find_range_top_down-1b

 

for_each_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++)

지정한 타입의 memblock 영역 전체를 처음부터 끝까지 loop를 돌며 @region 변수에 각각의 memblock_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))

요청한 노드 @nid에서 파편화된 페이지를 제외한 즉 온전한 페이지가 1개 이상인 memblock 영역을 조건으로 루프를 돈다. 매치된 memblock 영역의 정보로 @i에는 memblock 영역의 인덱스 번호, @p_start에는 파편화된 페이지가 아닌 온전한 페이지의 시작 pfn, @p_end에는 파편화된 페이지가 아닌 온전한 페이지의 끝 pfn + 1을 담고, 마지막으로 노드 id를 @p_nid에 알아온다.

 

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

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

 

__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;
}

다음 memblock 영역부터 시작하여 요청한 @nid 이면서 파편화되지 않은 페이지들을 대상으로 시작 pfn과 끝 pfn+1값을  @out_start_pfn 및 @out_end_pfn에 알아온다. 그리고 매치된 노드의 id 값도 @out_nid에 알아온다.

  • 코드 라인 8~12에서 memory memblock 타입에 등록된 영역 중 요청한 @idx 영역을 제외하고 그 영역부터 루프를 돈다.
  • 코드 라인 14~15에서 memblock 영역에 파편화되지 않은 온전한 1개 이상의 페이지가 포함되지 않은 경우 skip 한다.
  • 코드 라인 16~17에서 요청 노드 @nid와 영역의 노드가 매치되었거나 또는 any 노드 요청인 경우 루프를 탈출한다.
  • 코드 라인 19~22에서 루프가 끝날 때까지 매치되지 않았으면 idx에 -1을 담고 리턴한다.
  • 코드 라인 24~25에서 out_start_pfn에 페이지가 파편화되지 않고 온전히 포함된 시작 pfn을 담는다.
  • 코드 라인 26~27에서 out_end_pfn에 페이지가 파편화되지 않고 온전히 포함된 끝 pfn을 담는다.
  • 코드 라인 28~29에서 out_nid에 memblock 영역의 노드 id를 담는다.

 

아래 그림은 7개의 case 별로 __next_mem_pfn_range() 함수가 실행될 때 요청 idx+1부터 조건에 매치되는 블럭을 찾는 모습을 보여준다. 아래 그림 역시도 실제 memory memblock 들의 사이즈보다 훨씬 축소를 해놨기 때문에 계산 방법에 대해 이해를 돕는 것으로 활용해야 한다

  • A)의 경우 파편화되지 않은 온전한 1개 페이지가 없는 경우이다.
  • B)의 경우 노드 id를 지정한 경우이다.
  • @idx
    • 매치된 memblock 영역의 인덱스
  • @out_start_pfn
    • 파편화되지 않은 페이지의 시작 pfn
  • @out_end_pfn
    • 파편화되지 않은 페이지의 끝 pfn + 1
  • @out_nid
    • 매치된 memblock 영역의 노드 id

 

for_each_free_mem_range()

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
 * @flags: pick from blocks based on memory attributes
 * @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, flags, p_start, p_end, p_nid)          \
        for_each_mem_range(i, &memblock.memory, &memblock.reserved,     \
                           nid, flags, p_start, p_end, p_nid)

루프를 돌며 지정된 노드 @nid의 memory 영역에서 reserved 영역을 제외한 영역이 free 메모리이며 이 영역을 순서대로 시작 물리 주소를 @p_start, 끝 물리 주소를 @p_end, 노드 id를 @p_nid, 플래그를 @flags에 알아온다.

 

for_each_mem_range()

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
 * @flags: pick from blocks based on memory attributes
 * @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, flags,               \
                           p_start, p_end, p_nid)                       \
        for (i = 0, __next_mem_range(&i, nid, flags, type_a, type_b,    \
                                     p_start, p_end, p_nid);            \
             i != (u64)ULLONG_MAX;                                      \
             __next_mem_range(&i, nid, flags, type_a, type_b,           \
                              p_start, p_end, p_nid))

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

  • 인자 @type_b는 null로 주어질 수도 있다.

 

아래와 같이 우측 memory memblock 타입의 영역내에서 reserved memblock 타입의 영역들을 제외한 영역이 좌측 회색 박스의 free 영역이 매치된 영역이다. 그 매치된 영역들을 대상으로 루프가 제공된다.

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
 * @flags: pick from blocks based on memory attributes
 * @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,
                                      enum memblock_flags flags,
                                      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;

        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;

                /* if we want mirror memory skip non-mirror memory regions */
                if ((flags & MEMBLOCK_MIRROR) && !memblock_is_mirror(m))
                        continue;

                /* skip nomap memory unless we were asked for it explicitly */
                if (!(flags & MEMBLOCK_NOMAP) && memblock_is_nomap(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;
                }

reserved memblock 영역들 사이에서 노드 @nid 값과 플래그 @flags에 해당하는 영역을 대상으로 요청한 인덱스 @idx의 다음 free 공간을 찾아 반환한다. 64비트 @idx에 memory 타입 memblock 영역들의 인덱스와 reserved 타입 memblock 영역들의 인덱스를 반반 합쳐서 인덱스를 지정하여 요청한다. 출력 인자인 @out_start 및 @out_end에 찾은 free 영역의 시작 물리 주소와 끝 물리 주소를 알아온다. 또한 @out_nid에도 노드 id를 알아온다.

  • 코드 라인 8~9에서 idx 값을 절반으로 나누어 lsb 쪽을 idx_a의 카운터로 사용하고, msb 쪽을 idx_b의 카운터로 사용한다.
  • 코드 라인 11~13에서 노드 id 인수 값으로 deprecated된 MAX_NUMNODES를 사용하면 경고문을 출력한다.
  • 코드 라인 15~24에서 type_a의 memblock 영역을 대상으로 루프를 돌며 요청한 @nid가 아니면서 any 노드를 요청한 경우도 아니면 skip 한다.
    • 이 함수가 호출될 떄 type_a는 memory 타입, type_b는 reserved 타입이다.
    • m_start와 m_end는 현재 1차 루프 인덱스의 memblock 영역의 시작 주소와 끝 주소이다.
  • 코드 라인 27~28에서 hotplug 및 movable 노드 전용인 경우 skip 한다.
  • 코드 라인 31~32에서 mirror 플래그 요청되었지만 mirror 영역이 아닌 경우 skip 한다.
  • 코드 라인 35~36에서 nomap 플래그가 없는데 nomap 영역인 경우 skip 한다.
  • 코드 라인 38~48에서 type_b에 대한 영역이 지정되지 않으면(null) 현재 1차 루프 인덱스의 memblock에 대한 영역으로 out_start와 out_end를 결정하고 idx_a 만을 1 증가 시키고 함수를 성공리에 빠져나간다.

 

                /* 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 : PHYS_ADDR_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;
}
  • 코드 라인 2~10에서 type_b의 memblock 영역들 + 1만큼 2차 루프를 돈다.
    • r 영역은 현재 2차 루프 인덱스가 가리킨 곳의 영역을 가리킨다.
    • r_start는 idx_b가 0보다 크면 현재 이전 memblock의 끝 주소를 가리키고 idx_b가 0이면 0번 주소를 지정한다.
    • r_end는 idx_b가 등록된 갯수보다 작은 경우 r 영역의 시작주소를 지정하고 아니면 시스템 최대 주소를 지정한다.
  • 코드 라인 16~17에서 reserve memblock 영역이 memory memblock 영역을 벗어난 경우 2차 루프를 빠져나가서 다음 memory memblock을 준비하도록 한다.
    • 조건 A) r_start >= m_end
  • 코드 라인 19~26에서 두 영역이 교차하는 경우이다. out_start에 하단 reserve 영역값의 끝 주소나 memory 영역값의 시작 주소중 가장 큰 주소를 담는다. 그리고 out_end에 상단 reserve 영역값의 시작 주소나 memory 영역값의 끝 주소중에 가장 작은 주소를 담는다.
    • 조건 B) m_start < r_end
  • 코드 라인 31~36에서 reserve 영역의 끝 주소가 memory 영역의 끝주소와 비교하여 큰 경우 idx_a를 증가시키고 다음 memory block을 준비하기 위해 빠져나가고, 크지 않은 경우 idx_b를 증가시키고 계속하여 다음 reserve 영역을 준비하기 위해 빠져나간다.
    • 조건 B-1) m_end < r_end
  • 코드 라인 42에서 1차 루프를 끝까지 완료하면 더 이상 처리할 수 없으므로 idx에 ULLONG_MAX(시스템 최대 주소) 값을 부여하고 빠져나간다.

 

다음 그림은 __next_mem_range() 함수가 reserve된 영역들 사이의 free 영역을 찾아 루프를 돌 때 free 영역의 산출에 사용되는 case 들을 보여준다.

 

아래 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()

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
 * @flags: pick from blocks based on memory attributes
 * @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, flags, p_start, p_end,  \
                                        p_nid)                          \
        for_each_mem_range_rev(i, &memblock.memory, &memblock.reserved, \
                               nid, flags, p_start, p_end, p_nid)

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

 

for_each_mem_range_rev()

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
 * @flags: pick from blocks based on memory attributes
 * @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, flags,           \
                               p_start, p_end, p_nid)                   \
        for (i = (u64)ULLONG_MAX,                                       \
                     __next_mem_range_rev(&i, nid, flags, type_a, type_b,\
                                         p_start, p_end, p_nid);        \
             i != (u64)ULLONG_MAX;                                      \
             __next_mem_range_rev(&i, nid, flags, type_a, type_b,       \
                                  p_start, p_end, p_nid))

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

  • 인덱스는 64비트 값이며 절반씩 나누어 상위 32비트는 memory memblock 영역에 대한 인덱스를 가리키고, 하위 32비트는 reserved memblock 영역에 대한 인덱스를 가리킨다.
    • 처음 시작 시 역순으로 루프를 시작하므로 ~0UUL 값이 대입되어 시작한다.
  • ARM은 free 메모리를 할당하기 위해 검색시 이 top down 방식의 매크로를 사용한다.

 

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

memblock15

 

아래와 같이 memory region이 여러 개일 때 i 값의 추적을 표현하였다.

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

 

__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()를 reverse하는 것 이외에 코드가 동일하므로 소스 해석은 하지 않는다.
/**
 * __next_mem_range_rev - generic next function for for_each_*_range_rev()
 *
 * @idx: pointer to u64 loop variable
 * @nid: node selector, %NUMA_NO_NODE for all nodes
 * @flags: pick from blocks based on memory attributes
 * @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
 *
 * Finds the next range from type_a which is not marked as unsuitable
 * in type_b.
 *
 * Reverse of __next_mem_range().
 */
void __init_memblock __next_mem_range_rev(u64 *idx, int nid,
                                          enum memblock_flags flags,
                                          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 inn
stead\n"))
                nid = NUMA_NO_NODE;

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

        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 we want mirror memory skip non-mirror memory regions */
                if ((flags & MEMBLOCK_MIRROR) && !memblock_is_mirror(m))
                        continue;

                /* skip nomap memory unless we were asked for it explicitly */
                if (!(flags & MEMBLOCK_NOMAP) && memblock_is_nomap(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 : PHYS_ADDR_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 – ARM32

# 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

 

예) rock960 – ARM64

$ /sys/kernel/debug/memblock$ cat memory
   0: 0x0000000000200000..0x00000000f7ffffff

$ /sys/kernel/debug/memblock$ cat reserved
   0: 0x0000000002080000..0x00000000033b5fff  <- 커널
   1: 0x00000000ef400000..0x00000000f5dfffff
   2: 0x00000000f5eef000..0x00000000f5f01fff
   3: 0x00000000f6000000..0x00000000f7bfffff
   4: 0x00000000f7df4000..0x00000000f7df4fff
   5: 0x00000000f7df5e00..0x00000000f7df5fff
   6: 0x00000000f7e74000..0x00000000f7f61fff
   7: 0x00000000f7f62600..0x00000000f7f6265f
   8: 0x00000000f7f62680..0x00000000f7f626df
   9: 0x00000000f7f62700..0x00000000f7f6282f
  10: 0x00000000f7f62840..0x00000000f7f62857
  11: 0x00000000f7f62880..0x00000000f7f62887
  12: 0x00000000f7f648c0..0x00000000f7f6492b
  13: 0x00000000f7f64940..0x00000000f7f649ab
  14: 0x00000000f7f649c0..0x00000000f7f64a2b
  15: 0x00000000f7f64a40..0x00000000f7f64a47
  16: 0x00000000f7f64a64..0x00000000f7f64aea
  17: 0x00000000f7f64aec..0x00000000f7f64b1a
  18: 0x00000000f7f64b1c..0x00000000f7f64b4a
  19: 0x00000000f7f64b4c..0x00000000f7f64b7a
  20: 0x00000000f7f64b7c..0x00000000f7f64baa
  21: 0x00000000f7f64bac..0x00000000f7fcdff7
  22: 0x00000000f7fce000..0x00000000f7ffffff

 

참고

 

 

Memblock – (1)

<kernel v5.0>

Memblock

memblock 메모리 할당자는 커널 부트업 타임에 가장 먼저 활성화되는 메모리 할당자로, 다른 커널 메모리 할당자가 준비되기 전에 메모리 범위를 등록하여 사용한다. 주로 부팅 타임에 사용되지만 메모리 핫플러그 기능이 활성화되어 있으면 런타임에도 사용한다. 2010년 커널 v2.6.35에서 memblock이 처음 소개되어 사용하기 전에는 bootmem이라는 메모리 할당자를 사용했었다. bootmem이 부트업에 필요한 메모리의 일부(lowmem)만을 관리하는 것에 비해 memblock은 전체 메모리를 관리한다. 커널에서 페이지 할당자로 사용하는 버디 시스템이 준비되기 전까지 메모리 할당을 먼저(early) 할 수 있는 메모리 관리자는 memblock이 유일하다. 따라서 early 메모리 할당자라는 용어를 사용한다. 또한 memblock은 LMB(Logical Memory Block)라고 알려져 있다.

 

Memblock의 구조

memblock은 다음과 같이 세 가지 타입으로 나누어 관리하고 있다

  • memory 타입
    •  memory 타입은 사용할 물리 메모리 영역을 등록하여 사용한다. 커널 파라미터에 의해 실제 물리 메모리의 일부 영역만을 사용하게끔 제한되어 등록 가능하다. 처음에는 regions[ ] 배열에 최대 128개의 영역을 사용할 수 있고, 추후 2배 단위로 계속 확장될 수 있다.
  • reserved 타입
    • reserved 타입은 사용 중이거나 사용할 물리 메모리 영역을 등록하여 사용한다. 처음에는 regions[ ] 배열에 최대 128개의 영역을 사용할 수 있고, 추후 2배 단위로 계속 확장될 수 있다.
  • physmem 타입
    • physmem 타입은 2014년에 추가된 타입이며, 물리적으로 감지된 메모리 영역을 등록하여 사용되고 이 값은 등록된 후 수정되지 않는다는 특성이 있다. memory 타입과 달리 커널 파라미터에 의해 제한된 메모리가 아닌 실제 물리 메모리 크기를 등록하여 사용할 계획으로 코드가 추가되었고, 현재 s390 아키텍처에서 사용하고 있다. regions[ ] 배열로 처음에 최대 4개의 엔트리를 사용한다.

 

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

memblock1

 

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 에서 제어할 수 있는 옵션이다.
  • 이 옵션이 사용되면 코드 부분과 데이터 부분이 각각 .meminit.text  및 .meminit.data 섹션을 사용하게 컴파일러에게 지시한다.
  • 이 옵션을 사용하지 않으면 memblock 섹션을 지정하지 않는다. 지정하지 않으면 default인 .text와 .data 섹션에 위치하므로 메모리에 항상 상주한다.

 

include/linux/memblock.h

#ifdef CONFIG_ARCH_DISCARD_MEMBLOCK
#define __init_memblock __meminit
#define __initdata_memblock __meminitdata
void memblock_discard(void);
#else
#define __init_memblock
#define __initdata_memblock
#endif

 

include/linux/init.h

#define __meminit        __section(.meminit.text) __cold notrace \
                                                  __latent_entropy
#define __meminitdata    __section(.meminit.data)

 

CONFIG_MEMORY_HOTPLUG

  • CONFIG_ARCH_DISCARD_MEMBLOCK 옵션을 사용하는 경우에만 다음과 같이 동작한다.
    • CONFIG_MEMORY_HOTPLUG 옵션을 사용하지 않는 경우 부트 프로세스가 끝나고 나면 memblock 영역을 삭제할 수 있다. 이러한 경우 부팅 이후에 memblcok을 사용하면 안된다.
    • CONFIG_MEMORY_HOTPLUG를 사용하는 경우 부트 프로세스가 끝나더라도 계속 사용할 수 있도록 보존 한다.

 

include/asm-generic/vmlinux.lds.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

 

초기화

전역 구조체명 memblock은 컴파일 타임에 아래와 같이 초기화된다.

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

 

memblock_region 배열

mm/memblock.c

static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblocc
k;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_RESERVED_REGIONS] __initdd
ata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS] __initdata_memblocc
k;
#endif

컴파일 타임에 세 가지 타입의 memblock 영역에 사용할 엔트리 배열의 개수를 지정한다. 배열의 크기는 위에서 아래 순서대로 각각 128, 128, 4개이다. 물리 메모리 등록은 몇 번 등록하지 않으므로 최대 4개로 제한된 상태이다.

 

memblock 배열

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,
        .memory.name            = "memory",

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

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

        .bottom_up              = false,
        .current_limit          = MEMBLOCK_ALLOC_ANYWHERE,
};

컴파일 타임에 3 가지 타입의 memblock 영역을 관리하는 memblock을 준비한다. 처음에 부트업에서는 각 memblock 영역들은 static 한 배열을 가리키고 있다가, 확장이 필요할 때 dynamic하게 추가될 수 있다.

  • 코드 라인 2~5에서 memory memblock을 128개 엔트리 배열로 초기화한다.
  • 코드 라인 7~10에서 reserved memblock을 128개 엔트리 배열로 초기화한다. 이 배열의 크기는 추후 reserved 영역이 등록되어 가득 차면 2배 단위로 확장하여 사용할 수 있다.
  • 코드 라인 12~17에서 physmem memblock을 4개 엔트리 배열로 초기화한다.
  • 코드 라인 19에서 비어 있는 영역의 검색을 위에서 아래 주소 방향으로 수행하도록 초깃값으로 설정한다.
  • 코드 라인 20에서 최대 메모리 할당 제한 값을 시스템이 사용하는 주소의 가장 큰 주소를 갖게 하여 제한이 없는 상태로 초기화한다

 

다음 그림은 memblock, memblock_type 및 memblock_regions 구조체들 사이의 연관성을 보여준다.

 

주요 구조체

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
};
  • bottom_up
    • 메모리 할당을 아래에서 위로 검색하여 빈 공간을 할당할 수 있도록 하는 옵션으로, 현재 x86_64 NUMA 시스템에 먼저 적용되어 있다. NUMA 시스템에서 메모리 핫플러그 기능을 사용하는 경우 효과가 있다. 커널이 할당한 메모리가 최대한 커널이 있는 low 메모리 주소 근처에 할당하게 유도하여 특정 노드의 메모리를 시스템에서 오프(off)할 때 그 노드에서 이미 할당되어 사용하는 페이지들이 적은 경우 마이그레이션(migration)되는 비율을 최대한 억제할 수 있기 때문에 설계된 기능이다(1 = 아래에서 위로 할당).
  • current_limit
    • 커널 부트업 프로세스 루틴에서 커널 메모리의 할당을 제한하고자 하는 경우에 사용한다. 64비트 시스템에서 모든 물리 메모리에 대해 가상 메모리에 1:1 direct 매핑되어 있으므로 별도로 lowmem 영역으로 관리하지 않기 때문에 current_limit에는 커널 초기 빌드 시 주어진 사용되는 MEMBLOCK_ALLOC_ANYWHERE(0xffffffff_ffffffff) 값으로 지정된다.
  • memory
    • 물리 메모리 영역 등록
  • reserved
    • reserved 영역 등록
  • physmem
    • 물리 메모리 영역 등록(일부 아키텍처 사용)

 

struct memblock_type

include/linux/memblock.h

struct memblock_type {
        unsigned long cnt;
        unsigned long max;
        phys_addr_t total_size;
        struct memblock_region *regions;
        char *name;
};
  • cnt
    • 사용 영역 엔트리 수다. 엔트리 영역이 한 건도 등록되지 않아도 1부터 시작하도록 설계되었다. 첫 엔트리 영역이 추가되는 경우 이 값은 바뀌지 않고, 두 번째 엔트리가 추가될 때마다 증가된다.
  • max
    • 최대 사용 가능한 영역 엔트리 수
  • total_size
    • 해당 memblock 타입의 등록된 모든 영역의 크기를 더한 값(bytes)
  • *regions
    • 영역을 가리키는 포인터
  • *name
    • 영역명

 

struct memblock_region

include/linux/memblock.h

struct memblock_region {
        phys_addr_t base;
        phys_addr_t size;
        enum memblock_flags flags;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
        int nid;
#endif
};
  • base
    • 시작 물리 주소
  • size
    • 영역 크기
  • flags
    • flags에는 다음과 같이 4개의 매크로 상수를 사용하고 3개의 비트 요청을 조합하여 사용할 수 있다.
      • MEMBLOCK_NONE(0x0): 특이 요청 없는 영역
      • MEMBLOCK_HOTPLUG(0x1): 메모리 핫플러그 영역
      • MEMBLOCK_MIRROR(0x2): 미러된 영역
      • MEMBLOCK_NOMAP(0x4): 커널이 직접 매핑하지 않는 영역
  • nid
    • 노드 id

 


 

Memblock의 기본 관리 API

메모리를 추가할 때 멀티 노드(NUMA 등) 시스템이 아닌 경우 memblock_add( ) 함수를 사용하면 내부에서 노드 id는 0으로 처리된다. 그리고 멀티 노드 시스템을 사용하는 경우 memblock_add_node( ) 함수를 사용하여 노드 id까지 지정을 한다. 머신을 설계할 때 등록되는 메모리 영역이 서로 겹치지 않도록 되어 있지만, 여러 가지 예외 처리를 위해 다양한 상황에서 요청한 영역이 겹치지 않도록 조정을 한다.

 

메모리 영역 추가

memblock_add()

mm/memblock.c

/**
 * memblock_add - add new memblock region
 * @base: base address of the new region
 * @size: size of the new region
 *
 * Add new memblock region [@base, @base + @size) to the "memory"
 * type. See memblock_add_range() description for mode details
 *
 * Return:
 * 0 on success, -errno on failure.
 */
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
        phys_addr_t end = base + size - 1;

        memblock_dbg("memblock_add: [%pa-%pa] %pF\n",
                     &base, &end, (void *)_RET_IP_);

        return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}

메모리 영역을 memory memblock에 추가한다.

  • 물리 메모리 시작 주소 @base 부터 @size 만큼을 memory memblock에 추가한다.

 

reserve 영역 추가

memblock_reserve()

mm/memblock.c

int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
        phys_addr_t end = base + size - 1;

        memblock_dbg("memblock_reserve: [%pa-%pa] %pF\n",
                     &base, &end, (void *)_RET_IP_);

        return memblock_add_range(&memblock.reserved, base, size, MAX_NUMNODES, 0);
}

reserved 영역을 reserved memblock에 추가한다.

  • 물리 메모리 시작 주소 @base 부터 @size 만큼을 reserved memblock에 추가한다.

 

영역 추가

memblock_add_range()

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, enum memblock_flags flags)
{
        bool insert = false;
        phys_addr_t obase = base;
        phys_addr_t end = base + memblock_cap_size(base, &size);
        int idx, nr_new;
        struct memblock_region *rgn;

        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 accommodate the new area.  The second actually inserts them.
         */
        base = obase;
        nr_new = 0;

        for_each_memblock_type(idx, type, rgn) {
                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) {
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
                        WARN_ON(nid != memblock_get_region_node(rgn));
#endif
                        WARN_ON(flags != rgn->flags);
                        nr_new++;
                        if (insert)
                                memblock_insert_region(type, idx++, 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, idx, base, end - base,
                                               nid, flags);
        }

        if (!nr_new)
                return 0;

        /*
         * 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 영역을 추가하는데, 기존 memblock 영역들과 중복되는 곳은 중복되지 않게 사이사이에 끼워 넣고 마지막으로 인접 memblock과 경계가 붙어 있고 플래그 타입이 같은 memblock들을 merge한다. return 값은 항상 0이다.

  • 코드 라인 15~23에서 memblock 영역이 비어 있다면 중복 체크 없이 첫 memblock 영역에 대한 설정을 하고 함수를 종료한다.
  • 코드 라인 24에서 repeat 레이블을 통해 한 번 반복되는데, 첫 번째 수행을 first round라고 하고 다음번 수행을 second round라고 할 때 first round에서는 루프를 돌때 끼워 넣어야 할 memblock 수를 체크할 목적으로 카운터(nr_new)만 증가시킨다. second round에서는 루프를 돌며 실제로 memblock을 곳곳에 인접 memblock과 중복되지 않게 끼워 넣는다.
  • 코드 라인 33~35에서 요청한 memblock 타입들에 대해 루프를 돈다.
  • 코드 라인 37~38에서 new memblock 영역이 비교하는 memblock 영역의 하단에 위치하여 겹치지 않으므로 더이상 반복 루프를 진행할 필요가 없어서 루프를 빠져나간다.
    • 아래 그림의 (A)에 해당한다. 참고로 regions[] 배열은 base 주소 순으로 정렬되어 있다. regions[0]에 가장 하위의 base 주소가 위치한다.
  • 코드 라인 39~40에서 new memblock 영역이 비교하는 memblock 영역의 상부에 위치하여 겹치지 않으므로 스킵하여 다음 memblock과 비교하도록 한다.
    • 아래 그림의 (B)에 해당한다.
  • 코드 라인 45~55 new memblock 영역이 비교하는 memblock 영역과 겹치므로 요청한 new memblock을 이 위치에 끼워 넣는데, 그 영역은 비교하는 memblock 영역과 겹치지 않는 영역의 크기만큼으로 조절한다.
    • 아래 그림의 (C)에 해당한다.
  • 코드 라인 57에서 base를 끼워 넣어야 할 때를 대비하여 끼워 넣을 memblock 영역의 시작 주소를 미리 설정해놓는다.
  • 코드 라인 61~66에서 루프 종료 후 new memblock 영역의 끝부분이 상부로 돌출되어 남아 있다면 그 돌출된 부분만을 memblock으로 추가한다.
    • 아래 그림의 (D)에 해당한다.
  • 코드 라인 75~80에서 first round의 경우 기존 memblock들과 끼워 넣을 memblock들의 합이 최대 관리 개수를 넘어가는 경우에 한해 memblock 영역의 엔트리 수를 두 배 더 크게 넓히기 위해 memblock_double_array( ) 함수를 호출한다. 이 때 충분한 엔트리 개 수가 준비될 때까지 반복한다.
  • 코드 라인 81~84에서 second round에서 끼워 넣은 memblock들에 대해 주변 memblock들과 인접하고 flag 타입이 동일한 memblock들을 memblock_merge_regions( ) 함수를 사용하여 merge한다.

 

다음 그림은 요청한 메모리 영역을 추가하는 흐름을 보여준다

memblock2

 

아래 그림에서 회색으로 6개의 기존 영역이 등록되어 있고, 하늘색으로 새로운 memblock을 6가지 케이스에 대해 추가할 때 region[0]부터 화살표 방향으로 비교하는 모습을 보여준다.

  • 추가할 영역이 기존 memblock 영역과 겹치는 경우 일단 겹쳐지지 않는 영역으로 쪼개어 추가한 후 나중에 인접 블럭들끼리 최종 merge한다.
  • insertion 수
    • memblock이 추가될 때 실제 insertion이 일어나는 개수
  • merge 후 cnt 수
    • 추후 merge된 후 최종적으로 남게되는 memblock 수

memblock3

 

memblock_insert_region()

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, 
                                                   enum memblock_flags 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;
}

new 영역을 지정된 타입의 지정된 index memblock 위치에 끼워(insert) 넣는다. memblock_type 구조체의 필드 cnt와 total_size에는 카운터를 증가시키고 증가된 size도 더한다.

  • 코드 라인 10에서 지정된 index의 memblock부터 마지막 memblock까지 한 칸씩 뒤로 복사한다.
  • 코드 라인 11~14에서 멀티 노드를 지원하기 위해 노드 id를 지정한다.
  • 코드 라인 15~16에서 memblock 타입 정보에는 카운터를 증가 시키고 증가된 size도 더한다.

 

memblock 배열 확장

memblock_double_array()

memblock이 위치한 배열이 작아서 확장이 필요할 때 호출되면 두 배로 키울 수 있다. 다음 코드를 통해 memblock_double_array( ) 함수를 분석해보자.

mm/memblock.c -1/2-

/**
 * 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.
 *
 * Return:
 * 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, new_end;
        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;
        }
  • 코드 라인 14~15에서 memblock_allow_resize( )가 호출되어 memblock_can_resize 전역 변수가 1로 설정된 후에만 이 함수가 동작하도록 제한한다.
    • 커널 이미지와 메모리 영역이 매핑 완료되고, arm64_memblock_init() 루틴이 수행되어 memblock이 사용될 준비가 되면 다음 함수 호출 경로에서 사용된다.
      • setup_arch() -> paging_init()의 마지막에서 -> memblock_allow_resize()이 호출된다.
  • 코드 라인 18~25에서 새로 관리할 영역을 기존 관리 영역보다 두 배 큰 크기로 할당받을 준비를 한다.
  • 코드 라인 28~31에서 기존 memblock 관리 맵이 static이나 memblock에서 할당받은 것이 아니라 이미 슬랩(slab)으로 전환하여 운영되고 있는 중인지 여부를 확인한다.
  • 코드 라인 44~46에서 정규 메모리 할당자인 슬랩을 사용할 수 있는 단계에서 kmalloc( )으로 메모리를 할당받는다.
  • 코드 라인 47~50에서 슬랩을 사용하지 못하는 경우 memblock_add( ) 함수에서 사용 요청한 영역을 피해 memblock_find_in_range( )로 새로 만들 관리 영역을 사용할 수 있는 공간을 찾아온다. 요청 타입이 reserved 타입이 아닌 경우, 즉 memory 타입인 경우에는 할당 영역 검색을 0부터 시작하게 한다.
    • 메모리 타입을 등록하는 중에 관리 영역이 확장되는 경우, 새로 할당받아야 하는 영역은 그 전에 등록한 모든 메모리 영역 내에서 빈자리를 검색하면 된다. 어차피 새로 추가될 메모리 공간은 기존 메모리 공간과 간섭이 없는 영역일 것이다. 우선, 새로 관리 영역을 할당받을 공간은 추가 요청 영역을 피해야 하므로 요청 영역의 상부를 먼저 검색한다.
  • 코드 라인 52~58에서 만일 첫 번째 검색에서 할당받지 못했거나 요청 타입이 reserved인 경우에 추가 요청 영역을 피해 하부를 검색한다.

 

mm/memblock.c -2/2-

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

        new_end = addr + new_size - 1;
        memblock_dbg("memblock: %s is doubled to %ld at [%pa-%pa]",
                        type->name, type->max * 2, &addr, &new_end);

        /*
         * 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;
}
  • 코드 라인 16~20에서 새로 할당받은 관리 영역 시작 주소에 기존 memblock 영역들을 모두 복사하고 복사되지 않은 빈 곳은 0으로 초기화한다. 또한 max를 기존 값의 두 배로 변경한다.
  • 코드 라인 23~27에서 확장하기 전의 기존 관리 영역을 해제한다. 초기 관리 영역은 컴파일 타임에 선언된 배열 변수 영역에 있으므로 삭제할 수 없어서 그냥 버린다. 삭제할 관리 영역이 초기 영역이 아니면 다음과 같이 할당자 종류에 따라 처리한다.
    • 기존에 슬랩을 사용 중이었으면 kfree( )로 기존 영역을 해제한다.
    • 슬랩을 사용하지 않았다면 memblock_free( )로 기존 영역을 해제한다.

 

memblock_allow_resize()

mm/memblock.c

void __init memblock_allow_resize(void)
{
        memblock_can_resize = 1;
}

memblock 영역이 필요 시 확장이 가능하도록 1을 대입한다.

 

memblock 병합

memblock_merge_regions()

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--;
        }
}

인접 메모리 블록이 같은 플래그 타입을 사용하는 경우 병합한다

  • 코드 라인 6~8에서 요청 memblock 타입의 개수만큼 거꾸로 감소시키며 루프를 돈다.
  • 코드 라인 10~17에서 경계가 붙어 있지 않거나 두 memblock 간의 flag 상태가 다른 경우 memblock을 합치지 않고 스킵한다.
  • 코드 라인 19~22에서 경계가 붙어 있다면 memblock을 합친다. 참고로 병합이 된 경우 다시 한 번 다음 블럭과 비교하기 위해 regions[] 배열을 지정하는 인덱스 i 값은 증가시키지 않는다.

 

memblock_cap_size()

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_MAX - base);
}

영역이 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 주소는 사용불가능)

 

memblock 삭제

다음 그림은 memblock_remove() 함수의 호출 관계이다.

memblock5

 

memblock_remove()

mm/memblock.c

int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
        phys_addr_t end = base + size - 1;

        memblock_dbg("memblock_remove: [%pa-%pa] %pS\n",
                     &base, &end, (void *)_RET_IP_);

        return memblock_remove_range(&memblock.memory, base, size);
}

물리 메모리 주소 @base 부터 @size 만큼을 memory 타입 memblock 영역에서 제거한다.

 

memblock_free()

mm/memblock.c

/**
 * memblock_free - free boot memory block
 * @base: phys starting address of the  boot memory block
 * @size: size of the boot memory block in bytes
 *
 * Free boot memory block previously allocated by memblock_alloc_xx() API.
 * The freeing memory will not be released to the buddy allocator.
 */
int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size)
{
        phys_addr_t end = base + size - 1;

        memblock_dbg("   memblock_free: [%pa-%pa] %pF\n",
                     &base, &end, (void *)_RET_IP_);

        kmemleak_free_part_phys(base, size);
        return memblock_remove_range(&memblock.reserved, base, size);
}

물리 메모리 주소 @base 부터 @size 만큼을 reserved 타입 memblock 영역에서 제거한다.

 

memblock_remove_range()

mm/memblock.c

static int __init_memblock memblock_remove_range(struct memblock_type *type,
                                          phys_addr_t base, phys_addr_t size)
{
        int start_rgn, end_rgn;
        int i, ret;

        ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);
        if (ret)
                return ret;

        for (i = end_rgn - 1; i >= start_rgn; i--)
                memblock_remove_region(type, i);
        return 0;
}

요청한 @type의 memblock에서 물리 주소 @base 부터 @size까지 영역을 제거한다.

  • 코드 라인 7~9에서 제거할 영역의 시작과 끝 주소를 기준으로 memblock을 분리한다.
  • 코드 라인 11~12에서 제거할 영역에 해당하는 memblock 영역을 삭제한다.

 

memblock_remove_region()

mm/memblock.c

static void __init_memblock memblock_remove_region(struct memblock_type *type, unsigned long r)
{
        type->total_size -= type->regions[r].size;
        memmove(&type->regions[r], &type->regions[r + 1],
                (type->cnt - (r + 1)) * sizeof(type->regions[r]));
        type->cnt--;

        /* Special case for empty arrays */
        if (type->cnt == 0) {
                WARN_ON(type->total_size != 0);
                type->cnt = 1;
                type->regions[0].base = 0;
                type->regions[0].size = 0;
                type->regions[0].flags = 0;
                memblock_set_region_node(&type->regions[0], MAX_NUMNODES);
        }
}

요청한 @type의 memblock에서 인덱스 r에 해당하는 memblock을 삭제한다. 상위 memblock들이 삭제한 위치로 이동해온다.

 

다음 그림은 4개의 memblock 영역이 있고 그 중 노란색의 remove 범위를 삭제하면 변경되는 memblock 영역을 보여준다.

memblock6

 

memblock 분리

다음 그림은 memblock_ioslate_range() 함수가 처리되는 과정을 보여준다.

 

memblock_isolate_range()

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.
 *
 * Return:
 * 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 idx;
        struct memblock_region *rgn;

        *start_rgn = *end_rgn = 0;

        if (!size)
                return 0;

        /* we'll create at most two more regions */
        while (type->cnt + 2 > type->max)
                if (memblock_double_array(type, base, size) < 0)
                        return -ENOMEM;

        for_each_memblock_type(idx, type, rgn) {
                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, idx, rbase, base - rbase,
                                               memblock_get_region_node(rgn),
                                               rgn->flags);
                } 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, idx--, rbase, end - rbase,
                                               memblock_get_region_node(rgn),
                                               rgn->flags);
                } else {
                        /* @rgn is fully contained, record it */
                        if (!*end_rgn)
                                *start_rgn = idx;
                        *end_rgn = idx + 1;
                }
        }

        return 0;
}

요청 영역의 상단과 하단 라인에 겹치는 memblock에서 영역을 분리한다. 분리된 memblock 시작 인덱스가 출력 인자의 @start_rgn에 저장되고 끝 인덱스 + 1 값이 출력 인자 @end_rgn에 저장된다. 만일 분리한 memblock 항목이 없으면 두 출력 인자에 0이 저장된다.

  • 코드 라인 5에서 영역의 끝 물리 주소는 아키텍처가 지원하는 물리 주소의 끝을 초과하지 않도록 제한한다.
  • 코드 라인 15~17에서 해당 타입의 최대 개수를 사용하려 할 때 관리 배열을 두 배로 확장한다. 확장이 불가능하면 메모리가 부족하다는 -ENOMEM을 리턴한다.
  • 코드 라인 19~21에서 첫 memblock 영역부터 마지막 영역에 대해 루프를 돌며 rbase와 rend에는 해당 루프 인덱스의 memblock 시작 주소와 끝 주소가 담긴다.
  • 코드 라인 23~24에서 현재 인덱스 영역의 시작 주소가 주어진 영역의 상단보다 크거나 같은 경우 더는 상위 인덱스 memblock에서 처리할 필요가 없으므로 루프를 빠져나가게 한다.  (아래 그림의 A 조건)
  • 코드 라인 25~26에서 현재 인덱스 영역의 끝 주소가 주어진 영역의 상단보다 같거나 큰 경우 아직 겹쳐지는 영역이 아니므로 다음 인덱스 memblock을 처리하기 위해 스킵한다. (아래 그림의 B 조건)
  • 코드 라인 28~38에서 현재 인덱스 영역의 시작 주소가 주어진 영역의 시작 주소보다 작은 경우, 즉 주어진 영역의 시작 주소가 현재 인덱스 memblock과 겹친 경우 다음과 같이 처리한다. (아래 그림의 C 조건)
    • 현재 인덱스 memblock을 하단의 겹친 라인을 기점으로 상부로 영역을 옮긴다. 또한 인덱스 memblock의 하단 겹친 라인을 기점으로 새롭게 하부에 memblock을 insert한다.
  • 코드 라인 39~49에서 현재 인덱스 영역의 끝 주소가 주어진 영역의 끝 주소보다 큰 경우, 즉 주어진 영역의 끝 주소가 현재 인덱스 memblock과 겹친 경우 다음과 같이 처리한다. (아래 그림의 D 조건)
    • 현재 인덱스 memblock을 상단의 겹친 라인을 기점으로 상부로 영역을 옮긴다. 또한 인덱스 memblock의 상단에 겹친 라인을 기점으로 새롭게 하부에 memblock을 삽입한다.
  • 코드 라인 50~55에서 어떠한 조건에도 걸리지 않은 경우로, 현재 인덱스 영역이 주어진 영역에 포함된 경우다. 이때는 start_rgn에 현재 인덱스 값을 지정하고, end_rgn에는 현재 인덱스 값 + 1을 지정한다.

 

다음 그림은 회색으로 표시된 6개의 memblock 영역이 있는 상황에서 6개 각각 하늘색의 isolation 영역이 주어졌을 때 region[0]부터 화살표 방향으로 비교하는 모습을 보여준다.

  • 분리할 영역이 기존 memblock 영역과 겹치는 경우 겹쳐지는 부분은 memblock을 나눈다. 그리고 memblock이 없는 부분은 추가한다.
  • insertion
    • memblock이 추가될 때 실제 insertion이 일어나는 개수를 표시하였다.
  • start_rgn
    • isolation 범위의 시작이 겹치는 기존 memblock 영역의 인덱스 번호
  • end_rgn
    • isolation 범위의 끝이 겹치는 기존 memblock 영역의 인덱스 번호 + 1

 

다음 그림은 위 그림 6개 예제의 첫 번째 부분을 조금 더 자세하게 표현하였다.

  • (C) 및 (D) case에서 insert를 하는 경우 처리 중인 idx 영역을 위로 밀어내고 추가한다.
  • (D) case를 진행하는 경우 idx에 해당하는 영역을 다시 한 번 진행하는 것에 주의한다.

 

다음 그림은 위 그림 6개 예제의 세 번째 부분을 조금 더 자세하게 표현하였다.

 

참고

 

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