Linux v6.18.37 · 개념과 코드 읽기

RISC-V 64 head.S의 hart·DTB 수신과 SATP 전환

이 코드는 어떤 문제를 푸나요?

MMU를 켜면 같은 PC 숫자가 다른 주소로 해석될 수 있습니다. RISC-V의 relocate_enable_mmu는 복귀 주소와 trap 벡터를 먼저 맞춘 뒤 trampoline 표와 호출자가 넘긴 커널 표를 순서대로 적재합니다. 초기 부트 hart는 임시 early_pg_dir를, secondary hart는 swapper_pg_dir를 넘깁니다. v6.18.37의 이 루틴 전체를 읽어 주소 전환 도중에도 다음 명령을 가져올 수 있어야 한다는 문제를 설명합니다.

읽을 범위: v6.18.37 · arch/riscv/kernel/head.S · relocate_enable_mmu 73–125행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

SATP

주소 변환 모드·ASID·최상위 표의 물리 페이지 번호를 담는 CSR입니다.

RA 보정

나중에 ret할 주소도 가상 주소로 맞춰야 새 주소 환경에서 올바른 위치로 돌아갑니다.

trampoline 표

주소 전환을 안전하게 넘기는 최소 임시 매핑입니다. 뒤이어 설치할 호출자 표와 다르며, 초기 부트의 early_pg_dir도 아직 최종 매핑은 아닙니다.

처음 읽을 때

전환 전 PC, 전환 후 PC, trap 목적지, 복귀 주소를 네 칸으로 적어 보십시오.

더 깊이 살펴볼 때

VA와 PA가 같은 경우의 순차 진행과 다른 경우의 trap 이동, 두 SFENCE.VMA의 목적을 구분하십시오.

그림으로 보는 변화

두 번째 SATP는 호출자가 넘긴 커널 표를 설치합니다

CONFIG_MMU의 두 호출 경로는 같은 relocate_enable_mmu()에 서로 다른 표의 주소를 a0로 전달합니다.

초기 부트 hartsetup_vm() 후 a0 = early_pg_dir · 초기 임시 커널 매핑. head.S:321–325
secondary harta0 = swapper_pg_dir · 준비된 커널 표. head.S:164–170
공통 루틴: trampoline_pg_dir SATP → VA≠PA면 보정한 TVEC로 trap, VA=PA면 순차 진행 → 1: 합류 → a2에 준비한 호출자 표 SATP → sfence.vma → 보정된 ra로 ret

초기 부트에서 이 두 번째 SATP가 곧 최종 매핑이라는 뜻은 아닙니다. setup_vm_final()이 나중에 swapper_pg_dir에 최종 매핑을 만듭니다.

호출자별 두 번째 SATP의 표 · Linux v6.18.37
경로a0로 전달한 표이 시점의 의미
초기 부트 hartearly_pg_dir시스템 메모리를 발견하기 위한 임시 커널 매핑
secondary hartswapper_pg_dir이미 준비된 커널 표로 진입
RISC-V 64 head.S의 hart·DTB 수신과 SATP 전환의 단계별 개념 그림
기존 3단계 그림의 “정식 표”는 trampoline과 구별되는 호출자 표의 축약 표현입니다. 초기 부트에서는 early_pg_dir이고 최종 매핑은 아직 아닙니다. 위 호출 경로 그림과 함께 읽으세요.
1단계 설명

1단계 고정

GIF 원본 열기

1. 주소 보정

커널 가상 시작점과 현재 물리 시작점의 차이로 RA와 trap 목적지를 맞춥니다.

화살표는 주소값 계산이며 메모리 복사가 아닙니다.

2. 임시 변환 활성화

PTE 쓰기를 동기화한 뒤 trampoline SATP를 적재합니다.

VA와 PA 관계에 따라 trap으로 다음 가상 위치로 이동할 수 있습니다.

3. 호출자 표로 교체

a2에 준비한 호출자 표의 SATP와 fence를 적용한 뒤 보정한 RA로 돌아갑니다. 초기 부트의 표는 early_pg_dir, secondary의 표는 swapper_pg_dir입니다.

마지막 화살표는 제어 흐름의 복귀입니다.

relocate_enable_mmu를 한 줄씩 읽기

줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.

	.global relocate_enable_mmu
relocate_enable_mmu:
	/* Relocate return address */
	la a1, kernel_map
	XIP_FIXUP_OFFSET a1
	REG_L a1, KERNEL_MAP_VIRT_ADDR(a1)
	la a2, _start
	sub a1, a1, a2
	add ra, ra, a1

	/* Point stvec to virtual address of intruction after satp write */
	la a2, 1f
	add a2, a2, a1
	csrw CSR_TVEC, a2

	/* Compute satp for kernel page tables, but don't load it yet */
	srl a2, a0, PAGE_SHIFT
	la a1, satp_mode
	XIP_FIXUP_OFFSET a1
	REG_L a1, 0(a1)
	or a2, a2, a1

	/*
	 * Load trampoline page directory, which will cause us to trap to
	 * stvec if VA != PA, or simply fall through if VA == PA.  We need a
	 * full fence here because setup_vm() just wrote these PTEs and we need
	 * to ensure the new translations are in use.
	 */
	la a0, trampoline_pg_dir
	XIP_FIXUP_OFFSET a0
	srl a0, a0, PAGE_SHIFT
	or a0, a0, a1
	sfence.vma
	csrw CSR_SATP, a0
