# relocate_kernel.S — 말로 풀어 읽기
Linux v6.18.37 · arch/arm64/kernel/relocate_kernel.S

kexec는 현재 커널에서 새 커널로 넘어가는 기능입니다. 새 커널 페이지를 최종 위치에 복사하는 동안 현재 커널의 자료구조도 덮어써질 수 있습니다. 그래서 먼저 필요한 값을 레지스터에 보존하고, 페이지 목록을 해석해 복사를 마친 뒤 새 진입점으로 이동합니다.

## 처음 읽을 때
목록 항목은 주소만 담지 않고 하위 비트에 종류 표시도 붙입니다. 먼저 주소 부분과 플래그 부분을 분리해 읽으세요.

## 더 깊이 살펴볼 때
안전한 실행 페이지, 별도 선형 매핑, 복사 뒤 캐시 관리, MMU-off 전환을 하나의 의존 순서로 설명해 보세요. 어느 단계가 빠지면 다음 단계가 성립하지 않는지 생각해 봅니다.

## 복사 가능한 코드 영역
원본 1–38행

### 11행
```asm
#include <linux/kexec.h>
```
kexec 복사 목록의 항목 종류 비트 정의를 가져옵니다. 낮은 비트의 SOURCE·DESTINATION·INDIRECTION·DONE을 주소와 분리하여 목록을 해석합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 12행
```asm
#include <linux/linkage.h>
```
복사 루틴의 심볼 시작·끝을 선언할 linkage 매크로를 가져옵니다. C 코드가 이 코드 범위를 별도 안전한 페이지로 복사하는 데 필요한 심볼 정보를 제공합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 14행
```asm
#include <asm/assembler.h>
```
페이지 복사, 캐시 정리, TTBR 전환 같은 ARM64 공통 매크로를 가져옵니다. 기존 커널 메모리를 덮어쓰는 동안 외부 함수 호출에 의존하지 않고 작업하도록 펼쳐집니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 15행
```asm
#include <asm/kexec.h>
```
ARM64 kexec의 제어 코드 크기·아키텍처 번호·구조체 관련 정의를 포함합니다. 본문에서 쓰는 KIMAGE_* 필드 오프셋은 이 헤더 자체의 숫자 정의가 아니라 asm-offsets.c가 C 구조체 배치에서 생성하며, assembler.h를 통해 포함됩니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 16행
```asm
#include <asm/page.h>
```
PAGE_SIZE와 PAGE_MASK 등의 페이지 정의를 가져옵니다. 목록 엔트리에서 종류 비트를 지우고 한 페이지씩 복사하는 기준이 됩니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 17행
```asm
#include <asm/sysreg.h>
```
새 이미지로 넘기기 전에 사용할 SCTLR의 MMU-off 설정을 가져옵니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 18행
```asm
#include <asm/virt.h>
```
HVC_SOFT_RESTART 등 EL2로 재시작을 요청하는 데 필요한 번호를 가져옵니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 20행
```asm
.macro turn_off_mmu tmp1, tmp2
```
페이지 복사를 끝낸 뒤 새 이미지가 요구하는 MMU-off 상태로 바꾸는 인라인 매크로입니다. 이 정의에서 실제 사용되는 작업 레지스터는 tmp1이며 tmp2는 본문에서 사용하지 않습니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 21행
```asm
	mov_q   \tmp1, INIT_SCTLR_EL1_MMU_OFF
```
MMU·캐시를 끈 SCTLR 초기값을 tmp1에 준비합니다. 엔디언과 필요한 제어 비트를 포함하므로 단순히 0으로 쓰는 동작과는 다릅니다. mov_q는 64비트 상수를 필요한 MOVZ/MOVN·MOVK 조합으로 구성하는 매크로입니다. 해당 주소의 메모리에서 데이터를 가져오는 명령은 아닙니다.

### 22행
```asm
	pre_disable_mmu_workaround
```
해당 Qualcomm Falkor 우회 설정이 있는 빌드에서는 SCTLR의 MMU 비트를 내리기 직전에 ISB를 넣습니다.

### 23행
```asm
	msr	sctlr_el1, \tmp1
```
준비한 제어값을 SCTLR_EL1에 써서 주소 변환과 캐시 사용을 중단합니다. 복사한 코드는 이미 idmap에서 실행 중이므로 이 전환을 견딜 수 있습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 24행
```asm
	isb
```
SCTLR 변경을 뒤따르는 명령에 반영합니다. 이후에는 새 이미지의 물리 진입 주소로 넘어갈 준비가 된 상태입니다.

