Os Arm Linux Kernelv4.6 1
chapter 2
이 챕터에서는 커널의 초기 부팅 과정을 담당하는 head.S와 런타임 중에 발생하는 익셉션을 처리하는 익셉션 핸들링 코드에 대해서 다룬다
2.0
32비트와 64비트 커널은 가상 주소 공간의 크기가 다르다
-
32비트
전체 주소 공간을 유저 주소 공간과 커널 주소 공간으로 분할하여 사용한다 -> 3G/1G, 2G/2G, 1G/3G중 하나로 분할한다
-
64비트
전체 주소 공간이 다 매핑되지 않는다. 유저 주소 공간과 커널 주소 공간의 크기는 CONFIG_ARM64_VA_BITS에 의해 동일한 사이즈로 결정된다. 유저 주소 공간은 0번지에서 시작하고 커널 주소 공간은 주소 공간의 끝에 위치한다. 공간의 크기는 주소 변환 단위인 페이지 크기에 따라 달라진다.
아직 왜 페이지 크기에 영향을 받는 것인지 이해를 하지 못했다. 마찬가지로 아래의 계산도 이해하지 못했다.
ARM64_4K_PAGES를 기본 값으로 선택하는데 VA_BITS는 39비트가 된다.
2.1 커널의 진입점 head.S
head.S
는 CPU의 초기화를 담당한다.
ARM64 커널의 실행 조건은 다음과 같이 정의되어 있다.
- MMU는 꺼져 있을 것
- 데이터 캐시는 꺼져 있을 것(인스트럭션은 상관X)
- x0 레지스터는 FDT 바이너리의 물리 주소를 가르킬것
EFI 관련 코드를 제외하면 stext
에서 시작하며 stext의 실행 흐름은 다음과 같다.
- 부트 파라미터 저장
- EL2로 진입했을 경우 EL2 설정
- 커널 이미지의 가상 주소와 물리 주소 사이 오프셋 계산
- 커널을 위한 페이지 테이블 설정
- 프로세서 초기화
- MMU 활성화
- start_kernel로 점프
stext 프로시저는 ENTRY(심볼 이름) ~ ENDPROC(심볼 이름)의 형태로 둘러싸여 있다. ENTRY는 심볼을 링커에게 제공(.global)하고 심볼을 정렬(.align)한다. ENDPROC은 심볼이 함수임을 알려주고 심볼의 크기를 계산한다.
2.1.1 부트 파라미터 저장
preserve_boot_args 프로시저는 커널 부팅을 위해 전달된 부트 파라미터를 저장한다. x0
레지스터만 dtb(device tree blob)의 물리주소
로 명시된다. MMU가 꺼진 상태에서 저장 명령 이후에 캐시가 정리되도록 메모리 베리어를 사용한다.(dmb sy). __inval_cache_range을 통해 x0, x1 레지스터가 가리키는 메모리 영역의 캐시를 무효화한다.(boot_args에 저장한 메모리 영역)
2.1.2 EL2 설정
el2_setup 프로시저는 하이퍼바이저 동작을 위한 레지스터를 설정한다. 커널이 EL1으로 부팅되었다면 시스템의 엔디안 설정, 부트 모드를 기록하는 부분만 수행된다. CPU_BE, CPU_LE 매크로를 통해 엔디안을 설정한다. 그리고 w20 레지스터에 EL1에서 부팅했음을 기록하고 리턴한다.
2.1.3 CPU 부트 모드 저장
set_cpu_boot_mode_flag 프로시저는 CPU 부트 모드를 별도의 변수(__boot_cpu_mode)에 저장한다. 저장된 부트 모드는 추후 CPU가 EL1으로 부팅했는지 EL2로 부팅했는지 확인하는 용도로 사용한다. 이 변수는 .data.cacheline_aligned 섹션에 저장한다. .pushsection은 현재 섹션을 링커 스택에 저장하고 인자로 지정한 섹션으로 전환한다. popsection은 이전 섹션을 복원한다. L1_CACHE_SHIFT 단위로 정렬하는데 일반적으로 캐시 라인으로 정렬하면 한 번의 메모리 접근으로 연속된 데이터를 캐시에 올려둘 수 있어 실행 속도의 향상이 가능하다.
2.1.4 페이지 테이블 생성
__create_page_tables 프로시저는 MMU를 활성화하기 위한 페이지 테이블을 생성한다. idmap
페이지 테이블은 MMU가 켜질 때 사용되고 swapper
페이지 테이블은 커널 이미지 영역을 매핑한다.
- idmap은 가상 주소를 물리 주소와 같은 주소로 1:1 매핑하는 것. MMU가 켜졌을 때 다음에 실행할 인스트럭션은 아직 물리 주소이므로 이 주소에 대한 매핑을 idmap 페이지 테이블에 생성해둔다.
- swapper 페이지 테이블은 start_kernel()에서 호출하는 paging_init()에서 다시 매핑된다.
각 페이지 테이블의 주소는 TTBR
에 저장되므로 정렬 제한이 존재하며 커널은 이를 지키기 위해 vmlinux.lds에서 페이지 크기로 정렬해 배치한다. 데이터 캐시를 무효화하여 더티 캐시를 제거하고 stp 명령어를 활용하여 테이블 엔트리를 0으로 초기화한다.(post-index 활용). 페이지 테이블 생성 준비 과정이 끝나면 본격적으로 엔트리를 설정한다.
일반적으로 레지스터에 값을 저장하기 위해 사용되는 mov 대신 ldr을 사용한 이유는 저장해야 할 값이 크기 때문이다, mov는 immediate로 16비트까지만 지정 가능하지만, ldr은 적당한 명령어로 변환되어 32비트 크기까지 저장할 수 있다.
VA_BITS가 48보다 작을 경우 높은 물리 주소에 위치한 시스템 RAM 영역을 모두 매핑하기에 부족할 수 있으므로 별도의 작업이 필요한지 판단한다.
-
create_pgd_entry는 tbl로 주어진 테이블용 메모리 공간에
pgd
,pud
,pte
순으로 페이지 테이블을 생성하는 매크로이다. 페이지 테이블을 생성하고 연결하는 래퍼 매크로다. SWAPPER_PGTABLE_LEVELS 정의에 따라 create_table_entry 매크로를 호출해 테이블 엔트리를 생성한다.tbl이 무엇인지는 모르겠다..
-
create_table_entry 매크로는 가상 주소에 해당하는 페이지 테이블 엔트리의 하위 테이블을 생성하고 엔트리로 가르킨다.
-
create_block_map 매크로를 통해 idmap 페이지 테이블의 가상 주소에 대한 블록 매핑을 수행한다. 페이지 테이블 블록 엔트리를 생성하는 매크로다. 가상 주소의 시작과 끝 주소를 받아 해당 영역에 대해 SWAPPER_BLOCK_SIZE 단위로 엔트리를 생성한다. 각 엔트리의 내용은 전달받은 물리 주소와 속성으로 생성된다.
커널 이미지 가상주소에 KASLR 변위만큼 더하여 커널 주소를 계산하여 create_pgd_entry로 swapper 페이지 테이블을 생성한다. create_block_map을 통해 가상 주소 KIMAGE_VADDR 부터 kernel_img_size만큼을 물리 주소 __PHYS_OFFSET으로 블록 매핑한다.
- __PHYS_OFFSET은 (KERNEL_START - TEXT_OFFSET)으로 정의되있다.
TEXT_OFFSET은 CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET의 설정에 따라 랜더마이즈 코드가 설정되 있다. 페이지 테이블 생성과 매핑이 끝나면 MMU 비활성화 상태에서도 speculative load가 발생할 수 있기 떄문에 페이지 테이블 영역의 데이터 캐시를 무효화하고 백업한 lr 주소로 복귀한다.
2.1.5 CPU 초기화
__cpu_setup 프로시저는 기본적인 CPU 설정과 MMU를 동작시킬 때 필요한 CPU 설정을 한다. 시작할 때 TLB 엔트리를 무효화한다. CPACR_EL1에서 FPEN 비트를 설정해 FP/SIMD 명령이 EL1으로 트랩되지 않고 허용되도록 설정하고 MDSCR_EL1에서 TDCC 비트를 설정해서 EL0에서 DBG 레지스터에 접근하면 EL1으로 트랩시킨다.
FP는 부동소수점, SIMD는 다중 프로세스가 다중 데이터에 같은 연산을 수행하는 것을 지원하는 명령어이다.
reset_pmusernr_el0 매크로는 EL0에서 PMU 접근을 막는다. ID_AA64DFR0_EL1에서 PMUVer 비트를 추출해 있으면 EL0가 PMU를 읽으면 EL1으로 트랩시키고 없으면 매크로를 종료한다.
PMU는 Performance Monitoring Unit의 약자이며 CPU나 메모리 등을 모니터링하는 듯 하다.
MAIR_EL1을 설정하는데 각 8비트의 속성 세트를 8개까지 지정할 수 있다. 메모리 접근 특성을 지정하고 페이지 테이블 매핑 시 영역 특성에 따라 MAIR 속성 세트의 인덱스를 기록하는 간접 지정 방식을 사용한다. 크게 normal 메모리와 device 메모리로 구분된다.
??
SCTLR_EL1에 기록할 값을 준비하기 위해서 crval의 clear 비트들은 지우고 set 비트들은 설정한다. set 비트는 hw reserved 값과 sw setting 값을 결합해 생성한 값인데 MMU, 인스트럭션 캐시, 데이터 캐시 enable을 포함한다. TCR_XXXX…을 활용하여 MMU의 주소 변환 기능을 제어하는 TCR 레지스터의 설정값을 만든다. 이 설정 값을 tcr_set_idmap_t0sz 매크로에 전달해 T0SZ 필드를 갱신한다. 가상 주소의 크기가 48비트가 아니면 t0sz 를 앞서 계산한 ‘64 - 가상 주소 비트 수’로 대체한다.
ID_AA64MMFR0_EL1 레지스터의 PARange 값을 추출해 TCR_EL1 레지스터의 IPOS 비트 [34:32]에 해당하는 부분에 덮어쓴다.
- ID_AA64MMFR 레지스터는 메모리 영역에 대한 특정 값이 기록된 읽기 전용 레지스터이다.
- PARange는 지원하는 물리 주소 영역의 크기를 의미한다. 이 값을 TCR의 중간 변환으로 사용할 물리 주소 영역 크기를 지정할 값으로 삼는 것이다. 32비트에서 48비트 범위 안의 값 중 몇 가지 약속된 값을 사용할 수 있다.
이렇게 준비된 값을 tcr_el1 레지스터에 기록하고 ret으로 복귀하는데 복귀 위치는 분기 전에 지정한 __enable_mmu이다.
리눅스 커널 arm
- 페이지 크기에 따라 가상 주소 공간의 크기가 정해진다? -> p.59
- adrp란 어떤 instruction인가
댓글남기기