# Context switch: callee-saved register와 task state

v6.18.37 / arch/arm64/kernel/entry.S

실행 중이던 함수가 나중에 이어서 실행되려면 그 함수의 스택과 보존 레지스터가 돌아와야 합니다. arm64는 x19~x29·SP·LR, x86은 ABI와 전환 코드가 정한 저장 상태, RISC-V는 s0~s11·SP·RA 등을 중심으로 바꿉니다. 대표로 arm64 cpu_switch_to 전체를 읽으며 예외 진입의 전체 레지스터 저장과 구분합니다.

## cpu_switch_to

```c

SYM_FUNC_START(cpu_switch_to)
	save_and_disable_daif x11
	mov	x10, #THREAD_CPU_CONTEXT
	add	x8, x0, x10
	mov	x9, sp
	stp	x19, x20, [x8], #16		// store callee-saved registers
	stp	x21, x22, [x8], #16
	stp	x23, x24, [x8], #16
	stp	x25, x26, [x8], #16
	stp	x27, x28, [x8], #16
	stp	x29, x9, [x8], #16
	str	lr, [x8]
	add	x8, x1, x10
	ldp	x19, x20, [x8], #16		// restore callee-saved registers
	ldp	x21, x22, [x8], #16
	ldp	x23, x24, [x8], #16
	ldp	x25, x26, [x8], #16
	ldp	x27, x28, [x8], #16
	ldp	x29, x9, [x8], #16
	ldr	lr, [x8]
	mov	sp, x9
	msr	sp_el0, x1
	ptrauth_keys_install_kernel x1, x8, x9, x10
	scs_save x0
	scs_load_current
	restore_irq x11
	ret
SYM_FUNC_END(cpu_switch_to)

```

### 823행

```c

SYM_FUNC_START(cpu_switch_to)

```

SYM_FUNC_START는 함수 기호와 어셈블러 메타데이터를 선언합니다. 호출 시 x0는 이전 task, x1은 다음 task이며 x0는 보존해야 합니다.

### 824행

```c

	save_and_disable_daif x11

```

전환 도중 IRQ 등 예외 상태 변경으로 새·옛 task 상태가 섞이지 않게 DAIF를 저장하고 차단합니다. 원래 값은 x11에 둡니다.

### 825행

```c

	mov	x10, #THREAD_CPU_CONTEXT

```

task_struct 안의 cpu_context까지의 빌드 시 결정된 offset을 x10에 넣습니다. 구조체 크기를 숫자로 외우지 않습니다.

### 826행

```c

	add	x8, x0, x10

```

이전 task 주소 x0에 offset을 더해 저장할 context의 시작 주소 x8을 만듭니다.

### 827행

```c

	mov	x9, sp

```

현재 스택 포인터를 일반 레지스터 x9로 옮겨 뒤의 쌍 저장 명령에 사용할 수 있게 합니다.

### 828행

```c

	stp	x19, x20, [x8], #16		// store callee-saved registers

```

이전 task의 x19와 x20을 현재 x8 위치에 저장한 뒤 x8을 16바이트 증가시킵니다. post-index 증가는 저장 뒤입니다.

### 829행

```c

	stp	x21, x22, [x8], #16

```

다음 슬롯에 이전 task의 x21과 x22를 저장하고 x8을 다음 쌍으로 옮깁니다.

### 830행

```c

	stp	x23, x24, [x8], #16

```

x23과 x24의 보존값을 기록합니다. 같은 CPU의 계산 레지스터를 task의 메모리 사본으로 만드는 중입니다.

### 831행

```c

	stp	x25, x26, [x8], #16

```

x25와 x26의 보존값을 기록하고 저장 포인터를 16바이트 전진시킵니다.

### 832행

```c

	stp	x27, x28, [x8], #16

```

x27과 x28을 기록합니다. 이 레지스터들도 호출 규약상 호출받는 쪽이 유지할 값입니다.

### 833행

```c

	stp	x29, x9, [x8], #16

```

프레임 포인터 x29와 앞에서 복사한 SP 값 x9를 함께 저장합니다.

### 834행

```c

	str	lr, [x8]

```

이전 task가 나중에 이어 실행할 복귀 주소 LR을 마지막 슬롯에 저장합니다.

### 835행

```c

	add	x8, x1, x10

```

저장 대상에서 복원 대상으로 전환합니다. 이제 x8은 다음 task x1의 cpu_context를 가리킵니다.

### 836행

```c

	ldp	x19, x20, [x8], #16		// restore callee-saved registers

```

다음 task의 x19와 x20을 읽고 x8을 16바이트 이동합니다. 앞의 저장과 대응하는 슬롯입니다.

### 837행

```c

	ldp	x21, x22, [x8], #16

```

다음 task가 멈췄을 때의 x21과 x22를 복원합니다.

### 838행

```c

	ldp	x23, x24, [x8], #16

```

다음 task의 x23과 x24를 복원합니다.

### 839행

```c

	ldp	x25, x26, [x8], #16

```

다음 task의 x25와 x26을 복원합니다.

### 840행

```c

	ldp	x27, x28, [x8], #16

```

다음 task의 x27과 x28을 복원합니다.

### 841행

```c

	ldp	x29, x9, [x8], #16

```

다음 task의 FP를 x29로, 스택 위치를 임시 x9로 읽습니다. SP 변경은 다음 명령들에서 따로 수행합니다.

### 842행

```c

	ldr	lr, [x8]

```

다음 task가 다시 돌아갈 LR을 복원합니다. 이후 ret의 목적지가 바뀌는 핵심입니다.

### 843행

```c

	mov	sp, x9

```

실제 SP를 다음 task의 스택으로 바꿉니다. 이제 이후 스택 접근은 새 task의 작업 공간을 사용합니다.

### 844행

```c

	msr	sp_el0, x1

```

SP_EL0에 다음 task 포인터를 넣습니다. 이 커널 경로에서 SP_EL0를 current 식별용으로 사용하며 현재 커널 SP와는 다릅니다.

### 845행

```c

	ptrauth_keys_install_kernel x1, x8, x9, x10

```

다음 task의 커널 포인터 인증 키를 설치합니다. x8~x10은 이 매크로의 임시 레지스터입니다.

### 846행

```c

	scs_save x0

```

이전 task x0에 shadow call stack 위치를 보존합니다. 실제 동작 유무는 SCS 설정에 따라 달라집니다.

### 847행

```c

	scs_load_current

```

현재 task에 맞는 shadow call stack 포인터를 읽습니다. 일반 SP와 별도 보호용 스택입니다.

### 848행

```c

	restore_irq x11

```

x11에 남긴 원래 DAIF 상태를 복원합니다. 이름은 restore_irq이지만 매크로 본문은 DAIF 전체를 쓰며 전환 보호 구간을 마무리합니다.

### 849행

```c

	ret

```

새로 복원한 LR로 돌아갑니다. 다음 task가 이전에 멈췄던 실행 경로가 이어집니다.

### 850행

```c

SYM_FUNC_END(cpu_switch_to)

```

함수 기호의 크기와 범위를 닫는 메타데이터입니다. CPU가 실행하는 복귀 명령은 앞의 ret입니다.