bootmem_init() – ARM64

<kernel v5.0>

부트 메모리 초기화

bootmem_init()

arch/arm64/mm/init.c

void __init bootmem_init(void)
{
        unsigned long min, max;

        min = PFN_UP(memblock_start_of_DRAM());
        max = PFN_DOWN(memblock_end_of_DRAM());

        early_memtest(min << PAGE_SHIFT, max << PAGE_SHIFT);

        max_pfn = max_low_pfn = max;

        arm64_numa_init();
        /*
         * Sparsemem tries to allocate bootmem in memory_present(), so must be
         * done after the fixed reservations.
         */
        arm64_memory_present();

        sparse_init();
        zone_sizes_init(min, max);

        memblock_dump_all();
}

부트 메모리 초기화 루틴에서는 각 노드의 각 존별 초기화를 수행한다.

  • 코드 라인 5~8에서 “memtest=<count>” 커널 파라메터가 주어진 경우 메모리에 대한 패턴 테스트를 <count> 수 만큼 수행한다.
  • 코드 라인 10에서 arm64에는 highmem이 없다. 따라서 highmem 경계를 나타내는 max_low_pfn은 max 값과 동일하다.
  • 코드 라인 12에서 NUMA 시스템인 경우 초기화를 수행한다. ACPI 또는 디바이스 트리를 통해 초기화를 수행한다. ACPI 또는 디바이스 트리를 통해 NUMA 초기화를 수행하지 못하는 경우이거나,  “numa=off” 커널 파라메터가 주어진 경우 NUMA disable 상태로 초기화를 수행한다.
  • 코드 라인 17에서 각 메모리 모델의 관리를 위해 필요한 데이터를 초기화한다.
    • sparse memory 모델의 경우 memblock에 등록된 모든 메모리 블록에 대해 섹션별로 메모리가 존재하는 곳의 mem_section을 초기화한다.
  • 코드 라인 19에서 Sparse memory 모델을 사용하는 시스템을 위해 관리 영역을 할당받고 매핑 초기화한다.
  • 코드 라인 20에서 존별로 메모리 영역을 지정하고 초기화한다.
  • 코드 라인 22에서 “memblock=debug” 커널 파라메터가 주어진 경우 memblock 상태를 덤프한다.

 

다음은 4G RAM을 가진 rock960 보드의 memblock 상태를 덤프하여 보여준다.

$ cat /sys/kernel/debug/memblock/memory
   0: 0x0000000000200000..0x00000000f7ffffff
$ cat /sys/kernel/debug/memblock/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

 


 

zone_sizes_init() – NUMA

arch/arm64/mm/init.c

static void __init zone_sizes_init(unsigned long min, unsigned long max)
{
        unsigned long max_zone_pfns[MAX_NR_ZONES]  = {0};

        if (IS_ENABLED(CONFIG_ZONE_DMA32))
                max_zone_pfns[ZONE_DMA32] = PFN_DOWN(max_zone_dma_phys());
        max_zone_pfns[ZONE_NORMAL] = max;

        free_area_init_nodes(max_zone_pfns);
}

메모리 모델에 따른 노드, 존의 초기화를 다룬다.

  • 코드 라인 5~6에서 dma32 존의 끝(pfn)을 지정한다.
    • 실제 끝 pfn보다 1 추가된 값을 가진다. (예: 0xf_ffff -> 0x10_0000)
  • 코드 라인 7에서 normal 존의 끝(pfn)을 지정한다.
    • 메모리가 dma32 존에 들어갈 정도로 작은 경우 dma32 존과 normal 존에 대한 max_zone_pfns[] 값은 동일하다.
    • 예) 1G 메모리
      • max_zone_pfns[] = { 0x8_0000, 0x8_0000, 0 }
  • 코드 라인 9에서 max_zone_pfns[] 배열 정보를 사용하여 빈 페이지들을 초기화한다.

 

다음 그림은 CONFIG_ZONE_DMA32 및 CONFIG_NUMA 커널 옵션을 사용한 경우 max_zone_pfns[] 값을 산출하는 과정이다.

  • DMA 영역은 물리 공간에서 최대 4G 영역을 사용할 수 있으며, 4G 단위의 영역 경계를 넘어가지 못한다.

 

zone_sizes_init() – UMA

arch/arm64/mm/init.c

