sanity_check_meminfo()

이 함수에서는 등록된 memory memblock에 대해 미리 사전 체크를 하여 early memory allocator로 동작할 수 있도록 다음과 같이 준비한다.

  • lowmem 영역만 사용해야 하는 case에 대해 memblock 영역 삭제
    • HIGHMEM을 사용하지 않을 경우 또는 캐시가 VIPT aliasing을 사용하는 경우 memory 영역에 등록된 memblock 들이 lowmem 영역을 초과하는 경우 해당 초과 영역들을 제거한다.
  • arm_lowmem_limithigh_memory 설정
    • memory block의 끝 주소를 arm_lowmem_limit으로 하되  vmalloc_limit(vmalloc_min의 물리주소)을 초과하지 않도록 한다.
    • high_memory는 arm_lowmem_limit의 가상 주소 값이다.
  • memblock.current_limit 설정
    • memory memblock들이 2M 단위로 align되어 있어야 커널 설정 초기에 사용되는 early memory allocator에서 2M 영역을 할당하여 사용하는 reserve memblock을 운영하여야 하므로 각 memory memblock 들이 2M align되어 있지 않은 memblock이 있는 경우 그 지점의 2M round down 주소까지로 사용을 제한하도록 memblock_limit를 설정한다.

sanity_check_meminfo_1a

 

sanity_check_meminfo()

arch/arm/mm/mmu.c

