Memblock – (1)

<kernel v5.10>

Memblock

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

 

Memblock의 구조

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

  • memory 타입
    •  memory 타입은 사용할 물리 메모리 영역을 등록하여 사용한다. 커널 파라미터에 의해 실제 물리 메모리의 일부 영역만을 사용하게끔 제한되어 등록 가능하다. 처음에는 regions[ ] 배열에 최대 128개의 영역을 사용할 수 있고, 추후 2배 단위로 계속 확장될 수 있다.
  • reserved 타입
    • reserved 타입은 사용 중이거나 사용할 물리 메모리 영역을 등록하여 사용한다. 처음에는 regions[ ] 배열에 최대 128개의 영역을 사용할 수 있고, 추후 2배 단위로 계속 확장될 수 있다.

 

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

 

physmem 타입

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

 

CONFIG_HAVE_MEMBLOCK_PHYS_MAP

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

CONFIG_ARCH_KEEP_MEMBLOCK

  • memblock에 등록된 데이터를 early 부트 프로세스 이후 메모리에서 제거하지 않고 보존할 수 있게 vmlinux.lds.h 에서 제어할 수 있는 옵션이다.

 

include/linux/memblock.h

#ifndef CONFIG_ARCH_KEEP_MEMBLOCK
#define __init_memblock __meminit
#define __initdata_memblock __meminitdata
void memblock_discard(void);
#else
#define __init_memblock
#define __initdata_memblock
static inline void memblock_discard(void) {}
#endif

 

include/linux/init.h

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

 

CONFIG_MEMORY_HOTPLUG

  • CONFIG_ARCH_KEEP_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]
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",

        .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에서 비어 있는 영역의 검색을 위에서 아래 주소 방향으로 수행하도록 초깃값으로 설정한다.
  • 코드 라인 13에서 최대 메모리 할당 제한 값을 시스템이 사용하는 주소의 가장 큰 주소를 갖게 하여 제한이 없는 상태로 초기화한다

 

