arm64_memblock_init()

<kernel v5.10>

Memblock 초기화

memblock은 커널 빌드 타임에 준비한 static 배열을 사용하여 운용하므로 memblock 자체적으로 별도의 초기화는 필요 없고,  시스템에서 사용하는 기본 영역들을 reserve 하는 준비하는 과정이 있다.

 

reserved 영역의 엔트리 등록은 초기 커널 부트업 과정에서 다음과 같은 영역을 reserved memblock에 등록하며 각 아키텍처 및 머신에 따라서 설정이 달라진다.

  • 커널 영역
  • initrd 영역
  • DTB 영역 및 DTB reserved-mem 노드가 요청하는 영역
  • CMA-DMA 영역
  • crash kernel 영역
  • elf core 헤더 영역

 

그 외에 시스템 메모리 영역을 초과하는 영역이나, DTB의 chosen 노드의 “linux,usable-memory-range” 속성으로 사용할 수 있는 메모리 영역을 제한한 경우 해당 영역을 memblock에서 제거한다.

 

다음 그림은 arm64_memblock_init() 함수에서 reserve하는 memblock들을 보여준다.

 

arm64_memblock_init()

arch/arm64/mm/init.c – 1/3-

void __init arm64_memblock_init(void)
{
        const s64 linear_region_size = BIT(vabits_actual - 1);

        /* Handle linux,usable-memory-range property */
        fdt_enforce_memory_region();

        /* Remove memory above our supported physical address size */
        memblock_remove(1ULL << PHYS_MASK_SHIFT, ULLONG_MAX);

        /*
         * Select a suitable value for the base of physical memory.
         */
        memstart_addr = round_down(memblock_start_of_DRAM(),
                                   ARM64_MEMSTART_ALIGN);

        /*
         * Remove the memory that we will not be able to cover with the
         * linear mapping. Take care not to clip the kernel which may be
         * high in memory.
         */
        memblock_remove(max_t(u64, memstart_addr + linear_region_size,
                        __pa_symbol(_end)), ULLONG_MAX);
        if (memstart_addr + linear_region_size < memblock_end_of_DRAM()) {
                /* ensure that memstart_addr remains sufficiently aligned */
                memstart_addr = round_up(memblock_end_of_DRAM() - linear_region_size,
                                         ARM64_MEMSTART_ALIGN);
                memblock_remove(0, memstart_addr);
        }

        /*
         * If we are running with a 52-bit kernel VA config on a system that
         * does not support it, we have to place the available physical
         * memory in the 48-bit addressable part of the linear region, i.e.,
         * we have to move it upward. Since memstart_addr represents the
         * physical address of PAGE_OFFSET, we have to *subtract* from it.
         */
        if (IS_ENABLED(CONFIG_ARM64_VA_BITS_52) && (vabits_actual != 52))
                memstart_addr -= _PAGE_OFFSET(48) - _PAGE_OFFSET(52);

        /*
         * Apply the memory limit if it was set. Since the kernel may be loaded
         * high up in memory, add back the kernel region that must be accessible
         * via the linear mapping.
         */
        if (memory_limit != PHYS_ADDR_MAX) {
                memblock_mem_limit_remove_map(memory_limit);
                memblock_add(__pa_symbol(_text), (u64)(_end - _text));
        }
  • 코드 라인 3에서 linear_region_size는 64비트 커널에서 사용할 가상 주소 크기의 절반을 담는다.
    • 예) 가상 주소 크기를 256T(vabits_actual=48)로 한 경우 이의 절반인 128T 크기가 연속된 영역으로 정의된다.
  • 코드 라인 6에서 디바이스 트리(FDT)가 지정한 사용 메모리 영역이 제한된 경우 그 영역 이외의 memblock 영역을 제거한다.
    • chosen 노드에 “linux,usable-memory-range” 속성으로 사용할 수 있는 메모리 영역을 제한할 수 있다.
  • 코드 라인 9에서 시스템 물리 메모리 영역을 초과하는 영역은 모두 제거한다.
  • 코드 라인 14~15에서 물리메모리의 시작 주소를 알아온다. ARM64 시스템에서 이 주소는 1G 섹션 단위로 정렬된다.
  • 코드 라인 22~29에서 lm(linear mapping) 가상 주소 영역을 초과하는 물리 주소 영역은 제거한다.
    • 커널 리니어 매핑 사이즈를 초과하는 물리 메모리의 끝을 memory memblock 영역에서 제거한다. 커널이 메모리의 끝 부분에 로드된 경우가 있으므로 이러한 경우 끝 부분을 기준으로 로드된 커널이 제거되지 않도록 제한한다.
      • 예) VA_BITS = 39, DRAM 크기 = 1TB인 경우에는 리니어 매핑 영역이 256GB로 제한되므로 768GB 메모리를 리니어 매핑으로 사용할 수 없게 된다.
    • 로드된 커널이 커널 리니어 매핑 사이즈보다 큰 메모리의 상위쪽에 로드된 경우 메모리의 상위에 위치한 커널을 보호하기 위해 커널 리니어 매핑 사이즈를 초과한 메모리의 아랫 부분을 제거한다.
  • 코드 라인 38~39에서 52bit vabits 커널이 실제 48bit vabits로 운영하는 시스템에서 동작하는 경우 lm 가상 주소와 물리 주소의 변환에 사용하는 memstart_addr 값을 조정해야 한다.
  • 코드 라인 46~49에서 DRAM 메모리 제한을 설정한 경우 제한 메모리 범위를 초과한 DRAM 메모리 영역을 memory memblock 영역에서 제거한다.

 