void __init sanity_check_meminfo(void)
{
        phys_addr_t memblock_limit = 0; 
        int highmem = 0; 
        phys_addr_t vmalloc_limit = __pa(vmalloc_min - 1) + 1; 
        struct memblock_region *reg;

        for_each_memblock(memory, reg) {
                phys_addr_t block_start = reg->base;
                phys_addr_t block_end = reg->base + reg->size;
                phys_addr_t size_limit = reg->size;

                if (reg->base >= vmalloc_limit)
                        highmem = 1; 
                else
                        size_limit = vmalloc_limit - reg->base;


                if (!IS_ENABLED(CONFIG_HIGHMEM) || cache_is_vipt_aliasing()) {

                        if (highmem) {
                                pr_notice("Ignoring RAM at %pa-%pa (!CONFIG_HIGHMEM)\n",
                                          &block_start, &block_end);
                                memblock_remove(reg->base, reg->size);
                                continue;
                        }

                        if (reg->size > size_limit) {
                                phys_addr_t overlap_size = reg->size - size_limit;

                                pr_notice("Truncating RAM at %pa-%pa to -%pa",
                                          &block_start, &block_end, &vmalloc_limit);
                                memblock_remove(vmalloc_limit, overlap_size);
                                block_end = vmalloc_limit;
                        }
                }
  • phys_addr_t vmalloc_limit = __pa(vmalloc_min – 1) + 1;
    • vmalloc_min
      • VMALLOC_END(0xff00_0000) – (240 << 20) – VMALLOC_OFFSET(8M)
      • 0xff00_000 – 240M – 8M = 0xef80_0000
      • VMALLOC 영역이 최소 보장되어야 하는 하한 주소로 아키텍처 마다 다르다.
        • 32 bit ARM 에서는 0xef80_0000으로 고정되어 사용된다.
    • vmalloc_limit
      • vmalloc_min의 물리 주소가 담긴다.
      • rpi2: 0x6f80_0000
  • for_each_memblock(memory, reg) {
    • 등록된 전체 memory memblock 영역을 루프로 돈다.
  • if (reg->base >= vmalloc_limit)
    • 영역의 시작 물리 주소가 vmalloc_limit을 초과한 경우 highmem 영역이라 판단한다.
  • if (!IS_ENABLED(CONFIG_HIGHMEM) || cache_is_vipt_aliasing()) {
    • HIGHMEM 설정이 안되어 있거나 d-cache가 vipt aliasing을 사용하는 경우
      • rpi2: d-cache는 CACHEID_VIPT_NONALIASING
  • if (highmem) {
    • highmem 영역을 사용하는 블럭은 삭제한다.
  • if (reg->size > size_limit) {
    • size가 lowmem 영역을 넘어가는 경우 넘어가는 부분 만큼을 제거한다.

HIGHMEM을 사용하지 않는 경우 memblock들이 vmalloc_limit을 초과하는 경우 해당 영역을 제거한다.

sanity_check_meminfo_3b

 

                if (!highmem) {
                        if (block_end > arm_lowmem_limit) {
                                if (reg->size > size_limit)
                                        arm_lowmem_limit = vmalloc_limit;
                                else
                                        arm_lowmem_limit = block_end;
                        }
  • if (block_end > arm_lowmem_limit) {
    • 블럭이 arm_lowmem_limit를 초과한 경우
  • if (reg->size > size_limit)
    • 블럭 사이즈가 lowmem 영역까지 남은 공간을 초과하는 경우 arm_lowmem_limit에 vmalloc_limit을 대입하고 그렇지 않은 경우 블럭의 끝을 지정한다.

sanity_check_meminfo_4a

 

                        /*
                         * Find the first non-pmd-aligned page, and point
                         * memblock_limit at it. This relies on rounding the
                         * limit down to be pmd-aligned, which happens at the
                         * end of this function.
                         *
                         * With this algorithm, the start or end of almost any
                         * bank can be non-pmd-aligned. The only exception is
                         * that the start of the bank 0 must be section-
                         * aligned, since otherwise memory would need to be
                         * allocated when mapping the start of bank 0, which
                         * occurs before any free memory is mapped.
                         */
                        if (!memblock_limit) {
                                if (!IS_ALIGNED(block_start, PMD_SIZE))
                                        memblock_limit = block_start;
                                else if (!IS_ALIGNED(block_end, PMD_SIZE))
                                        memblock_limit = arm_lowmem_limit;
                        }

                }
        }

        high_memory = __va(arm_lowmem_limit - 1) + 1;

        /*
         * Round the memblock limit down to a pmd size.  This
         * helps to ensure that we will allocate memory from the
         * last full pmd, which should be mapped.
         */
        if (memblock_limit)
                memblock_limit = round_down(memblock_limit, PMD_SIZE);
        if (!memblock_limit)
                memblock_limit = arm_lowmem_limit;

        memblock_set_current_limit(memblock_limit);
}
  • if (!memblock_limit) {
    • memblock_limit값이 설정되지 않았으면
  • if (!IS_ALIGNED(block_start, PMD_SIZE))
    • 블럭의 시작 주소가 2M align되어 있지 않은 경우 memblock_limit에 블럭 시작 주소를 대입한다.
  • else if (!IS_ALIGNED(block_end, PMD_SIZE))
    • 블럭의 끝 주소가 2M align되어 있지 않은 경우 memblock_limit에 블럭 끝 주소를 대입한다.
  • memblock_limit = round_down(memblock_limit, PMD_SIZE);
    • memblock_limit 주소를 2M round down 한다.
  • memblock_set_current_limit(memblock_limit);
    • 전역 변수 memblock.current_limit를 설정한다.
  • 등록된 memblock은 커널 설정 초기에 2M 단위의 메모리를 할당 받아 사용한다. 따라서 align되지 않은 메모리가 배열에 등록된 경우 align 된 영역까지만 사용하고 나머지 메모리는 사용하지 않도록 memblock_limit를 설정한다.

sanity_check_meminfo_5a

 

lowmem 영역

  • lowmem 영역은 물리 메모리가 1:1로 커널 영역에 매핑되어 사용할 수 있는 영역이다.
    • vmalloc_limit
      • 현재 커널에서 lowmem 영역을 최대 키울 수 있는 한도내의 물리 메모리 끝 주소
      • 메모리 크기와 관계 없이 커널 영역의 크기에 따라 계산되는 물리 주소
      • 예)
        • VM_SPLIT_3G: 0x2f80_0000 (max lowmem=760M)
        • VM_SPLIT_2G: 0x6f80_0000 (max lowmem=1G+760M)
    • arm_lowmem_limit
      • 물리 메모리 크기가 max lowmem을 초과하는 경우 arm_lowmem_limit는 vmalloc_limit 값과 동일하다.
      • 물리 메모리 크기가 max lowmem보다 작은 경우 arm_lowmem_limit는 물리 메모리의 끝 주소가 대입된다.
    • high_memory
      • arm_lowmem_limit의 가상 주소와 동일하다.

sanity_check_meminfo_2b

 

참고

댓글 남기기