다음 그림은 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;
};
  • 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 영역 등록

 

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_NEED_MULTIPLE_NODES
        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("%s: [%pa-%pa] %pS\n", __func__,
                     &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한다.

 

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

 

아래 그림에서 회색으로 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. */
        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)으로 전환하여 운영되고 있는 중인지 여부를 확인한다.
  • 코드 라인 34~36에서 정규 메모리 할당자인 슬랩을 사용할 수 있는 단계에서 kmalloc( )으로 메모리를 할당받는다.
  • 코드 라인 37~40에서 슬랩을 사용하지 못하는 경우 memblock_add( ) 함수에서 사용 요청한 영역을 피해 memblock_find_in_range( )로 새로 만들 관리 영역을 사용할 수 있는 공간을 찾아온다. 요청 타입이 reserved 타입이 아닌 경우, 즉 memory 타입인 경우에는 할당 영역 검색을 0부터 시작하게 한다.
    • 메모리 타입을 등록하는 중에 관리 영역이 확장되는 경우, 새로 할당받아야 하는 영역은 그 전에 등록한 모든 메모리 영역 내에서 빈자리를 검색하면 된다. 어차피 새로 추가될 메모리 공간은 기존 메모리 공간과 간섭이 없는 영역일 것이다. 우선, 새로 관리 영역을 할당받을 공간은 추가 요청 영역을 피해야 하므로 요청 영역의 상부를 먼저 검색한다.
  • 코드 라인 42~48에서 만일 첫 번째 검색에서 할당받지 못했거나 요청 타입이 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("%s: [%pa-%pa] %pS\n", __func__,
                     &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("%s: [%pa-%pa] %pS\n", __func__,
                     &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

 

arm_memblock_init()

<kernel v4.0>

reserve memblock 영역에 다음 영역들을 등록한다.

  • 커널 영역 (XIP 커널인 경우 코드를 제외한 커널 영역)
  • initrd 영역
  • 페이지 테이블 영역
  • 아키텍처 머신이 지정하는 reserve 영역
  • DTB 영역 및 DTB가 지정하는 reserved mem 영역
  • CMA 영역(연속된 메모리 핸들링이 필요한 영역)
    • 다음 2가지 설정에 의해 영역 크기가 할당될 수 있다.
      • “cma=” 커널 cmdline 문자열에 의해 호출되어 지정된다.
      • CONFIG_CMA_SIZE_SEL_MBYTES 옵션을 사용하여 요청 영역을 받아 등록
    • cma(Contigugos Memory Allocator) 영역에도 추가한다.

arm_memblock_init

 

arm_memblock_init()

arch/arm/mm/init.c

void __init arm_memblock_init(const struct machine_desc *mdesc)
{
        /* Register the kernel text, kernel data and initrd with memblock. */
#ifdef CONFIG_XIP_KERNEL
        memblock_reserve(__pa(_sdata), _end - _sdata);
#else
        memblock_reserve(__pa(_stext), _end - _stext);
#endif
#ifdef CONFIG_BLK_DEV_INITRD
        /* FDT scan will populate initrd_start */
        if (initrd_start && !phys_initrd_size) {
                phys_initrd_start = __virt_to_phys(initrd_start);
                phys_initrd_size = initrd_end - initrd_start;
        }   
        initrd_start = initrd_end = 0;
        if (phys_initrd_size &&
            !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
                pr_err("INITRD: 0x%08llx+0x%08lx is not a memory region - disabling initrd\n",
                       (u64)phys_initrd_start, phys_initrd_size);
                phys_initrd_start = phys_initrd_size = 0;
        }   
        if (phys_initrd_size &&
            memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {
                pr_err("INITRD: 0x%08llx+0x%08lx overlaps in-use memory region - disabling initrd\n",
                       (u64)phys_initrd_start, phys_initrd_size);
                phys_initrd_start = phys_initrd_size = 0;
        }           
        if (phys_initrd_size) {
                memblock_reserve(phys_initrd_start, phys_initrd_size);

                /* Now convert initrd to virtual addresses */
                initrd_start = __phys_to_virt(phys_initrd_start);
                initrd_end = initrd_start + phys_initrd_size;
        }           
#endif      

        arm_mm_memblock_reserve();

        /* reserve any platform specific memblock areas */
        if (mdesc->reserve)
                mdesc->reserve();

        early_init_fdt_scan_reserved_mem();

        /* reserve memory for DMA contiguous allocations */
        dma_contiguous_reserve(arm_dma_limit);

        arm_memblock_steal_permitted = false;
        memblock_dump_all();
}
  • 커널 영역을 reserve memblock에 등록한다.
    • XIP_KERNEL 옵션을 사용하는 경우 code 영역을 제외한 나머지 커널 영역을 등록한다.
  • CONFIG_BLK_DEV_INITRD
    • 특정 메모리 영역을 램디스크로 사용할 수 있다.
  • if (initrd_start && !phys_initrd_size) {
    • initrd_start 가 지정되었고 phys_initrd_size가 0이 아니면 phys_initrd_start 와 phys_initrd_size를 지정한다.
  • if (phys_initrd_size && !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
    • initrd 영역이 memory memblock 영역에 포함되어 있지 않은 경우 에러 메시지를 출력하고 initrd 영역을 memblock에 추가하는 것을 포기하기 위해 크기를 0으로 설정한다.
  • if (phys_initrd_size && memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {
    • initrd 영역이 이미 reserved memblock 영역에 겹친 경우 에러 메시지를 출력하고 initrd 영역을 memblock에 추가하는 것을 포기하기 위해 크기를 0으로 설정한다.
  • memblock_reserve(phys_initrd_start, phys_initrd_size);
    • initrd 영역을 reserve memblock에 추가한다.
  • arm_mm_memblock_reserve();
    • 페이지 테이블을 reserve memblock에 추가한다.
  • if (mdesc->reserve) mdesc->reserve();
    • 아키텍처의 지정된 reserve() 함수를  호출한다.
  •  early_init_fdt_scan_reserved_mem();
    • 다음 3가지 영역을 reserve memblock에 추가한다.
      • DTB 영역
      • DTB 헤더의 off_mem_rsvmap 필드가 가리키는 memory reserve 블럭(바이너리)에서 읽은 메모리 영역들
      • DTB reserved-mem 노드 영역이 요청하는 영역을 reserve memblock에 추가한다.
  • dma_contiguous_reserve(arm_dma_limit);
    • 디바이스 드라이버(dma for coherent/cma for dma)가 필요로 하는 DMA 영역을 reserve memblock에 추가하고 CMA(Contiguous Memory Allocator)에도 추가한다.
      • cma_areas[]에 추가된 엔트리는 CMA 드라이버가 로드되면서 초기화될 때 사용한다.
      • dma_mmu_remap[]에 추가된 엔트리는 추후 dma_contiguous_remap() 함수를 통해 페이지 테이블에 IO 속성으로 매핑할 때 사용된다.
      • 참고: CMA(Contiguous Memory Allocator) for DMA | 문c
  • memblock_dump_all();
    • 커널 cmdline에 “debug” 옵션을 사용하는 경우 memory & reserve memblock 영역을 dump 한다.

아래 그림과 같은 순서로 몇 가지 영역을 reserved memblock에 추가한다.

arm_memblock_init-2

  • CMA 관련 테이블은 다음과 같이 2개이다.
    • dma_mmu_remap[] 배열은 추후 테이블에 IO 속성으로 매핑할 때 사용된다.
    • cma_areas[] 배열은 CMA 드라이버가 로드되면서 이 항목을 사용하여 초기화한다.

 

arm_mm_memblock_reserve()

arch/arm/mm/mmu.c

/*
 * Reserve the special regions of memory
 */
void __init arm_mm_memblock_reserve(void)
{
        /*   
         * Reserve the page tables.  These are already in use,
         * and can only be in node 0.
         */
        memblock_reserve(__pa(swapper_pg_dir), SWAPPER_PG_DIR_SIZE);

#ifdef CONFIG_SA1111
        /*   
         * Because of the SA1111 DMA bug, we want to preserve our
         * precious DMA-able memory...
         */
        memblock_reserve(PHYS_OFFSET, __pa(swapper_pg_dir) - PHYS_OFFSET);
#endif
}
  • 커널 1차 페이지 테이블 영역을 reserve memblock에 추가한다.

 

DTB for reserved memory region

일반적인 메모리 영역에서 특정 목적으로 제외(reserve)시켜야 하는 메모리 영역을 지정한다. 아래 예를 살펴본다.

  • reserved-memory 노드로 구성한다.
    • 구성 시 #address-cells와 #size-cells는 루트 노드의 것과 동일해야 한다.
    • ranges 속성이 존재해야 한다.
  • 서브 노드들에는 영역의 시작과 사이즈에 대한 정보가 포함되어 있다.
    • compatible 속성을 두어 연동되는 디바이스 드라이버를 지정할 수 있다.
    • cma 및 dma 드라이버의 경우는 디바이스명으로 “shared-cma-pool” 및 “shared-dma-pool” 로 지정되어 있고 이를 통해서 별도의 지정된 설정 함수를 실행시켜 초기화를 수행한다.
      • 아직 실제 dtb 사례에서 cma용 shared-cma-pool이나 dma용 shared-dma-pool 디바이스 명을 사용하여 구동시킨 사례는 찾아 볼 수 없었지만 설계는 이미 아래 예와 같이 되어 있고 코드도 준비되어 있다.
    • 영역의 크기 정보는 reg와 size를 통해서 한다.
      • reg 속성을 사용하는 경우 static한 방법으로 지정된 메모리 영역을 reserve memblock에 추가한다.
      • size 속성을 사용하는 경우 dynamic 한 방법을 사용하여 디바이스 드라이버가 시작 주소를 구해 영역을 reserve memblock에 추가한다.
        • alloc-range 속성 값으로 지정된 범위내에서 요청한 사이즈 공간을 찾아 reserve memblock에 추가한다.
        • alloc-range 속성을 지정하지 않은 경우 전체 메모리를 대상으로 요청된 사이즈 만큼 reserve memblock에 추가한다.
      • reg와 size를 동시에 지정하는 경우 size는 무시된다.
    • no-map 속성이 있는 경우에는 reserve 영역에서 오히려 제거한다.
/ {
	#address-cells = <1>;
	#size-cells = <1>;

	memory {
		reg = <0x40000000 0x40000000>;
	};

	reserved-memory {
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;

		/* global autoconfigured region for contiguous allocations */
		linux,cma {
			compatible = "shared-dma-pool";
			reusable;
			size = <0x4000000>;
			alignment = <0x2000>;
			linux,cma-default;
		};

		display_reserved: framebuffer@78000000 {
			reg = <0x78000000 0x800000>;
		};

		multimedia_reserved: multimedia@77000000 {
			compatible = "acme,multimedia-memory";
			reg = <0x77000000 0x4000000>;
		};
	};

	/* ... */

	fb0: video@12300000 {
		memory-region = <&display_reserved>;
		/* ... */
	};

	scaler: scaler@12500000 {
		memory-region = <&multimedia_reserved>;
		/* ... */
	};

	codec: codec@12600000 {
		memory-region = <&multimedia_reserved>;
		/* ... */
	};
};

 

early_init_fdt_scan_reserved_mem()

drivers/of/fdt.c

/**
 * early_init_fdt_scan_reserved_mem() - create reserved memory regions
 *
 * This function grabs memory from early allocator for device exclusive use
 * defined in device tree structures. It should be called by arch specific code
 * once the early allocator (i.e. memblock) has been fully activated.
 */
void __init early_init_fdt_scan_reserved_mem(void)
{
        int n;
        u64 base, size;

        if (!initial_boot_params)
                return;

        /* Reserve the dtb region */
        early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
                                          fdt_totalsize(initial_boot_params),
                                          0);

        /* Process header /memreserve/ fields */
        for (n = 0; ; n++) {
                fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
                if (!size)
                        break; 
                early_init_dt_reserve_memory_arch(base, size, 0);
        }   

        of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);
        fdt_init_reserved_mem();
}
  • early_init_dt_reserve_memory_arch(__pa(initial_boot_params), fdt_totalsize(initial_boot_params), 0);
    • DTB 영역을 reserve memblock에 추가한다.
  • fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
    • DTB 헤더에서 reservedmem 필드가 가리키는 바이너리 영역을 읽어 reserve할 base(시작주소)및 size를 알아온다.
    • 사이즈가 0이 아닐때 까지 반복된다.
      • DTB에서 reserved memory block은 8바이트 숫자로 시작주소 및 사이즈가 필요한 만큼 반복된다.
  • early_init_dt_reserve_memory_arch(base, size, 0);
    • 알아온 영역을 다음 2가지 경우에 따라 처리한다.
      • static 방법: DTB에서 reg를 읽은 경우 base, size 정보로 reserve memblock에 추가한다.
      • dynamic 방법: DTB에서 size 속성을 읽은 경우 2번에 나누어 초기화 하는데 먼저 reserved_mem[] 배열에만 추가한다.
  • of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);
    • 첫 번째 depth의 “reserved-memory” 노드명을 찾아 “status” 속성값이 “ok” 또는 “okay”인 경우 알아온 영역을 다음 2가지 경우에 따라 처리한다.
      • static 방법: DTB에서 reg를 읽은 경우 base, size 정보로 reserve memblock에 추가한다.
      • dynamic 방법: DTB에서 size 속성을 읽은 경우 2번에 나누어 초기화 하는데 먼저 reserved_mem[] 배열에만 추가한다.
  • fdt_init_reserved_mem();
    • dynamic 방법의 연장 즉 2nd phase 초기화로 reserved_mem[] 배열에 등록된 수 만큼 루프를 돌며 읽어와서 DTB의 alloc-range 속성이 요청하는 메모리 range 들에서 reserve할 영역을 찾은 경우 reserve memblock에 추가하고 등록된 디바이스를 찾아 초기화 함수를 실행하게 한다.

 

early_init_dt_reserve_memory_arch()

drivers/of/fdt.c

int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base,
                                        phys_addr_t size, bool nomap)
{
        if (nomap)
                return memblock_remove(base, size);
        return memblock_reserve(base, size);
}
  • nomap이 true인 경우 해당 영역을 reserve memblock에서 삭제한다.
  • nomap이 false인 경우 해당 영역을 reserve memblock에 추가한다.

 

__fdt_scan_reserved_mem()

drivers/of/fdt.c”

/**
 * fdt_scan_reserved_mem() - scan a single FDT node for reserved memory
 */
static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname,
                                          int depth, void *data)
{
        static int found;
        const char *status;
        int err;

        if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) {
                if (__reserved_mem_check_root(node) != 0) {
                        pr_err("Reserved memory: unsupported node format, ignoring\n");
                        /* break scan */
                        return 1;
                }
                found = 1;
                /* scan next node */
                return 0;
        } else if (!found) {
                /* scan next node */
                return 0;
        } else if (found && depth < 2) {
                /* scanning of /reserved-memory has been finished */
                return 1;
        }

        status = of_get_flat_dt_prop(node, "status", NULL);
        if (status && strcmp(status, "okay") != 0 && strcmp(status, "ok") != 0)
                return 0;

        err = __reserved_mem_reserve_reg(node, uname);
        if (err == -ENOENT && of_get_flat_dt_prop(node, "size", NULL))
                fdt_reserved_mem_save_node(node, uname, 0, 0);

        /* scan next node */
        return 0;
}
  • if (!found && depth == 1 && strcmp(uname, “reserved-memory”) == 0) {
    • 1 depth 인 노드명이 “reserved-memory” 이면
    •  아래 예) arch/arm/boot/dts/atlas7-evb.dts
      • 0x5e80_0000 부터 8M를 reserve memblock에 추가
      • 0x4600_0000 부터 2M를 reserve memblock에서 삭제
        reserved-memory {
                #address-cells = <1>;
                #size-cells = <1>;
                ranges;

                vpp_reserved: vpp_mem@5e800000 {
                        compatible = "sirf,reserved-memory";
                        reg = <0x5e800000 0x800000>;
                };

                nanddisk_reserved: nanddisk@46000000 {
                        reg = <0x46000000 0x200000>;
                        no-map;
                };
        };
  • if (__reserved_mem_check_root(node) != 0) {
    • 현재 노드의 #address-cells와 #size-cells가 root 노드에 있는 #address-cells와 #size-cells와 같고 “ranges” 속성값이 제공되는 경우 성공리에 0이 리턴된다. 그 외에는 음수의 에러(-EINVAL)를 리턴한다.
  • status = of_get_flat_dt_prop(node, “status”, NULL);
    • reserved-memory 노드를 찾은 다음 depth의 노드에서 “status” 속성을 찾는다.
  • if (status && strcmp(status, “okay”) != 0 && strcmp(status, “ok”) != 0)
    • status 속성이 있는 경우 속성 값이 “okay”가 아니면서 “ok”도 아닌 경우 리턴한다.
  • err = __reserved_mem_reserve_reg(node, uname);
    • 해당 노드의 reg 속성 값으로 reserve memblock에 추가한다.
    • base 주소가 지정되지 않아 dynamic 할당을 시도하는 경우에는 reg 속성 대신 size 속성을 사용하므로 이 때에는 실패로 리턴하게 된다.
  • if (err == -ENOENT && of_get_flat_dt_prop(node, “size”, NULL))
    • 호출한 함수가 에러이면서 “size” 속성을 찾을 수 있는 경우
  • fdt_reserved_mem_save_node(node, uname, 0, 0);
    • 시작 주소와 사이즈를 0으로 해서 전역 변수 reserved_mem[] 배열에 추가한다.
    • 배열에 추가된 이 정보는 추후 fdt_init_reserved_mem() 함수를 호출하여 2nd phase 초기화를 수행하게 한다.

 

__reserved_mem_reserve_reg()

해당 함수는 CONFIG_OF_EARLY_FLATTREE 옵션을 사용한 경우에만 동작한다. (DTB 기본 옵션)

drivers/of/fdt.c

/**
 * res_mem_reserve_reg() - reserve all memory described in 'reg' property
 */
static int __init __reserved_mem_reserve_reg(unsigned long node,
                                             const char *uname)
{
        int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
        phys_addr_t base, size;
        int len;
        const __be32 *prop;
        int nomap, first = 1;

        prop = of_get_flat_dt_prop(node, "reg", &len);
        if (!prop)
                return -ENOENT;

        if (len && len % t_len != 0) {
                pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n",
                       uname);
                return -EINVAL;
        }

        nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;

        while (len >= t_len) {
                base = dt_mem_next_cell(dt_root_addr_cells, &prop);
                size = dt_mem_next_cell(dt_root_size_cells, &prop);

                if (size &&
                    early_init_dt_reserve_memory_arch(base, size, nomap) == 0)
                        pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %ld MiB\n",
                                uname, &base, (unsigned long)size / SZ_1M);
                else
                        pr_info("Reserved memory: failed to reserve memory for node '%s': base %pa, size %ld MiB\n",
                                uname, &base, (unsigned long)size / SZ_1M);

                len -= t_len;
                if (first) {
                        fdt_reserved_mem_save_node(node, uname, base, size);
                        first = 0;
                }
        }
        return 0;
}