arch/arm64/mm/init.c – 2/3-

.       if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && phys_initrd_size) {
                /*
                 * Add back the memory we just removed if it results in the
                 * initrd to become inaccessible via the linear mapping.
                 * Otherwise, this is a no-op
                 */
                u64 base = phys_initrd_start & PAGE_MASK;
                u64 size = PAGE_ALIGN(phys_initrd_start + phys_initrd_size) - base;

                /*
                 * We can only add back the initrd memory if we don't end up
                 * with more memory than we can address via the linear mapping.
                 * It is up to the bootloader to position the kernel and the
                 * initrd reasonably close to each other (i.e., within 32 GB of
                 * each other) so that all granule/#levels combinations can
                 * always access both.
                 */
                if (WARN(base < memblock_start_of_DRAM() ||
                         base + size > memblock_start_of_DRAM() +
                                       linear_region_size,
                        "initrd not fully accessible via the linear mapping -- please check your bootloader ...\n")) {
                        phys_initrd_size = 0;
                } else {
                        memblock_remove(base, size); /* clear MEMBLOCK_ flags */
                        memblock_add(base, size);
                        memblock_reserve(base, size);
                }
        }

        if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
                extern u16 memstart_offset_seed;
                u64 range = linear_region_size -
                            (memblock_end_of_DRAM() - memblock_start_of_DRAM());

                /*
                 * If the size of the linear region exceeds, by a sufficient
                 * margin, the size of the region that the available physical
                 * memory spans, randomize the linear region as well.
                 */
                if (memstart_offset_seed > 0 && range >= ARM64_MEMSTART_ALIGN) {
                        range /= ARM64_MEMSTART_ALIGN;
                        memstart_addr -= ARM64_MEMSTART_ALIGN *
                                         ((range * memstart_offset_seed) >> 16);
                }
        }
  • 코드 라인 1~28에서 램디스크(initrd) 영역을 reserved memblock에 추가한다.
  • 코드 라인 30~45에서 보안 목적으로 CONFIG_RANDOMIZE_BASE 커널 옵션을 사용하여 커널 시작 주소가 랜덤하게 바뀌는 경우 memstart_addr을 구한다.

 

arch/arm64/mm/init.c – 3/3-

        /*
         * Register the kernel text, kernel data, initrd, and initial
         * pagetables with memblock.
         */
        memblock_reserve(__pa_symbol(_text), _end - _text);
        if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && phys_initrd_size) {
                /* the generic initrd code expects virtual addresses */
                initrd_start = __phys_to_virt(phys_initrd_start);
                initrd_end = initrd_start + phys_initrd_size;
        }

        early_init_fdt_scan_reserved_mem();

        if (IS_ENABLED(CONFIG_ZONE_DMA)) {
                zone_dma_bits = ARM64_ZONE_DMA_BITS;
                arm64_dma_phys_limit = max_zone_phys(ARM64_ZONE_DMA_BITS);
        }

        if (IS_ENABLED(CONFIG_ZONE_DMA32))
                arm64_dma32_phys_limit = max_zone_phys(32);
        else
                arm64_dma32_phys_limit = PHYS_MASK + 1;

        reserve_crashkernel();

        reserve_elfcorehdr();

        high_memory = __va(memblock_end_of_DRAM() - 1) + 1;

        dma_contiguous_reserve(arm64_dma32_phys_limit);
}
  • 코드 라인 5에서 커널 영역을 reserve한다.
  • 코드 라인 6~10에서 램디스크(initrd) 영역 주소를 가상 주소로 변환하여 저장한다.
  • 코드 라인 12에서 DTB에 관련된 다음의 세 가지 영역을 추가한다.
    • DTB 자신의 영역
    • DTB 헤더의 off_mem_rsvmap 필드가 가리키는 memory reserve 블록(바이너리)에서 읽은 메모리 영역들
    • DTB reserved-mem 노드 영역이 요청하는 영역들
  • 코드 라인 14~22에서 디바이스 드라이버(dma for coherent/cma for dma)가 필요로 하는 DMA 및 DMA32 영역을 구한다.
  • 코드 라인 24에서  crash 커널 영역을 reserve 한다.
  • 코드 라인 26에서 elf core 헤더 영역을 reserve 한다.
  • 코드 라인 28에서 ARM64의 경우 highmem을 사용하지 않는다. 따라서 메모리의 끝 주소를 대입한다.
  • 코드 라인 30에서 dma 영역을 reserved memblock에 추가하고 CMA(Contiguous Memory Allocator)에도 추가한다. 전역 cma_areas[ ] 배열에 추가한 엔트리는 CMA 드라이버가 로드되면서 초기화될 때 사용한다. 또한 전역 dma_mmu_remap[ ] 배열에 추가된 엔트리는 추후 dma_contiguous_remap( ) 함수를 통해 지정된 영역에 대응하는 페이지 테이블 엔트리들을 IO 속성으로 매핑할 때 사용한다.

 

참고

댓글 남기기