static void __init zone_sizes_init(unsigned long min, unsigned long max)
{
        struct memblock_region *reg;
        unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
        unsigned long max_dma = min;

        memset(zone_size, 0, sizeof(zone_size));

        /* 4GB maximum for 32-bit only capable devices */
#ifdef CONFIG_ZONE_DMA32
        max_dma = PFN_DOWN(arm64_dma_phys_limit);
        zone_size[ZONE_DMA32] = max_dma - min;
#endif
        zone_size[ZONE_NORMAL] = max - max_dma;

        memcpy(zhole_size, zone_size, sizeof(zhole_size));

        for_each_memblock(memory, reg) {
                unsigned long start = memblock_region_memory_base_pfn(reg);
                unsigned long end = memblock_region_memory_end_pfn(reg);

                if (start >= max)
                        continue;

#ifdef CONFIG_ZONE_DMA32
                if (start < max_dma) {
                        unsigned long dma_end = min(end, max_dma);
                        zhole_size[ZONE_DMA32] -= dma_end - start;
                }
#endif
                if (end > max_dma) {
                        unsigned long normal_end = min(end, max);
                        unsigned long normal_start = max(start, max_dma);
                        zhole_size[ZONE_NORMAL] -= normal_end - normal_start;
                }
        }

        free_area_init_node(0, zone_size, min, zhole_size);
}

메모리 모델에 따른 노드, 존의 초기화를 다룬다.

  • 코드 라인 10~13에서 dma32 존 사이즈를 지정한다.
  • 코드 라인 14에서 dma32 존 사이즈를 제외한 메모리로 normal 존 사이즈를 지정한다.
  • 코드 라인 16에서 zone_size[ ]를 zhole_size[ ]에 복사한다.
  • 코드 라인 18~23에서 memory memblock을 루프를 돌며 메모리의 끝 주소 이상인 영역은 skip 한다.
  • 코드 라인 25~30에서 max_dma 아래에 위치한 영역에 대해 dma32 존에 대한 zhole_size를 구한다.
  • 코드 라인 31~35에서 max_dma를 초과하는 영역에 대해 normal 존에 대한 zhole_size를 구한다.
  • 코드 라인 38에서 0번 노드에 대해 zone_size[ ]와, zhole_size[ ] 정보를 사용하여 빈 페이지들을 초기화한다.

 

다음 그림은 CONFIG_ZONE_DMA32 커널 옵션을 사용한 경우 zone_size[] 및 zhole_size[] 값을 산출하는 과정이다.

  • DMA 영역은 물리 공간에서 최대 4G 영역을 사용할 수 있으며, 4G 단위의 영역 경계를 넘어가지 못한다.

 

max_zone_dma_phys()

arch/arm64/mm/init.c

/*
 * Return the maximum physical address for ZONE_DMA32 (DMA_BIT_MASK(32)). It
 * currently assumes that for memory starting above 4G, 32-bit devices will
 * use a DMA offset.
 */
static phys_addr_t __init max_zone_dma_phys(void)
{
        phys_addr_t offset = memblock_start_of_DRAM() & GENMASK_ULL(63, 32);
        return min(offset + (1ULL << 32), memblock_end_of_DRAM());
}

DMA 가능한 최대 물리 메모리의 끝 주소를 반환한다. 단 DMA 가능한 물리 메모리의 끝 주소는 시작 주소를 4G 단위로 내림 정렬한 후 4G를 더한 값을 초과하지 않는다. DMA 영역은 물리 공간에서 최대 4G 영역을 사용할 수 있으며, 4G 단위의 영역 경계를 넘어가지 못한다.

  • 예) DRAM 시작 주소=0x8_8000_0000, DRAM 끝 주소=0x8_f000_0000
    • offset=0x8_0000_0000, 반환 값=0x8_f000_0000
  • 예) DRAM 시작 주소=0x8_8000_0000, DRAM 끝 주소=0x9_800_0000
    • offset=0x8_0000_0000, 반환 값=0x9_0000_0000

 

GENMASK_ULL()

include/linux/bits.h

#define GENMASK_ULL(h, l) \
        (((~0ULL) - (1ULL << (l)) + 1) & \
         (~0ULL >> (BITS_PER_LONG_LONG - 1 - (h))))

high 비트 번호 @h 부터 low 비트 번호 @l 까지 1로 설정한 64비트 값을 반환한다.

  • 예) h=63, l=32
    • 0xffff_ffff_0000_0000

 

참고

댓글 남기기

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