DTB를 분석하여 다음 두 가지 방법 중 하나를 사용하여 memblock에 등록한다.

  • static
    • reserved-mem 노드의 reg 속성에 지정된 크기로 memblock에 곧장 등록한다.
  • dynamic
    • reserved-mem 노드의 size 속성이 지정된 경우 reserved_mem[] 배열에 추가하고 추 후 second pass initialization 루틴(fdt_init_reserved_mem())이 호출될 때 초기화를 진행하게 한다.

 

  • int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
    • 루트 노드에 있는 #addr-cells + #size-cells를 4로 곱한 값이 처리할 데이터 길이다.
  • prop = of_get_flat_dt_prop(node, “reg”, &len);
    • “reg” 속성을 찾는다.
  • nomap = of_get_flat_dt_prop(node, “no-map”, NULL) != NULL;
    • “no-map” 속성이 찾아지면 true
      • reserve영역에서 해지
  • base = dt_mem_next_cell(dt_root_addr_cells, &prop);
    • 루트노드의 #addr-cells x 4 바이트 만큼 읽어서 base에 저장한다.
  • size = dt_mem_next_cell(dt_root_size_cells, &prop);
    • 루트노드의 #size-cells x 4 바이트 만큼 읽어서 size에 저장한다.
  • if (size && early_init_dt_reserve_memory_arch(base, size, nomap) == 0)
    • size가 주어진 경우 해당 영역을 static하게 reserve memblock에 추가하거나 삭제한다.
  •  fdt_reserved_mem_save_node(node, uname, base, size);
    • 처음 한 번만 이루틴이 호출된다.
    • 전역 구조체 reserved_mem[] 배열에 노드 포인터, 노드명, 시작주소, 사이즈 등을 추가한다.
    • 이 배열은 추후에 fdt_init_reserved_mem() 함수가 호출될 때 초기화를 수행한다.

 