.align 2
1:
	/* Set trap vector to spin forever to help debug */
	la a0, .Lsecondary_park
	csrw CSR_TVEC, a0

	/* Reload the global pointer */
	load_global_pointer

	/*
	 * Switch to kernel page tables.  A full fence is necessary in order to
	 * avoid using the trampoline translations, which are only correct for
	 * the first superpage.  Fetching the fence is guaranteed to work
	 * because that first superpage is translated the same way.
	 */
	csrw CSR_SATP, a2
	sfence.vma

	ret
	.global relocate_enable_mmu

다른 파일에서 참조할 수 있도록 relocate_enable_mmu 기호를 공개합니다.

relocate_enable_mmu:

주소 전환 루틴의 시작 위치입니다. a0에는 커널 페이지 표 루트의 물리 주소가 전달됩니다.

	la a1, kernel_map

kernel_map 자료의 현재 접근 가능한 주소를 a1에 얻습니다.

	XIP_FIXUP_OFFSET a1

XIP 빌드에서 필요한 주소 보정을 매크로에 맡깁니다. 일반 RAM 실행과 주소 배치가 다를 수 있습니다.

	REG_L a1, KERNEL_MAP_VIRT_ADDR(a1)

kernel_map의 커널 가상 시작 주소를 읽습니다. REG_L은 XLEN에 맞는 load를 선택합니다.

	la a2, _start

현재 _start 기호 주소를 a2에 가져옵니다.

	sub a1, a1, a2

가상 시작과 현재 시작 사이의 차이를 a1에 계산합니다. 이것이 코드 주소를 보정할 값입니다.

	add ra, ra, a1

RA에 주소 차이를 더해 MMU 전환 후 돌아갈 위치도 가상 주소 기준으로 맞춥니다.

	la a2, 1f

뒤에 나오는 1번 지역 라벨의 현재 주소를 구합니다. 1f의 f는 앞으로 나오는 라벨을 뜻합니다.

	add a2, a2, a1

그 라벨 주소에도 같은 차이를 더해 전환 후의 가상 목적지를 만듭니다.

	csrw CSR_TVEC, a2

CSR_TVEC에 그 목적지를 설치합니다. 전환 직후 VA와 PA가 다르면 trap을 통해 이 지점에 도달할 수 있게 합니다.

	srl a2, a0, PAGE_SHIFT

커널 루트의 물리 주소를 PAGE_SHIFT만큼 내려 SATP가 요구하는 물리 페이지 번호로 만듭니다.

	la a1, satp_mode

사용할 SATP 변환 모드가 들어 있는 변수의 주소를 얻습니다.

	XIP_FIXUP_OFFSET a1

XIP 구성의 주소 보정을 적용합니다.

	REG_L a1, 0(a1)

satp_mode의 실제 값을 읽습니다. 변수 주소와 그 안의 모드값을 구분합니다.

	or a2, a2, a1

호출자가 넘긴 커널 표의 PPN과 모드 비트를 합친 SATP 값을 a2에 보관합니다. 이 표는 두 번째 SATP 쓰기에서 설치합니다.

	la a0, trampoline_pg_dir

우선 설치할 임시 trampoline 페이지 표 주소를 얻습니다.

	XIP_FIXUP_OFFSET a0

임시 표 주소에도 XIP 보정을 적용합니다.

	srl a0, a0, PAGE_SHIFT

임시 표 주소를 SATP용 PPN으로 변환합니다.

	or a0, a0, a1

임시 PPN과 앞에서 읽은 모드 비트를 합칩니다.

	sfence.vma

setup_vm이 기록한 PTE와 주소 변환 접근의 순서를 맞추는 SFENCE.VMA를 수행합니다.

	csrw CSR_SATP, a0

임시 SATP를 설치합니다. 현재 주소가 같은 방식으로 번역되지 않으면 앞서 준비한 TVEC 경로로 이어집니다.

.align 2

이어질 라벨을 어셈블러의 2^2 바이트 경계에 정렬합니다. 실행 중 주소를 반올림하는 명령은 아닙니다.

1:

전환 후 도달할 1번 지역 라벨입니다. 위의 1f 참조와 연결됩니다.

	la a0, .Lsecondary_park

후속 예외에서 대기할 디버깅용 park 루프의 주소를 구합니다.

	csrw CSR_TVEC, a0

TVEC를 park 루프로 다시 바꿉니다. 초기 전환용 단발 목적지를 계속 사용하지 않기 위해서입니다.

	load_global_pointer

가상 주소 환경에 맞는 global pointer를 다시 준비합니다. 구체적인 PC 상대 로딩은 매크로 정의에 있습니다.

	csrw CSR_SATP, a2

a2에 준비해 둔 호출자 표의 SATP를 설치합니다. 초기 부트에서는 early_pg_dir이며 최종 매핑은 나중에 따로 구성합니다.

	sfence.vma

임시 표에서 얻은 번역이 이후 접근에 잘못 남지 않도록 SFENCE.VMA를 수행합니다.

	ret

처음에 가상 주소로 보정한 RA로 돌아갑니다. MMU 전환 함수의 호출자 실행이 새 주소 환경에서 이어집니다.

함께 생각해 볼 질문

SATP를 쓰면 RA 값도 자동 변환됩니까?

아닙니다. 코드에서 주소 차이를 더해 직접 보정합니다.

trampoline 표가 영구 커널 표입니까?

아닙니다. 전환을 통과한 뒤 호출자가 넘긴 표로 바뀝니다. 초기 부트의 early_pg_dir는 임시 매핑이고 최종 매핑은 뒤의 setup_vm_final()에서 구성합니다.

SFENCE.VMA는 일반 데이터 캐시 비우기입니까?

아닙니다. 주소 변환과 페이지 표 접근의 동기화에 관련된 명령입니다.

출처와 읽은 범위

Linux stable v6.18.37 · arch/riscv/kernel/head.S

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