arm64_memblock_init()

<kernel v5.0>

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/2-

void __init arm64_memblock_init(void)
{
        const s64 linear_region_size = -(s64)PAGE_OFFSET;

        /* 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);

        /*
         * Ensure that the linear region takes up exactly half of the kernel
         * virtual address space. This way, we can distinguish a linear address
         * from a kernel/module/vmalloc address by testing a single bit.
         */
        BUILD_BUG_ON(linear_region_size != BIT(VA_BITS - 1));

        /*
         * 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);
        }

        /*
         * 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비트 커널에서 사용할 가상 주소 크기의 절반을 담는다.
    • 예) 가상 주소 크기를 512G(VA_BITS = 39)로 한 경우 256G 크기가 연속된 영역으로 정의된다.
  • 코드 라인 6에서 디바이스 트리(FDT)가 지정한 사용 메모리 영역이 제한된 경우 그 영역 이외의 memblock 영역을 제거한다.
    • chosen 노드에 “linux,usable-memory-range” 속성으로 사용할 수 있는 메모리 영역을 제한할 수 있다.
  • 코드 라인 9에서 시스템 물리 메모리 영역을 초과하는 영역은 모두 제거한다.
  • 코드 라인 21~22에서 물리 메모리의 시작 주소는 커널 설정에 따라 섹션 크기 또는 pud 크기로 정렬하여 사용한다.
  • 코드 라인 29~30에서 커널 리니어 매핑 사이즈를 초과하는 물리 메모리의 끝을 memory memblock 영역에서 제거한다. 커널이 메모리의 끝 부분에 로드된 경우가 있으므로 이러한 경우 끝 부분을 기준으로 로드된 커널이 제거되지 않도록 제한한다.
    • 예) VA_BITS = 39, DRAM 크기 = 1TB인 경우에는 리니어 매핑 영역이 256GB로 제한되므로 768GB 메모리를 리니어 매핑으로 사용할 수 없게 된다.
  • 코드 라인 만일 31~36 로드된 커널이 커널 리니어 매핑 사이즈보다 큰 메모리의 상위쪽에 로드된 경우 메모리의 상위에 위치한 커널을 보호하기 위해 커널 리니어 매핑 사이즈를 초과한 메모리의 아랫 부분을 제거한다.
  • 코드 라인 43~46에서 DRAM 메모리 제한을 설정한 경우 제한 메모리 범위를 초과한 DRAM 메모리 영역을 memory memblock 영역에서 제거한다.

 

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

        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_size);

                /*
                 * 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 booo
tloader ...\n")) {
                        initrd_start = 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);
                }
        }

        /*
         * 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();

        /* 4GB maximum for 32-bit only capable devices */
        if (IS_ENABLED(CONFIG_ZONE_DMA32))
                arm64_dma_phys_limit = max_zone_dma_phys();
        else
                arm64_dma_phys_limit = PHYS_MASK + 1;

        reserve_crashkernel();

        reserve_elfcorehdr();

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

        dma_contiguous_reserve(arm64_dma_phys_limit);
}

시스템에서 사용할 몇 개의 영역을 memblock에 reserve한다.

  • 코드 라인 1~29에서 램디스크(initrd) 영역을 reserved memblock에 추가한다.
  • 코드 라인 31~46에서 보안 목적으로 CONFIG_RANDOMIZE_BASE 커널 옵션을 사용하여 커널 시작 주소가 랜덤하게 바뀌는 경우 memstart_addr을 구한다.
  • 코드 라인 52에서 커널 영역을 reserve한다.
  • 코드 라인 53~57에서 램디스크(initrd) 영역 주소를 가상 주소로 변환하여 저장한다.
  • 코드 라인 59에서 DTB에 관련된 다음의 세 가지 영역을 추가한다.
    • DTB 자신의 영역
    • DTB 헤더의 off_mem_rsvmap 필드가 가리키는 memory reserve 블록(바이너리)에서 읽은 메모리 영역들
    • DTB reserved-mem 노드 영역이 요청하는 영역들
  • 코드 라인 62~65에서 디바이스 드라이버(dma for coherent/cma for dma)가 필요로 하는 DMA 영역을 구한다.
  • 코드 라인 67에서  crash 커널 영역을 reserve 한다.
  • 코드 라인 69에서 elf core 헤더 영역을 reserve 한다.
  • 코드 라인 71에서 ARM64의 경우 highmem을 사용하지 않는다. 따라서 메모리의 끝 주소를 대입한다.
  • 코드 라인 73에서 dma 영역을 reserved memblock에 추가하고 CMA(Contiguous Memory Allocator)에도 추가한다. 전역 cma_areas[ ] 배열에 추가한 엔트리는 CMA 드라이버가 로드되면서 초기화될 때 사용한다. 또한 전역 dma_mmu_remap[ ] 배열에 추가된 엔트리는 추후 dma_contiguous_remap( ) 함수를 통해 지정된 영역에 대응하는 페이지 테이블 엔트리들을 IO 속성으로 매핑할 때 사용한다.

 

참고

댓글 남기기

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