fdt_reserved_mem_save_node()

drivers/of/of_reserved_mem.c

/**
 * res_mem_save_node() - save fdt node for second pass initialization
 */
void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname,
                                      phys_addr_t base, phys_addr_t size)
{
        struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];

        if (reserved_mem_count == ARRAY_SIZE(reserved_mem)) {
                pr_err("Reserved memory: not enough space all defined regions.\n");
                return;
        }

        rmem->fdt_node = node;
        rmem->name = uname;
        rmem->base = base;
        rmem->size = size;

        reserved_mem_count++;
        return;
}
  • 전역 구조체 reserved_mem[] 배열에 노드 포인터, 노드명, 시작주소, 사이즈 등을 추가하고 전역 변수 reserved_mem_count를 증가시킨다.
  • 배열은 MAX_RESERVED_REGIONS(16)개로 초기화되어 있다.

 

reserved mem 초기화

fdt_init_reserved_mem()

drivers/of/of_reserved_mem.c

/**
 * fdt_init_reserved_mem - allocate and init all saved reserved memory regions
 */
void __init fdt_init_reserved_mem(void)
{
        int i;
        for (i = 0; i < reserved_mem_count; i++) {
                struct reserved_mem *rmem = &reserved_mem[i];
                unsigned long node = rmem->fdt_node;
                int len;
                const __be32 *prop;
                int err = 0;

                prop = of_get_flat_dt_prop(node, "phandle", &len);
                if (!prop)
                        prop = of_get_flat_dt_prop(node, "linux,phandle", &len);
                if (prop)
                        rmem->phandle = of_read_number(prop, len/4);

                if (rmem->size == 0)
                        err = __reserved_mem_alloc_size(node, rmem->name,
                                                 &rmem->base, &rmem->size);
                if (err == 0)               
                        __reserved_mem_init_node(rmem);
        }            
}

