이 코드는 어떤 문제를 푸나요?
실행 중이던 함수가 나중에 이어서 실행되려면 그 함수의 스택과 보존 레지스터가 돌아와야 합니다. arm64는 x19~x29·SP·LR, x86은 ABI와 전환 코드가 정한 저장 상태, RISC-V는 s0~s11·SP·RA 등을 중심으로 바꿉니다. 대표로 arm64 cpu_switch_to 전체를 읽으며 예외 진입의 전체 레지스터 저장과 구분합니다.
읽을 범위: v6.18.37 · arch/arm64/kernel/entry.S · cpu_switch_to 823–850행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
호출 규약
일반 함수 호출 경계에서는 호출받는 쪽이 보존할 레지스터가 정해져 있습니다. 따라서 스케줄 전환에 모든 범용 레지스터를 이 함수에서 저장할 필요는 없습니다.
스택 포인터
SP가 바뀌면 이후 지역 변수와 복귀 기록을 읽는 작업 공간도 다음 task의 것으로 바뀝니다.
복귀 주소
새로 읽은 LR로 ret하면 이전 task가 호출했던 지점이 아니라 다음 task가 멈췄던 호출 경로가 이어집니다.
처음 읽을 때
CPU 하나와 task 두 개의 저장 영역을 그려 실제 레지스터가 어느 영역으로 이동하는지 따라가시면 됩니다.
더 깊이 살펴볼 때
주소 공간·TLS·FP 상태 전환은 이 짧은 함수 바깥에서도 수행된다는 점과 IRQ·PAC·SCS 전환 순서를 연결해 보십시오.
그림으로 보는 변화

1. 이전 task 저장
x0가 가리키는 cpu_context에 보존 레지스터와 SP·LR을 기록합니다.
CPU에서 메모리로 향하는 화살표는 값의 저장입니다.
2. 다음 task 복원
x1가 가리키는 cpu_context에서 새 레지스터와 SP·LR을 읽습니다.
메모리에서 CPU로 향하는 화살표는 값의 복원입니다.
3. 다음 실행 재개
현재 task 관련 상태와 보호 장치를 바꾸고 새 LR로 돌아갑니다.
마지막 화살표는 데이터 이동이 아니라 제어 흐름의 이동입니다.
cpu_switch_to를 한 줄씩 읽기
줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
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)SYM_FUNC_START(cpu_switch_to)SYM_FUNC_START는 함수 기호와 어셈블러 메타데이터를 선언합니다. 호출 시 x0는 이전 task, x1은 다음 task이며 x0는 보존해야 합니다.
save_and_disable_daif x11전환 도중 IRQ 등 예외 상태 변경으로 새·옛 task 상태가 섞이지 않게 DAIF를 저장하고 차단합니다. 원래 값은 x11에 둡니다.
mov x10, #THREAD_CPU_CONTEXTtask_struct 안의 cpu_context까지의 빌드 시 결정된 offset을 x10에 넣습니다. 구조체 크기를 숫자로 외우지 않습니다.
add x8, x0, x10이전 task 주소 x0에 offset을 더해 저장할 context의 시작 주소 x8을 만듭니다.
mov x9, sp현재 스택 포인터를 일반 레지스터 x9로 옮겨 뒤의 쌍 저장 명령에 사용할 수 있게 합니다.
stp x19, x20, [x8], #16 // store callee-saved registers이전 task의 x19와 x20을 현재 x8 위치에 저장한 뒤 x8을 16바이트 증가시킵니다. post-index 증가는 저장 뒤입니다.
stp x21, x22, [x8], #16다음 슬롯에 이전 task의 x21과 x22를 저장하고 x8을 다음 쌍으로 옮깁니다.
stp x23, x24, [x8], #16x23과 x24의 보존값을 기록합니다. 같은 CPU의 계산 레지스터를 task의 메모리 사본으로 만드는 중입니다.
stp x25, x26, [x8], #16x25와 x26의 보존값을 기록하고 저장 포인터를 16바이트 전진시킵니다.
stp x27, x28, [x8], #16x27과 x28을 기록합니다. 이 레지스터들도 호출 규약상 호출받는 쪽이 유지할 값입니다.
stp x29, x9, [x8], #16프레임 포인터 x29와 앞에서 복사한 SP 값 x9를 함께 저장합니다.
str lr, [x8]이전 task가 나중에 이어 실행할 복귀 주소 LR을 마지막 슬롯에 저장합니다.
add x8, x1, x10저장 대상에서 복원 대상으로 전환합니다. 이제 x8은 다음 task x1의 cpu_context를 가리킵니다.
ldp x19, x20, [x8], #16 // restore callee-saved registers다음 task의 x19와 x20을 읽고 x8을 16바이트 이동합니다. 앞의 저장과 대응하는 슬롯입니다.
ldp x21, x22, [x8], #16다음 task가 멈췄을 때의 x21과 x22를 복원합니다.
ldp x23, x24, [x8], #16다음 task의 x23과 x24를 복원합니다.
ldp x25, x26, [x8], #16다음 task의 x25와 x26을 복원합니다.
ldp x27, x28, [x8], #16다음 task의 x27과 x28을 복원합니다.
ldp x29, x9, [x8], #16다음 task의 FP를 x29로, 스택 위치를 임시 x9로 읽습니다. SP 변경은 다음 명령들에서 따로 수행합니다.
ldr lr, [x8]다음 task가 다시 돌아갈 LR을 복원합니다. 이후 ret의 목적지가 바뀌는 핵심입니다.
mov sp, x9실제 SP를 다음 task의 스택으로 바꿉니다. 이제 이후 스택 접근은 새 task의 작업 공간을 사용합니다.
msr sp_el0, x1SP_EL0에 다음 task 포인터를 넣습니다. 이 커널 경로에서 SP_EL0를 current 식별용으로 사용하며 현재 커널 SP와는 다릅니다.
ptrauth_keys_install_kernel x1, x8, x9, x10다음 task의 커널 포인터 인증 키를 설치합니다. x8~x10은 이 매크로의 임시 레지스터입니다.
scs_save x0이전 task x0에 shadow call stack 위치를 보존합니다. 실제 동작 유무는 SCS 설정에 따라 달라집니다.
scs_load_current현재 task에 맞는 shadow call stack 포인터를 읽습니다. 일반 SP와 별도 보호용 스택입니다.
restore_irq x11x11에 남긴 원래 DAIF 상태를 복원합니다. 이름은 restore_irq이지만 매크로 본문은 DAIF 전체를 쓰며 전환 보호 구간을 마무리합니다.
ret새로 복원한 LR로 돌아갑니다. 다음 task가 이전에 멈췄던 실행 경로가 이어집니다.
SYM_FUNC_END(cpu_switch_to)함수 기호의 크기와 범위를 닫는 메타데이터입니다. CPU가 실행하는 복귀 명령은 앞의 ret입니다.
함께 생각해 볼 질문
왜 x0~x18은 모두 저장하지 않습니까?
일반 호출 경계에서 보존할 책임이 다른 레지스터이기 때문입니다. 임의 시점의 예외 진입과 조건이 다릅니다.
이 함수가 프로세스의 모든 상태를 바꿉니까?
아닙니다. 주소 공간, TLS, FP·벡터 등은 주변 전환 경로와 함께 읽어야 합니다.
ret 뒤에는 누가 실행됩니까?
복원한 다음 task의 LR이 가리키는 경로가 이어집니다.
출처와 읽은 범위
Linux stable v6.18.37 · arch/arm64/kernel/entry.S
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