### 25행
```asm
.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 27행
```asm
.section    ".kexec_relocate.text", "ax"
```
kexec가 별도 안전한 페이지로 복사해 실행할 코드를 이 섹션에 모읍니다. 새 이미지로 기존 커널 메모리를 덮어쓰는 중에도 복사 루틴 자신은 살아 있어야 합니다. 이는 빌드 중 출력할 바이트의 섹션을 선택하는 지시문이며, 실행 중 PC나 SP를 옮기지 않습니다.

![단계별 개념 그림](../media/relocate_kernel-overview.png)
화살표는 항목 해석, 메모리 복사, 실행 이동을 구분합니다. 세부 TTBR 교체와 예외 경로는 원문에서 이어 읽습니다.

## kimage에서 필요한 값 읽기
원본 39–55행

### 39행
```asm
SYM_CODE_START(arm64_relocate_new_kernel)
```
x0의 kimage 구조체를 받아 새 커널의 페이지를 최종 위치로 옮기는 진입점입니다. C 코드가 미리 마련한 전환용 페이지 테이블과 복사 목록을 사용하며, 완료하면 새 이미지로 넘어갑니다. 이 매크로는 정렬된 코드 시작 심벌을 정의하고 다른 오브젝트에서도 참조할 수 있게 합니다.

### 45행
```asm
	ldr	x28, [x0, #KIMAGE_START]
```
kimage.start를 x28에 보관합니다. 복사가 끝난 뒤 분기할 주소이며, 적재 방식에 따라 커널 진입점 또는 진입 전 검증을 수행하는 purgatory일 수 있습니다. 실제로 x0 + (KIMAGE_START) 주소의 메모리 8바이트를 x28에 읽습니다. 이 주소 형식은 기준 레지스터 x0 자체를 갱신하지 않습니다.
kimage가 복사 중 덮어써질 수 있어 진입점부터 레지스터에 보존합니다.

### 46행
```asm
	ldr	x27, [x0, #KIMAGE_ARCH_EL2_VECTORS]
```
전환 중 사용할 EL2 벡터 주소를 x27에 읽습니다. 0이 아니면 마지막에 EL2의 HVC_SOFT_RESTART 경로를 이용해야 함을 뜻합니다. 실제로 x0 + (KIMAGE_ARCH_EL2_VECTORS) 주소의 메모리 8바이트를 x27에 읽습니다. 이 주소 형식은 기준 레지스터 x0 자체를 갱신하지 않습니다.

### 47행
```asm
	ldr	x26, [x0, #KIMAGE_ARCH_DTB_MEM]
```
새 커널에 넘길 DTB의 물리 주소를 x26에 보관합니다. 복사 루프가 끝나면 부팅 규약에 맞춰 x0에 전달합니다. 실제로 x0 + (KIMAGE_ARCH_DTB_MEM) 주소의 메모리 8바이트를 x26에 읽습니다. 이 주소 형식은 기준 레지스터 x0 자체를 갱신하지 않습니다.

### 50행
```asm
	ldr	x18, [x0, #KIMAGE_ARCH_ZERO_PAGE] /* x18 = zero page for BBM */
```
TTBR1 교체 때 임시로 사용할 빈 페이지의 물리 주소를 x18에 읽습니다. 기존·새 페이지 테이블을 섞어 참조하지 않도록 중간에 빈 테이블을 거칩니다. 실제로 x0 + (KIMAGE_ARCH_ZERO_PAGE) 주소의 메모리 8바이트를 x18에 읽습니다. 이 주소 형식은 기준 레지스터 x0 자체를 갱신하지 않습니다.

### 51행
```asm
	ldr	x17, [x0, #KIMAGE_ARCH_TTBR1]	/* x17 = linear map copy */
```
복사 작업 동안 사용할 별도 linear map 테이블의 물리 주소를 x17에 읽습니다. 기존 커널의 원래 페이지 테이블은 곧 덮어써질 수 있습니다. 실제로 x0 + (KIMAGE_ARCH_TTBR1) 주소의 메모리 8바이트를 x17에 읽습니다. 이 주소 형식은 기준 레지스터 x0 자체를 갱신하지 않습니다.

### 52행
```asm
	ldr	x16, [x0, #KIMAGE_HEAD]		/* x16 = kimage_head */
```
물리 주소와 항목 종류 비트가 결합된 복사 목록의 첫 엔트리를 x16에 읽습니다. 이후 SOURCE, INDIRECTION, DESTINATION, DONE 비트를 구분하여 해석합니다. 실제로 x0 + (KIMAGE_HEAD) 주소의 메모리 8바이트를 x16에 읽습니다. 이 주소 형식은 기준 레지스터 x0 자체를 갱신하지 않습니다.

### 53행
```asm
	ldr	x22, [x0, #KIMAGE_ARCH_PHYS_OFFSET]	/* x22 phys_offset */
```
물리 주소와 현재 linear map 가상 주소 사이의 차이를 x22에 읽습니다. C 코드가 virt_to_phys(kimage) - (long)kimage로 준비한 값이며 페이지 주소에서 이를 빼면 복사에 사용할 가상 주소가 됩니다. 실제로 x0 + (KIMAGE_ARCH_PHYS_OFFSET) 주소의 메모리 8바이트를 x22에 읽습니다. 이 주소 형식은 기준 레지스터 x0 자체를 갱신하지 않습니다.

### 54행
```asm
	raw_dcache_line_size x15, x1		/* x15 = dcache line size */
```
현재 CPU의 CTR_EL0에서 데이터 캐시 라인 크기를 x15에 구합니다. 나중에 복사한 페이지를 이 간격으로 clean·invalidate하며 x1은 이 계산에만 쓰는 임시 값입니다. CTR_EL0.DminLine은 바이트 수 자체가 아닙니다. 4를 이 필드 값만큼 왼쪽으로 이동하여 바이트 수를 구합니다.

### 55행
```asm
	break_before_make_ttbr_switch	x18, x17, x1, x2 /* set linear map */
```
빈 페이지 x18을 거쳐 전환용 테이블 x17을 TTBR1에 설치합니다. 기존 TLB 항목을 무효화하여 원래 커널의 페이지 테이블이 덮어써져도 복사에 필요한 주소 변환을 유지합니다.

## 플래그로 구분하는 페이지 목록
원본 56–78행

### 56행
```asm
.Lloop:
```
x16의 복사 목록 엔트리에서 페이지 주소를 꺼내 해석을 시작하는 반복 지점입니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.

### 57행
```asm
	and	x12, x16, PAGE_MASK		/* x12 = addr */
```
목록 엔트리의 하위 종류 비트를 PAGE_MASK로 제거하여 페이지 물리 주소만 x12에 남깁니다. AND는 두 피연산자의 같은 위치 비트가 모두 1인 곳만 결과에 남깁니다.

### 58행
```asm
	sub	x12, x12, x22			/* Convert x12 to virt */
```
페이지 물리 주소에서 미리 계산한 차이 x22를 빼 linear map의 가상 주소로 바꿉니다. 아직 MMU가 켜져 있으므로 페이지 복사와 목록 읽기는 이 가상 주소를 사용합니다. 이 SUB 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
x22는 이 작업용 선형 매핑의 물리/가상 차이입니다. 모든 VA→PA 변환에 쓰는 보편적인 상수는 아닙니다.

### 60행
```asm
.Ltest_source:
```
현재 엔트리가 복사 원본 페이지인지 검사할 위치입니다. 원본이 아니면 다른 항목 종류를 순서대로 검사합니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.

### 61행
```asm
	tbz	x16, IND_SOURCE_BIT, .Ltest_indirection
```
SOURCE 비트가 없으면 이 항목을 원본 데이터 페이지로 해석하지 않고 다음 종류 검사로 넘어갑니다. 비트가 있으면 x12의 내용을 현재 목적지 x13으로 복사합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 64행
```asm
	mov	x19, x13
```
목적지의 시작 주소를 x19에 남깁니다. copy_page가 x13을 페이지 끝까지 증가시키므로 복사 후 캐시를 정리할 시작 주소가 따로 필요합니다. MOV는 x19의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 65행
```asm
	copy_page x13, x12, x1, x2, x3, x4, x5, x6, x7, x8
```
x12의 원본 페이지를 현재 목적지 x13으로 복사합니다. x1~x8은 64바이트씩 운반하는 작업 레지스터이며 x12·x13은 복사 후 다음 페이지 위치까지 증가합니다.
x13과 x12가 복사 뒤 증가합니다. x19에는 캐시 처리용 원래 목적지가 남아 있습니다.

### 66행
```asm
	add	x1, x19, #PAGE_SIZE
```
보관한 목적지 시작 x19에 PAGE_SIZE를 더해 캐시 처리 범위의 끝 주소를 만듭니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.

### 67행
```asm
	dcache_by_myline_op civac, sy, x19, x1, x15, x20
```
복사한 [x19, x1) 범위를 x15의 캐시 라인 크기씩 돌며 DC CIVAC를 수행합니다. 새 내용이 PoC까지 반영되도록 clean하고 캐시 라인을 invalidate한 뒤 DSB SY로 완료를 기다립니다. 시작 주소를 먼저 캐시 라인 경계로 내림 정렬하므로 범위의 첫 바이트가 속한 라인부터 처리합니다.

### 68행
```asm
	b	.Lnext
```
한 원본 페이지 처리를 마쳤으므로 다음 목록 엔트리를 읽는 공통 지점으로 넘어갑니다. 이 분기는 LR에 새 복귀 주소를 기록하지 않습니다.

### 69행
```asm
.Ltest_indirection:
```
현재 엔트리가 다른 목록 페이지를 가리키는지 검사할 위치입니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.

### 70행
```asm
	tbz	x16, IND_INDIRECTION_BIT, .Ltest_destination
```
INDIRECTION 비트가 없으면 목적지 지정 항목인지 검사합니다. 이 비트가 있으면 x12는 이미지 데이터가 아니라 다음 엔트리들이 담긴 목록 페이지 주소입니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 71행
```asm
	mov	x14, x12			/* ptr = addr */
```
새 목록 페이지 주소를 목록 커서 x14에 넣습니다. 이후 x14에서 8바이트 엔트리를 차례로 읽어 갑니다. MOV는 x14의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 72행
```asm
	b	.Lnext
```
목록 페이지를 바꿨으므로 공통 읽기 지점에서 그 페이지의 첫 엔트리를 가져옵니다. 이 분기는 LR에 새 복귀 주소를 기록하지 않습니다.

### 73행
```asm
.Ltest_destination:
```
현재 엔트리가 이후 복사의 목적지 주소를 지정하는지 검사할 위치입니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.

### 74행
```asm
	tbz	x16, IND_DESTINATION_BIT, .Lnext
```
DESTINATION 비트도 없으면 목적지를 바꾸지 않고 다음 엔트리로 넘어갑니다. 있으면 이어서 나올 SOURCE 페이지들의 복사 목적지를 지정합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 75행
```asm
	mov	x13, x12			/* dest = addr */
```
엔트리가 지정한 목적지 가상 주소를 x13에 저장합니다. 뒤따르는 원본 페이지들을 복사할 때 이 주소가 한 페이지씩 전진합니다. MOV는 x13의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 76행
```asm
.Lnext:
```
각 항목 처리가 끝난 뒤 다음 8바이트 엔트리를 읽는 공통 지점입니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.

### 77행
```asm
	ldr	x16, [x14], #8			/* entry = *ptr++ */
```
목록 커서 x14에서 64비트 엔트리 하나를 x16에 읽고 커서를 8바이트 전진시킵니다. 아직 데이터 페이지를 복사하는 명령은 아닙니다. 대괄호 밖의 오프셋 8은 후위 갱신입니다. 기존 x14 주소로 접근한 뒤 x14에 이 값을 더합니다.
다음 목록 항목을 읽고 목록 포인터 x14를 8 증가시킵니다.

### 78행
```asm
	tbz	x16, IND_DONE_BIT, .Lloop	/* while (!(entry & DONE)) */
```
DONE 비트가 없으면 새로 읽은 엔트리를 처리합니다. DONE이 있으면 복사 목록의 끝이므로 캐시·MMU 전환 마무리로 진행합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

## 복사 완료와 새 진입점
원본 79–100행

### 80행
```asm
	dsb	nsh
```
앞선 메모리·캐시 작업의 로컬 완료를 기다립니다. 새 코드를 실행하기 전에 복사 결과가 준비되어 있어야 합니다.

### 81행
```asm
	ic	iallu
```
현재 CPU의 명령 캐시를 무효화하여 새로 복사한 명령 대신 낡은 명령을 가져오는 일을 막습니다. 무효화 요청과 이후 명령 실행에 대한 동기화는 구분되며, 뒤의 DSB·ISB가 그 순서를 마무리합니다.

### 82행
```asm
	dsb	nsh
```
명령 캐시 무효화가 완료될 때까지 기다립니다.

### 83행
```asm
	isb
```
파이프라인을 동기화하여 이후 명령 가져오기가 갱신된 캐시 상태를 따르게 합니다.

### 84행
```asm
	turn_off_mmu x12, x13
```
복사와 캐시 정리가 끝났으므로 MMU와 캐시를 끕니다. x12는 SCTLR 값을 만드는 데 사용되며 x13 인자는 이 매크로 본문에서는 사용하지 않습니다. 매크로는 SCTLR_EL1에 MMU-off 제어값을 쓰고 ISB로 이후 명령을 동기화합니다.
이후에도 같은 명령을 읽을 수 있도록 idmap에서 실행하고 있습니다.

### 87행
```asm
	cbz	x27, .Lel1
```
EL2 벡터 주소가 0이면 현재 EL에서 새 이미지로 직접 분기합니다. 주소가 있으면 전환용 EL2 벡터에 HVC를 보내 EL2에서 시작하게 합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 88행
```asm
	mov	x1, x28				/* kernel entry point */
```
HVC_SOFT_RESTART의 진입 주소 인자로 kimage.start를 x1에 넣습니다. MOV는 x1의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 89행
```asm
	mov	x2, x26				/* dtb address */
```
새 이미지의 첫 인자인 DTB 물리 주소를 x2에 넣습니다. EL2 처리기가 이를 x0로 옮겨 새 이미지에 전달합니다. MOV는 x2의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 90행
```asm
	mov	x3, xzr
```
새 이미지의 두 번째 인자를 0으로 준비합니다. EL2 처리기에서 x1로 옮겨집니다. MOV는 x3의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 91행
```asm
	mov	x4, xzr
```
새 이미지의 세 번째 인자를 0으로 준비합니다. EL2 처리기에서 x2로 옮겨집니다. MOV는 x4의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 92행
```asm
	mov     x0, #HVC_SOFT_RESTART
```
x0에 HVC_SOFT_RESTART 서비스 번호를 넣어 기존 값을 덮어씁니다. 이 MOV는 x1~x4와 메모리를 바꾸지 않습니다. 다음 hvc #0이 실행되면 EL2 처리 코드가 앞서 준비한 진입 주소와 인자를 사용합니다. HVC 명령의 즉시값 #0과 달리 x0의 HVC_SOFT_RESTART 값이 요청할 서비스 종류를 정합니다.

### 93행
```asm
	hvc	#0				/* Jumps from el2 */
```
복사 중에도 유지되는 EL2 벡터의 재시작 처리기로 넘어갑니다. 처리기는 준비한 인자를 재배치하고 새 이미지 주소로 직접 분기합니다. 일반 함수 호출처럼 BL로 이동하는 대신 동기 예외를 통해 요청을 전달합니다.
hyp-stub는 x2=DTB를 새 커널의 x0로 옮깁니다.

### 94행
```asm
.Lel1:
```
EL2 경유가 필요 없는 경우 부팅 인자를 직접 배치하고 새 이미지로 분기하는 지점입니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.

### 95행
```asm
	mov	x0, x26				/* dtb address */
```
직접 분기 경로에서는 DTB의 물리 주소를 부팅 인자 x0에 넣습니다. MOV는 x0의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 96행
```asm
	mov	x1, xzr
```
AArch64 커널 진입 규약에 따라 예약된 부팅 인자 x1을 0으로 만듭니다. MOV는 x1의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 97행
```asm
	mov	x2, xzr
```
예약된 부팅 인자 x2를 0으로 만듭니다. MOV는 x2의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 98행
```asm
	mov	x3, xzr
```
예약된 부팅 인자 x3을 0으로 만듭니다. MOV는 x3의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 99행
```asm
	br	x28				/* Jumps from el1 */
```
복사를 마친 kimage.start 주소로 넘어갑니다. 적재 방식에 따라 커널 또는 purgatory가 실행되며, 이전 커널의 복사 루틴으로 돌아오지 않습니다. 이 분기는 LR에 새 복귀 주소를 기록하지 않습니다. 레지스터가 담은 주소로 실행 위치를 바꾸며, 그 주소에서 값을 읽어 다른 레지스터에 복사하는 load 명령은 아닙니다.
kimage.start가 직접 커널을 가리키면 x0의 DTB 주소를 커널 진입점에 전달합니다. 일반 kexec 경로에서는 purgatory가 먼저 실행될 수도 있으며, kexec_file 경로는 커널을 직접 시작합니다.

### 100행
```asm
SYM_CODE_END(arm64_relocate_new_kernel)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## 설명한 뒤 함께 생각해 볼 질문

### kimage에서 값을 필요할 때마다 읽으면 안 되나요?
복사 도중 kimage 자체가 덮어써질 수 있습니다. 이후에도 필요한 값은 복사 전에 확보합니다.

### SOURCE와 DESTINATION 항목은 같은 뜻인가요?
SOURCE는 현재 목적지로 복사할 페이지이고, DESTINATION은 이후 복사가 쓸 목적지 포인터를 설정합니다.

### 새 커널에 DTB 주소는 어떻게 전달되나요?
직접 경로에서는 x0에 넣습니다. EL2 경유 경로는 hyp-stub가 전달 인자를 재배치해 새 진입점의 x0로 보냅니다.