dynamic 방법에 의해 등록된 reserved_mem[] 배열을 읽어 DTB alloc-range 속성이 요청한 메모리 범위에서 size 속성 만큼 reserve 할 수 있는 영역을 찾고 성공한 경우 reserve memblock을 추가한다. 그런 후 각 디바이스 드라이버에 지정된  callback 함수(of_device_id->data)를 호출하여 해당 디바이스 드라이버를 초기화한다.

  • reserved_mem[] 배열에 등록된 항목들을 모두 조회한다.
  • unsigned long node = rmem->fdt_node;
    • rmem->fdt_node는 DTB에 reserved-mem 노드로 등록된 노드이다.
  •  prop = of_get_flat_dt_prop(node, “phandle”, &len);
    • 해당 노드에서 “phandle” 속성을 찾는다. 없으면 “linux,phandle”에서도 찾아본다.
  • if (prop) rmem->phandle = of_read_number(prop, len/4);
    • 속성이 발견되면 rmem->phandle에 값을 읽어 udpate 한다.
  •  err = __reserved_mem_alloc_size(node, rmem->name, &rmem->base, &rmem->size);
    • rmem->size가 0인 경우는 base 주소가 dynamic하게 지정될 수 있게 __reserved_mem_alloc_size() 함수를 호출하여 reserve memblock에 영역을 추가한다.
      • 추가할 사이즈 정보는 DTB의 size 속성을 읽어오고 범위 정보는 DTB의 alloc-range 속성을 읽어온다.
  • __reserved_mem_init_node(rmem);
    • 에러가 없는 경우 __reserved_mem_init_node() 함수를 호출하여 관련 reserved memory에 대한 해당 디바이스의 초기화 함수를 __rmem_of_table_sentinel 테이블에서 검색하여 호출한다.

 

