1. MMU

Page Table MMU 통해 page단위로 메모리를 실제, 가상 주소 값으로 연결해주는 역할을 한다.

동작을 살펴보면 MMU 내부에 TTBR 레지스터가 존재한다. 그리고 Register TTBR register 통해서 커널 공간과 유저 주소 공간에 매핑된 물리 자원에 접근이 가능하다.

TTBR1 register -> kernel 주소공간 page table -> physical memory
TTBR0 register -> user 주소공간 page table -> physical memory

쉽게 말하면 TTBR1 기준으로 TTBR1 register 참조해서 kernel address page table 위치를 찾아간다 kernel paget table에서는 값을변환하고자하는 index를바탕으로 page table 저장된 physical address값을 찾고 physical address 접근이 가능하다.

그리고 위에서 언급한 page table에서 index하나가 page하나가 된다. page size단위로만 메모리에 접근이 가능하다.

 

1) Page

처럼 모든 메모리 연산 처리는 page크기별로 진행된다. 그리고 page size ARM64 에서 지원하는 36,39,42,47 크기의 표현가능한 bit수를 선택함으로써 page의 갯수를 계산한다.

VA_BITS = 48, page size=4KB 경우

2^48 256TB 지원하며 page 하나의 크기는 4KB이므로 256TB/4KB = 640억개의 page 개수가 나오게 된다.

이렇게 되면 page table page갯수만큼 인덱스를 지원하므로 640억개의 index 가지게 되고

0x00000000_00000000 ~ 0x0000ffff_ffffffff 범위의 주소 공간을 차지하게 된다.

 

하지만 이렇게되면 너무 많은 주소 공간을 차지하는 문제가 발생하기 때문에 2단계, 3단계로 page table구조를만들어서 해결한다

TTBR register -> 1st page table -> 2st page table -> 3st page table -> physical address

 

2) Page Table

ARM64에서 지원하는 page table 단계별 명칭이 존재한다

PGD(1st) -> PUD(2st) -> PMD(3st) -> PTE(4st)

위에서 언급한 내용과 같이 page table 단계는 page size ARM64에서 사용하는 가상 주소 bit수의 조합에 따라서 결정된다.

이미지는 ARM64 virtual space 전체를 나타낸 것이다. Virtual size 위와 같이 kernel user 양끝에 매핑되며 kernel, user virtual space size(0x00000000_00000000~0x0000FFFF_FFFFFFFF) page size, 가상주소 bit, page table 단계에 따라서 결정된다.

 

2. Kernel Virtual Space

ARM64 virtual space 보면 위와같다 그리고 kernel space 위에 붙어있다. 그리고 kernel space 자세히 살펴보면 아래와 같다.

주소와 같이 kernel space 여러 역할로 나누어진다.

Linear Region : physical address 1:1 매핑

vmemmap     : fast mapping지원을 위해 따로 할당한 영역

pci I/O                 : PCI device 사용을위한 I/O 매핑

fix-map               : 컴파일 목적에 따라 이미 결정된 매핑영역

vmalloc               : vmalloc() -> vmap함수를 통해 할당하는 공간이고 PCI device를제외한 다른 device I/O 할당을 위해서도 사용

 

fixmap 경우 부팅 초반에 아직 가상메모리 관련해서 설정이 되어있지 않은 경우 early 매핑을 지원하기위한 목적으로도 사용한다.

 

1) Paging 초기화

부팅 초반에는 위와같은 virtual address 아예 할당되지 않았다 때문에 사용이불가능하고 fixmap 활용해서 진행한다.

arch/arm64/mm/mmu.c에서는 virtual address사용을위한 MMU 초기화가 진행된다.

void __init paging_init(void)
{
        
pgd_t *pgdp = pgd_set_fixmap(__pa_symbol(swapper_pg_dir));

        map_kernel(pgdp);
        
map_mem(pgdp);

        pgd_clear_fixmap();
        cpu_replace_ttbr1(lm_alias(swapper_pg_dir));
        
init_mm.pgd = swapper_pg_dir;

        memblock_free(__pa_symbol(init_pg_dir),
                     
__pa_symbol(init_pg_end) - __pa_symbol(init_pg_dir));

        memblock_allow_resize();
}

가장 먼저 pgd_set_fixmap(__pa_symbol(swapper_pg_dir)) 통해서 fixmap pgd에서 swapper_pg_dir 주소값을 가리키도록 한다.

swapper_pg_dir 1st page table 주소값을 가리킨다.

 

이후 map_kernel, map_mem 통해서 매핑을 진행한다.

그리고 pgd_clear_fixmap 통해서 처음 swapper_pg_dir 가리키게 설정한 fixmap pgd값을 지워준다.(설정한 이유는 아직 virtual공간이 제대로 할당되지 않은 순간에서 fixmap을통해 단순히 커널과 메모리간의 mapping을진행하기위함)

다음으로 TTBR1 register(kernel) 1st page_table주소 값을 가리키는 swapper_pg_dir 연결해준다.

 

map_kernel 경우

/*
 * Create fine-grained mappings for the kernel.
 */