__reserved_mem_init_node()

drivers/of/of_reserved_mem.c

/**
 * res_mem_alloc_size() - allocate reserved memory described by 'size', 'align'
 *                        and 'alloc-ranges' properties
 */
static int __init __reserved_mem_alloc_size(unsigned long node,
        const char *uname, phys_addr_t *res_base, phys_addr_t *res_size)
{
        int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
        phys_addr_t start = 0, end = 0;
        phys_addr_t base = 0, align = 0, size;
        int len;
        const __be32 *prop;
        int nomap;
        int ret;

        prop = of_get_flat_dt_prop(node, "size", &len);
        if (!prop)
                return -EINVAL;

        if (len != dt_root_size_cells * sizeof(__be32)) {
                pr_err("Reserved memory: invalid size property in '%s' node.\n",
                                uname);
                return -EINVAL;
        }
        size = dt_mem_next_cell(dt_root_size_cells, &prop);

        nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;

        prop = of_get_flat_dt_prop(node, "alignment", &len);
        if (prop) {
                if (len != dt_root_addr_cells * sizeof(__be32)) {
                        pr_err("Reserved memory: invalid alignment property in '%s' node.\n",
                                uname);
                        return -EINVAL;
                }
                align = dt_mem_next_cell(dt_root_addr_cells, &prop);
        }

dynamic 방법으로 alloc-ranges 속성이 지정한 범위 내에서 지정한 영역 크기를 reserve memblock에 추가한다.

  • int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
    • 노드에서 읽어와야 할 바이트 수를 알아낸다.
  • prop = of_get_flat_dt_prop(node, “size”, &len);
    • size 속성을 읽어와서 없으면 에러를 리턴한다.
  • if (len != dt_root_size_cells * sizeof(__be32)) {
    • 루트 노드의 #size-cells와 reserved-mem 노드의 #size-cells가 다른 경우 메시지를 출력하고 에러로 리턴한다.
  • size = dt_mem_next_cell(dt_root_size_cells, &prop);
    • size 속성에서 값(byte)을 읽어온다.
  • nomap = of_get_flat_dt_prop(node, “no-map”, NULL) != NULL;
    • no-map 속성이 있으면 true가 된다.
  • prop = of_get_flat_dt_prop(node, “alignment”, &len);
    • alignment 속성을 찾는다.
  • if (len != dt_root_addr_cells * sizeof(__be32)) {
    • 루트 노드의 #addr-cells와 reserved-mem 노드의 #addr-cells가 다른 경우 메시지를 출력하고 에러로 리턴한다.
  • align = dt_mem_next_cell(dt_root_addr_cells, &prop);
    • align 값을 읽어온다.
        prop = of_get_flat_dt_prop(node, "alloc-ranges", &len);
        if (prop) {

                if (len % t_len != 0) {
                        pr_err("Reserved memory: invalid alloc-ranges property in '%s', skipping node.\n",
                               uname);
                        return -EINVAL;
                }

                base = 0;

                while (len > 0) {
                        start = dt_mem_next_cell(dt_root_addr_cells, &prop);
                        end = start + dt_mem_next_cell(dt_root_size_cells,
                                                       &prop);

                        ret = early_init_dt_alloc_reserved_memory_arch(size,
                                        align, start, end, nomap, &base);
                        if (ret == 0) {
                                pr_debug("Reserved memory: allocated memory for '%s' node: base %pa, size %ld MiB\n",
                                        uname, &base,
                                        (unsigned long)size / SZ_1M);
                                break;
                        }
                        len -= t_len;
                }

        } else {
                ret = early_init_dt_alloc_reserved_memory_arch(size, align,
                                                        0, 0, nomap, &base);
                if (ret == 0)
                        pr_debug("Reserved memory: allocated memory for '%s' node: base %pa, size %ld MiB\n",
                                uname, &base, (unsigned long)size / SZ_1M);
        }

        if (base == 0) {
                pr_info("Reserved memory: failed to allocate memory for node '%s'\n",
                        uname);
                return -ENOMEM;
        }

        *res_base = base;
        *res_size = size;

        return 0;
}
  • prop = of_get_flat_dt_prop(node, “alloc-ranges”, &len);
    • alloc_ranges 속성을 찾는다.
  • if (len % t_len != 0) {
    • 루트 노드의 #addr-cells 및 #size-cells 가 reserved-mem 노드의 #addr-cells 및 #size-cells가 다른 경우 메시지를 출력하고 에러를 리턴한다.
  • while (len > 0) {
    • alloc_ranges 속성에서 영역을 여러 개의 배열로 지정한 경우를 위해 루프를 돈다.
  • start = dt_mem_next_cell(dt_root_addr_cells, &prop);
    • 시작 주소를 알아온다.
  • end = start + dt_mem_next_cell(dt_root_size_cells, &prop);
    • 끝 주소를 알아온다.
  • ret = early_init_dt_alloc_reserved_memory_arch(size, align, start, end, nomap, &base);
    • 읽어온 검색 범위에서 reserve memblock을 시도하고 성공하면 루프를 빠져나온다.
  • ret = early_init_dt_alloc_reserved_memory_arch(size, align, 0, 0, nomap, &base);
    • alloc-ranges 속성이 없는 경우에는 검색 범위를 메모리 전체로 지정하여 reserve memblock을 수행한다.
  • if (base == 0) {
    • reserve할 공간이 없어 실패한 경우 메시지를 출력하고 에러를 리턴한다.

 

__reserved_mem_init_node()

drivers/of/of_reserved_mem.c

/**
 * res_mem_init_node() - call region specific reserved memory init code
 */
static int __init __reserved_mem_init_node(struct reserved_mem *rmem)
{
        extern const struct of_device_id __reservedmem_of_table[];
        const struct of_device_id *i;

        for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) {
                reservedmem_of_init_fn initfn = i->data;
                const char *compat = i->compatible;

                if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))
                        continue;

                if (initfn(rmem) == 0) {
                        pr_info("Reserved memory: initialized node %s, compatible id %s\n",
                                rmem->name, compat);
                        return 0;
                }
        }
        return -ENOENT;
}

DTB의 reserved-mem 노드의 sub 노드가 사용하는 디바이스명(compat)으로 커널에 등록된 __reservedmem_of_table 에서 검색하여 해당 초기화 함수를 호출한다.

  • __reservedmem_of_table에서 __rmem_of_table_sentinel까지 루프를 돌며 of_device_id 구조체 값을 가져온다.
  • if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))
    • 가져온 구조체의 compat(드라이버명)이 요청한 노드와 같은 드라이버명을 사용하지 않는 경우 continue를 호출하여 다음을 검색한다.
  •  if (initfn(rmem) == 0) {
    • 드라이버명이 같은 경우 해당 구조체의 data에 등록된 함수를 호출한다.
  • 전체를 검색하여 실패한 경우  에러를 리턴한다.

 

__reservedmem_of_table

현재 커널에는 아래와 같이 두 개의 디바이스 드라이버 코드가 준비되어 있다.

  • rpi2: __of_device_cma와 __of_device_dma 두 개 구조체명이 __reservedmem_of_table에 등록된다.

 

struct __of_device_cma

CMA for DMA mapping framework 용도의 디바이스 드라이버로 사용된다.

drivers/base/dma-contiguous.c

static const struct reserved_mem_ops rmem_cma_ops = { 
        .device_init    = rmem_cma_device_init,
        .device_release = rmem_cma_device_release,
};

static int __init rmem_cma_setup(struct reserved_mem *rmem)
{
        phys_addr_t align = PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order);
        phys_addr_t mask = align - 1;
        unsigned long node = rmem->fdt_node;
        struct cma *cma;
        int err;

        if (!of_get_flat_dt_prop(node, "reusable", NULL) ||
            of_get_flat_dt_prop(node, "no-map", NULL))
                return -EINVAL;

        if ((rmem->base & mask) || (rmem->size & mask)) {
                pr_err("Reserved memory: incorrect alignment of CMA region\n");
                return -EINVAL;
        }

        err = cma_init_reserved_mem(rmem->base, rmem->size, 0, &cma);
        if (err) {
                pr_err("Reserved memory: unable to setup CMA region\n");
                return err;
        }
        /* Architecture specific contiguous memory fixup. */
        dma_contiguous_early_fixup(rmem->base, rmem->size);

        if (of_get_flat_dt_prop(node, "linux,cma-default", NULL))
                dma_contiguous_set_default(cma);

        rmem->ops = &rmem_cma_ops;
        rmem->priv = cma;

        pr_info("Reserved memory: created CMA memory pool at %pa, size %ld MiB\n",
                &rmem->base, (unsigned long)rmem->size / SZ_1M);

        return 0;
}
RESERVEDMEM_OF_DECLARE(cma, "shared-cma-pool", rmem_cma_setup);

RESERVEDMEM_OF_DECLARE를 통해서 __of_table_cma 이름의 of_device_id 구조체가 __reservedmem_of_table에 등록된다.

  • 디바이스명(compat)은 “shared-cma-pool”이다.
  • 이 디바이스의 초기화 함수는 rmem_cma_setup() 함수이다.

 

struct __of_device_dma

DMA for Coherent per-device 메모리 핸들링을 위한 디바이스 드라이버에 사용된다.

drivers/base/dma-coherent.c

static const struct reserved_mem_ops rmem_dma_ops = { 
        .device_init    = rmem_dma_device_init,
        .device_release = rmem_dma_device_release,
};

static int __init rmem_dma_setup(struct reserved_mem *rmem)
{
        unsigned long node = rmem->fdt_node;

        if (of_get_flat_dt_prop(node, "reusable", NULL))
                return -EINVAL;

#ifdef CONFIG_ARM
        if (!of_get_flat_dt_prop(node, "no-map", NULL)) {
                pr_err("Reserved memory: regions without no-map are not yet supported\n");
                return -EINVAL;
        }   
#endif

        rmem->ops = &rmem_dma_ops;
        pr_info("Reserved memory: created DMA memory pool at %pa, size %ld MiB\n",
                &rmem->base, (unsigned long)rmem->size / SZ_1M);
        return 0;
}
RESERVEDMEM_OF_DECLARE(dma, "shared-dma-pool", rmem_dma_setup);
  • RESERVEDMEM_OF_DECLARE를 통해서 __of_table_dma 이름의 of_device_id 구조체가 __reservedmem_of_table에 등록된다.
    • 디바이스명(compat) “shared-dma-pool”이다.
    • 이 디바이스의 초기화 함수는 rmem_dma_setup() 함수이다.

 

구조체 및 전역 변수

 

reserved_mem 구조체

include/linux/of_reserved_mem.h

struct reserved_mem {
        const char                      *name;
        unsigned long                   fdt_node;
        unsigned long                   phandle;
        const struct reserved_mem_ops   *ops;
        phys_addr_t                     base;
        phys_addr_t                     size;
        void                            *priv;
};

 

reserved_mem_ops 구조체

include/linux/of_reserved_mem.h

struct reserved_mem_ops {
        int     (*device_init)(struct reserved_mem *rmem,
                               struct device *dev);
        void    (*device_release)(struct reserved_mem *rmem,
                                  struct device *dev);
};

 

전역변수

drivers/of/of_reserved_mem.c

static const struct of_device_id __rmem_of_table_sentinel
        __used __section(__reservedmem_of_table_end);

 

#define MAX_RESERVED_REGIONS    16
static struct reserved_mem reserved_mem[MAX_RESERVED_REGIONS];
static int reserved_mem_count;

 

참고