static void __init map_kernel(pgd_t *pgdp)
{
        
static struct vm_struct vmlinux_text, vmlinux_rodata, vmlinux_inittext,
                                vmlinux_initdata
, vmlinux_data;

/*
         * Only rodata will be remapped with different permissions later on,
         * all other segments are allowed to use contiguous mappings.
         */
        
map_kernel_segment(pgdp, _stext, _etext, text_prot, &vmlinux_text, 0, VM_NO_GUARD);
        
map_kernel_segment(pgdp, __start_rodata, __inittext_begin, PAGE_KERNEL, &vmlinux_rodata, NO_CONT_MAPPINGS, VM_NO_GUARD);
        
map_kernel_segment(pgdp, __inittext_begin, __inittext_end, text_prot, &vmlinux_inittext, 0, VM_NO_GUARD);
        
map_kernel_segment(pgdp, __initdata_begin, __initdata_end, PAGE_KERNEL, &vmlinux_initdata, 0, VM_NO_GUARD);
        
map_kernel_segment(pgdp, _data, _end, PAGE_KERNEL, &vmlinux_data, 0, 0);

if (!READ_ONCE(pgd_val(*pgd_offset_pgd(pgdp, FIXADDR_START)))) {
                
/*
                 * The fixmap falls in a separate pgd to the kernel, and doesn't
                 * live in the carveout for the swapper_pg_dir. We can simply
                 * re-use the existing dir for the fixmap.
                 */
                
set_pgd(pgd_offset_pgd(pgdp, FIXADDR_START),
                        
READ_ONCE(*pgd_offset_k(FIXADDR_START)));
        
} else if (CONFIG_PGTABLE_LEVELS > 3) {
                
pgd_t *bm_pgdp;
                
p4d_t *bm_p4dp;
                
pud_t *bm_pudp;
                
/*
                 * The fixmap shares its top level pgd entry with the kernel
                 * mapping. This can really only occur when we are running
                 * with 16k/4 levels, so we can simply reuse the pud level
                 * entry instead.
                 */
                
BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));
                bm_pgdp
= pgd_offset_pgd(pgdp, FIXADDR_START);
                bm_p4dp
= p4d_offset(bm_pgdp, FIXADDR_START);
                bm_pudp
= pud_set_fixmap_offset(bm_p4dp, FIXADDR_START);
                
pud_populate(&init_mm, bm_pudp, lm_alias(bm_pmd));
                
pud_clear_fixmap();
        
} else {
                
BUG();
        
}

kasan_copy_shadow(pgdp);
}

map_kernel 경우 위와같이 rdata, data 영역등을 나누어서 할당하며 read,write,access 가능한 영역을 start, end등을 통해 명확히 규정한다. 그리고 kernel mapping 위해서 fixmap pgd값을 활용하고 있다. 때문에 map_kernel mapping을진행하기 전에 set_fixmap 통해서 임시로 설정을 진행한 것이다.

이후 map_mem() 진행한다.

static void __init map_mem(pgd_t *pgdp)
{
        
static const u64 direct_map_end = _PAGE_END(VA_BITS_MIN);
        
phys_addr_t kernel_start = __pa_symbol(_stext);
        
phys_addr_t kernel_end = __pa_symbol(__init_begin);
        
phys_addr_t start, end;
        
int flags = NO_EXEC_MAPPINGS;
        
u64 i;

/*
         * Setting hierarchical PXNTable attributes on table entries covering
         * the linear region is only possible if it is guaranteed that no table
         * entries at any level are being shared between the linear region and
         * the vmalloc region. Check whether this is true for the PGD level, in
         * which case it is guaranteed to be true for all other levels as well.
         */
        
BUILD_BUG_ON(pgd_index(direct_map_end - 1) == pgd_index(direct_map_end));

if (can_set_direct_map() || crash_mem_map || IS_ENABLED(CONFIG_KFENCE))
                flags
|= NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS;

/*
         * Take care not to create a writable alias for the
         * read-only text and rodata sections of the kernel image.
         * So temporarily mark them as NOMAP to skip mappings in
         * the following for-loop
         */
        
memblock_mark_nomap(kernel_start, kernel_end - kernel_start);

/* map all the memory banks */
        
for_each_mem_range(i, &start, &end) {
                
if (start >= end)
                        
break;
                
/*
                 * The linear map must allow allocation tags reading/writing
                 * if MTE is present. Otherwise, it has the same attributes as
                 * PAGE_KERNEL.
                 */
                
__map_memblock(pgdp, start, end, pgprot_tagged(PAGE_KERNEL),
                               flags
);
        
}

/*
         * Map the linear alias of the [_stext, __init_begin) interval
         * as non-executable now, and remove the write permission in
         * mark_linear_text_alias_ro() below (which will be called after
         * alternative patching has completed). This makes the contents
         * of the region accessible to subsystems such as hibernate,
         * but protects it from inadvertent modification or execution.
         * Note that contiguous mappings cannot be remapped in this way,
         * so we should avoid them here.
         */
        
__map_memblock(pgdp, kernel_start, kernel_end,
                      
PAGE_KERNEL, NO_CONT_MAPPINGS);
        
memblock_clear_nomap(kernel_start, kernel_end - kernel_start);
}

map_mem() fucntion 크게 2가지 역할을 수행한다 가장 먼저 kernel_image 시작 주소값과 size 전달해줌으로써 vmalloc read only feature 가상주소와 맵핑하는것 그리고 pysical주소에 기존 memblock()으로 구성된 데이터들을 반복문으로 모든 memblock() 시작 address size 계산하고 전달해줌으로써 Linear Region 1:1 맵핑해주는 작업이다.

 

결과적으로 정리하면 map_kernel 통해서 TTBR1 register kernel 가상 주소 table(page table) 연결하는(매핑하는)작업을 수행하고

map_mem 통해서 기존 memblock으로 구성한 physical address virtual address 연결하는 작업이다.(pgd table, pme, pte page table에서 physical address 가리키도록 하는 작업)

 

  • ref

ARM 리눅스 커널 출판사 제이펍

https://www.programmerall.com/article/34561974480/

출처: <https://elixir.bootlin.com/linux/v5.14.16/source/arch/arm64/mm/mmu.c>

http://www.iamroot.org/xe/index.php?mid=Programming&document_srl=210100

+ Recent posts