> [!abstract] Introduction > 컨텍스트 스위치*context switch*는 운영체제가 멀티태스킹을 실현하는 핵심 원리입니다. CPU의 제어권을 현재 실행 중인 태스크에서 다른 태스크로 넘기는 이 과정은, 소프트웨어(커널)와 하드웨어(CPU 아키텍처)가 긴밀하게 협력해야 하는 대표적인 영역입니다. 이 글에서는 컨텍스트 스위치가 무엇이며 언제, 왜 발생하는지부터 리눅스 커널에서의 실제 구현, 그리고 x86-64와 AArch64 아키텍처에서의 하드웨어 동작까지를 종합적으로 살펴봅니다. ## 컨텍스트란 무엇인가 컨텍스트*context*란 CPU가 특정 태스크의 실행을 재개하기 위해 필요한 모든 상태 정보의 총합이다. 이 정보는 [Process Control Block](Process%20Control%20Block.md)(이하 PCB)에 보관되며, 다음과 같은 요소들로 구성된다. | 범주 | 내용 | |------|------| | **범용 레지스터** | x86-64: `RAX`~`R15` (16개), AArch64: `X0`~`X30` (31개) | | **프로그램 상태** | 플래그 레지스터(`RFLAGS`), 조건 코드 | | **명령어 포인터** | `RIP` (x86-64), `PC` / `ELR_EL1` (AArch64) | | **부동소수점/SIMD**[^simd] | FPU, `SSE`/`AVX`/`AVX-512` (x86-64), `NEON`/`SVE`[^sve]/`SME` (AArch64) | | **메모리 관리** | 페이지 테이블 베이스 — `CR3` (x86-64), `TTBR0_EL1` (AArch64) | | **커널 스택** | 태스크별 커널 스택 포인터 | | **스케줄링 메타데이터** | 상태(`RUNNING`, `READY`, `BLOCKED`), 우선순위, 소비한 CPU 시간 | 컨텍스트의 크기는 아키텍처와 활용하는 확장 기능에 따라 달라진다. x86-64에서 `AVX-512`를 사용하면 `XSAVE`[^xsave] 영역만 약 2.7KB에 달하고, AArch64에서 `SVE`를 최대 벡터 길이로 사용하면 레지스터 파일만 약 8KB까지 커진다. ## 컨텍스트 스위치는 언제 발생하는가 컨텍스트 스위치를 유발하는 이벤트는 크게 비자발적*involuntary*과 자발적*voluntary*으로 나뉜다. **비자발적 전환**은 태스크의 의사와 무관하게 커널이 강제로 CPU를 회수하는 경우다. 타이머 인터럽트에 의한 선점*preemption*[^preemption]이 대표적이다. [스케줄러](Scheduler.md)가 설정한 타임 퀀텀[^time-quantum]이 만료되면, 타이머 인터럽트 핸들러가 `TIF_NEED_RESCHED` 플래그를 설정하고, 커널이 사용자 공간으로 복귀하기 직전 또는 선점 가능 지점에서 스케줄링이 발생한다. 더 높은 우선순위의 태스크가 깨어나는 경우에도 현재 태스크가 밀려난다. **자발적 전환**은 태스크가 스스로 CPU를 양보하는 경우다. `read()`, `write()` 같은 블로킹 시스템 콜을 호출하면 태스크는 I/O 완료를 기다리며 대기 상태로 전환된다. 뮤텍스나 세마포어 같은 동기화 프리미티브에서 블로킹되는 경우, `sched_yield()`를 명시적으로 호출하는 경우도 여기에 해당한다. ```mermaid flowchart LR A[실행 중인 태스크] -->|"타이머 인터럽트<br/>높은 우선순위 태스크 도착"| B[비자발적 전환] A -->|"블로킹 I/O<br/>동기화 대기<br/>sched_yield"| C[자발적 전환] B --> D["schedule() 호출"] C --> D D --> E[컨텍스트 스위치 실행] ``` ## 컨텍스트 스위치의 6단계 컨텍스트 스위치의 전체 과정은 커널 진입부터 사용자 공간 복귀까지 6개 단계로 구분할 수 있다. ### 1단계: 커널 진입 이벤트(인터럽트, 시스템 콜, 예외)가 발생하면 CPU는 하드웨어 수준에서 커널 모드로 전환한다. 이 과정에서 아키텍처마다 하드웨어가 자동으로 저장하는 상태의 범위가 다르다. x86-64의 경우, 인터럽트 경로에서는 `TSS`(Task State Segment)로부터 커널 스택 포인터(`RSP0`)를 로드한 뒤, `SS`, `RSP`, `RFLAGS`, `CS`, `RIP` 다섯 개 값을 커널 스택에 푸시한다. `SYSCALL` 명령어를 통한 빠른 경로에서는 `RIP`를 `RCX`에, `RFLAGS`를 `R11`에 저장하고, `LSTAR` MSR[^msr]에서 커널 진입점을 로드한다. 이 경로에서는 스택 전환이 자동으로 이루어지지 않으므로, 커널 코드가 per-CPU[^per-cpu] 자료구조에서 직접 커널 스택을 가져와야 한다. AArch64는 하드웨어가 저장하는 것이 `SPSR_EL1`(프로그램 상태)과 `ELR_EL1`(복귀 주소) 단 두 개뿐이다. 나머지 모든 레지스터는 커널의 진입 스텁이 `STP`(Store Pair) 명령어로 직접 저장해야 한다. ### 2단계: 현재 태스크 상태 저장 커널 진입 코드가 나머지 범용 레지스터를 커널 스택의 `pt_regs`[^pt-regs] 구조체에 저장한다. 부동소수점/SIMD 상태는 `switch_fpu()` 함수를 통해 태스크의 스레드 정보 구조체에 저장된다. 리눅스는 여기서 `TIF_NEED_FPU_LOAD` 플래그를 활용한 **지연 복원*lazy restore*** 전략을 사용한다 — FPU 상태는 사용자 공간으로 돌아갈 때 `switch_fpu_return()`에서 실제로 필요한 시점에만 복원한다. 이어서 나가는 태스크의 PCB가 갱신된다. 상태가 `RUNNING`에서 `READY`(또는 `BLOCKED`)로 변경되고, 스케줄링 통계가 기록되며, 커널 스택 포인터가 PCB에 저장된다. ### 3단계: 다음 태스크 선택 [스케줄러](Scheduler.md)가 호출된다. 리눅스 v6.6 이후에는 [Earliest Eligible Virtual Deadline First](Earliest%20Eligible%20Virtual%20Deadline%20First.md)(Earliest Eligible Virtual Deadline First)가 기본 스케줄링 알고리즘으로, 가상 데드라인이 가장 이른 적격 태스크를 선택한다. 선택된 태스크가 현재 태스크와 동일하면 전환 없이 바로 복귀한다. ### 4단계: 주소 공간 전환 들어오는 태스크가 다른 프로세스에 속하면 — 즉 `mm_struct`[^mm-struct]가 다르면 — 주소 공간을 전환해야 한다. 이 단계가 프로세스 간 컨텍스트 스위치에서 가장 비용이 큰 부분이다. x86-64에서는 새 프로세스의 `PML4`/`PML5`[^pml] 물리 주소를 `CR3` 레지스터에 기록한다. `PCID`(Process Context Identifier) 없이는 이 쓰기 자체가 TLB[^tlb] 전체를 플러시한다. `PCID`를 사용하면 `CR3`의 하위 12비트에 태그를 포함시키고 bit 63을 설정하여 "no-flush" 로드를 수행할 수 있어, 다른 프로세스의 TLB 항목이 보존된다. AArch64는 설계적으로 유리하다. 사용자 공간 페이지 테이블(`TTBR0_EL1`)과 커널 페이지 테이블(`TTBR1_EL1`)이 분리되어 있어, 프로세스 전환 시 `TTBR0`만 변경하면 된다. `ASID`(Address Space Identifier)가 TLB 항목에 태깅되므로, `ASID`가 다른 프로세스 간 전환에서는 TLB 플러시가 필요 없다. 16비트 `ASID`(ARMv8.2+)는 최대 65,536개의 주소 공간을 동시에 태깅할 수 있어 롤오버가 거의 발생하지 않는다. 같은 프로세스 내의 [스레드](Thread.md) 간 전환이라면 `mm_struct`가 동일하므로 이 단계가 완전히 생략된다. 이것이 스레드 컨텍스트 스위치가 프로세스 컨텍스트 스위치보다 가벼운 핵심 이유다. ### 5단계: 레지스터 및 스택 복원 들어오는 태스크의 커널 스택 포인터가 PCB에서 로드되고, 범용 레지스터가 커널 스택의 `pt_regs`에서 복원된다. 이 시점에서 CPU는 완전히 새 태스크의 컨텍스트에서 실행된다. 추가로 복원해야 할 per-process 상태로는 스레드 로컬 저장소 베이스(x86-64의 `FS`/`GS` 세그먼트 베이스), 디버그 레지스터(추적 중인 프로세스), 성능 모니터링 카운터 등이 있다. AArch64에서는 PAC(Pointer Authentication) 키 5개(각 128비트)와 MTE(Memory Tagging Extension) 관련 레지스터도 복원 대상이다. ### 6단계: 사용자 공간 복귀 x86-64에서는 `IRET`(약 40~80 사이클) 또는 `SYSRET`(약 20~30 사이클)를 통해 사용자 모드(Ring 3[^ring])로 복귀한다. `SYSRET`가 더 빠르지만, `RCX`에 비정규*non-canonical*[^non-canonical] 주소가 들어 있으면 커널 스택을 가진 채 Ring 3에서 폴트가 발생하는 보안 함정이 있어, 리눅스는 특정 엣지 케이스에서 `IRET`로 폴백한다. AArch64는 `ERET` 하나로 통일된 복귀 메커니즘을 제공한다. `ELR_EL1`에서 `PC`를, `SPSR_EL1`에서 `PSTATE`를 복원하여 `EL0`으로 돌아간다. 새 태스크는 이전에 중단된 정확한 명령어 지점에서 실행을 재개하며, 전환이 일어났다는 사실을 인지하지 못한다. ## 리눅스 커널 구현: `context_switch()` 리눅스 v6.19의 `context_switch()` 함수(`kernel/sched/core.c:5236`)는 위 6단계 중 4~5단계를 직접 오케스트레이션한다. 이 함수는 `__schedule()`에서 스케줄러가 다음 태스크를 선택한 후 호출된다. ```c /* * context_switch - switch to the new MM and the new thread's register state. */ static __always_inline struct rq * context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next, struct rq_flags *rf) { prepare_task_switch(rq, prev, next); arch_start_context_switch(prev); if (!next->mm) { // → 커널 스레드 enter_lazy_tlb(prev->active_mm, next); next->active_mm = prev->active_mm; if (prev->mm) // ← 유저 프로세스에서 온 경우 mmgrab_lazy_tlb(prev->active_mm); else prev->active_mm = NULL; } else { // → 유저 프로세스 membarrier_switch_mm(rq, prev->active_mm, next->mm); switch_mm_irqs_off(prev->active_mm, next->mm, next); lru_gen_use_mm(next->mm); if (!prev->mm) { // ← 커널 스레드에서 온 경우 rq->prev_mm = prev->active_mm; prev->active_mm = NULL; } } switch_mm_cid(rq, prev, next); prepare_lock_switch(rq, next, rf); switch_to(prev, next, prev); barrier(); return finish_task_switch(prev); } ``` 이 코드의 핵심 분기는 `next->mm`의 존재 여부다. **커널 스레드로의 전환(`!next->mm`)**: 커널 스레드는 고유한 사용자 공간 메모리 맵을 갖지 않는다. 이전 태스크의 `active_mm`[^active-mm]을 빌려 쓰는 **Lazy TLB** 최적화를 적용하여 `CR3`/`TTBR0` 변경과 TLB 플러시를 완전히 회피한다. `mmgrab_lazy_tlb()`로 참조 카운트를 올려 메모리 맵이 조기 해제되는 것을 방지한다. **유저 프로세스로의 전환**: `switch_mm_irqs_off()`가 실질적인 주소 공간 전환을 수행한다. 내부적으로 `CR3` 또는 `TTBR0`를 변경하며, `PCID`/`ASID`를 활용해 불필요한 TLB 플러시를 최소화한다. 양쪽 경로 모두 `switch_to()` 매크로를 통해 레지스터와 스택을 전환한다. 이 매크로는 아키텍처별 어셈블리로 구현되어 있으며, x86-64에서는 `__switch_to_asm()`이 callee-saved[^callee-saved] 레지스터(`RBX`, `RBP`, `R12`~`R15`)를 푸시하고 `RSP`를 교체한 뒤 `__switch_to()`를 호출한다. `switch_to()`가 반환된 시점에서 CPU는 이미 `next` 태스크의 컨텍스트에서 실행되고 있다. ![[contextSwitchCallFlow.svg]] `finish_task_switch()`는 전환 완료 후 마무리 작업을 담당한다. 런큐[^runqueue] 잠금 해제, 죽은 태스크의 자원 회수, `perf` 이벤트 처리 등을 수행한다. 이 함수는 BPF[^bpf] 및 `perf`에서 off-CPU[^off-cpu] 분석의 핵심 추적 지점이기도 하다. ## 프로세스 전환 vs. 스레드 전환 ![[processVsThreadSwitch.svg]] | 항목 | 프로세스 전환 | 스레드 전환 | |------|-------------|-----------| | 레지스터 저장/복원 | O | O | | 페이지 테이블 전환 | O | X (공유 주소 공간) | | TLB 플러시 | O (`PCID`/`ASID` 없으면) | X | | FPU 상태 전환 | O | O | | 캐시 영향 | 높음 (cold cache) | 낮음 (공유 캐시) | | 대략적 비용 | 약 1~10 μs | 약 0.5~5 μs | [스레드](Thread.md) 간 전환이 가벼운 이유는 명확하다. 같은 프로세스에 속한 스레드들은 동일한 `mm_struct`를 공유하므로, 주소 공간 전환과 그에 따른 TLB 무효화가 생략된다. ## 성능 비용 분석 컨텍스트 스위치의 비용은 직접 비용과 간접 비용으로 나뉘며, 실무에서는 간접 비용이 지배적인 경우가 많다. ### 직접 비용 직접 비용은 전환 과정 자체에서 소비되는 시간이다. 범용 레지스터 저장/복원에 약 50ns, `CR3` 쓰기와 TLB 플러시에 약 200~500ns, FPU의 `XSAVE`/`XRSTOR`에 약 200ns, 스케줄러의 다음 태스크 선택에 약 100~500ns가 소요된다. 합산하면 대략 1~5μs 수준이다. ### 간접 비용 간접 비용은 전환 이후에 발생하는 성능 저하다. TLB가 플러시되면 새 태스크의 메모리 접근마다 페이지 테이블 워크가 발생하고, 이전 태스크의 워킹 셋이 차지하던 캐시 라인이 새 태스크의 데이터로 교체되면서 L1/L2/L3 캐시 미스가 급증한다. 분기 예측기의 히스토리도 무효화되어 분기 미스프레딕션이 늘어난다. 이러한 간접 비용은 워킹 셋 크기에 따라 직접 비용의 수 배에서 수십 배에 달할 수 있다. ### KPTI의 추가 비용 x86-64에서 Meltdown 취약점 완화를 위해 도입된 `KPTI`(Kernel Page Table Isolation)는 프로세스마다 사용자/커널 두 세트의 페이지 테이블을 유지한다. 시스템 콜마다 `CR3`를 두 번 전환해야 하므로, syscall 집약적 워크로드에서 5~30%의 추가 오버헤드가 발생한다. `PCID`가 이 비용을 상당 부분 상쇄하지만, 완전히 제거하지는 못한다. AArch64는 `TTBR0`/`TTBR1` 분리 설계 덕분에 대부분의 코어에서 `KPTI`가 불필요하다(Cortex-A75 예외). ## x86-64 vs. AArch64 비교 | 항목 | x86-64 | AArch64 | |------|--------|---------| | 권한 모델 | Ring 0~3 (0과 3만 사용) | `EL0`~`EL3` | | 하드웨어 자동 저장 | 5개 (`SS`, `RSP`, `RFLAGS`, `CS`, `RIP`) | 2개 (`SPSR_EL1`, `ELR_EL1`) | | 시스템 콜 진입 | `SYSCALL`/`SYSRET` (전용 빠른 경로) | `SVC` (동기 예외와 동일 경로) | | 범용 레지스터 수 | 16 (`RAX`~`R15`) | 31 (`X0`~`X30`) + `SP_EL0` | | 페이지 테이블 레지스터 | `CR3` 하나 (전체 주소 공간) | `TTBR0` (사용자) + `TTBR1` (커널) 분리 | | TLB 태깅 | `PCID` (12비트, 최대 4,096) | `ASID` (8/16비트, 최대 65,536) | | 커널 매핑 전환 | 필요 (`CR3`가 전체를 커버) | 불필요 (`TTBR1` 고정) | | SIMD 상태 크기 | 최대 약 2.7KB (`AVX-512`) | 최대 약 8KB (`SVE` 최대 벡터 길이) | | Spectre 완화 | `IBPB` 명시적 발행 필요 | 대부분 코어에서 하드웨어 격리 | | 사용자 복귀 메커니즘 | `IRET` (느림) / `SYSRET` (빠름, 주의 필요) | `ERET` (단일 통합 메커니즘) | | 대략적 전환 비용 | 약 1~5 μs | 약 0.5~3 μs | AArch64가 컨텍스트 스위치에서 구조적으로 유리한 이유는 `TTBR` 분리 설계와 넓은 `ASID` 공간에 있다. 커널 매핑을 전환할 필요가 없고, TLB 플러시가 발생할 확률이 현저히 낮다. ## 완화 기법 컨텍스트 스위치의 비용을 줄이기 위해 하드웨어와 소프트웨어 양쪽에서 다양한 최적화가 적용된다. **`PCID`/`ASID`**는 TLB 항목에 프로세스 식별자를 태깅하여 전체 플러시를 회피한다. 스위치 집약적 워크로드에서 TLB 미스 페널티를 10~40% 줄인다. **Lazy FPU 복원**은 FPU 상태를 즉시 복원하지 않고, 태스크가 실제로 부동소수점 명령어를 사용할 때까지 지연시킨다. 리눅스에서는 `TIF_NEED_FPU_LOAD` 플래그로 구현된다. **CPU 어피니티 고정**은 특정 태스크를 같은 코어에 바인딩하여 웜 캐시와 TLB를 유지한다. **Tickless 커널(`NO_HZ`)**은 유휴 또는 단일 태스크 CPU에서 타이머 인터럽트를 억제하여 불필요한 전환을 방지한다. **사용자 공간 스케줄링**(고루틴, 그린 스레드 등)은 커널 진입 없이 사용자 공간에서 전환을 수행하여 비용을 수십 나노초 수준까지 낮춘다. 다만 커널의 선점과 I/O 스케줄링을 활용할 수 없다는 트레이드오프가 있다. --- [^simd]: SIMD(Single Instruction, Multiple Data)는 하나의 명령어로 여러 데이터를 동시에 처리하는 병렬 연산 방식이다. x86의 `SSE`/`AVX`, ARM의 `NEON`/`SVE`가 대표적이다. — Intel SDM Volume 1, Chapter 10; ARM Architecture Reference Manual, Chapter C1. [^sve]: `SVE`(Scalable Vector Extension)는 ARM의 가변 길이 벡터 확장이다. 벡터 길이가 128~2048비트 사이에서 하드웨어 구현에 따라 달라지므로, 컨텍스트 저장 크기도 가변적이다. `SME`(Scalable Matrix Extension)는 행렬 연산용 확장이다. — ARM Architecture Reference Manual for A-profile, Chapter B1 (The AArch64 Application Level Programmers' Model). [^xsave]: `XSAVE`/`XRSTOR`는 x86의 확장 상태 저장/복원 명령어로, FPU, `SSE`, `AVX`, `AVX-512` 등 프로세서 확장 기능의 상태를 메모리에 일괄 저장하거나 복원한다. 비트마스크(`XCR0`, `XSTATE_BV`)로 필요한 컴포넌트만 선택적으로 처리할 수 있다. — Intel SDM Volume 1, Chapter 13 "Managing State Using the XSAVE Feature Set". [^preemption]: 선점*preemption*이란 현재 실행 중인 태스크의 동의 없이 운영체제가 CPU를 강제로 회수하는 동작이다. 선점형 스케줄링에서는 타이머 인터럽트나 높은 우선순위 태스크 도착 시 자동으로 발생한다. — Silberschatz et al., *Operating System Concepts* 10th ed., §5.1. [^time-quantum]: 타임 퀀텀*time quantum*(또는 타임 슬라이스*time slice*)은 스케줄러가 하나의 태스크에 연속으로 허용하는 최대 CPU 사용 시간이다. 이 시간이 만료되면 타이머 인터럽트가 발생하여 스케줄링 판단이 이루어진다. — Silberschatz et al., *Operating System Concepts* 10th ed., §5.3.4. [^msr]: MSR(Model-Specific Register)은 프로세서 모델에 따라 다른 특수 레지스터로, `RDMSR`/`WRMSR` 명령어로 접근한다. `LSTAR`(MSR 주소 `0xC0000082`)는 `SYSCALL` 명령어 실행 시 분기할 커널 진입점 주소를 저장한다. — Intel SDM Volume 3, Chapter 5 "SYSCALL/SYSRET"; AMD64 APM Volume 2, §6.1.1. [^per-cpu]: per-CPU 자료구조는 각 CPU 코어마다 독립된 복사본을 두는 데이터로, 잠금 없이 접근할 수 있어 성능이 좋다. 커널 스택 포인터, 현재 태스크 포인터(`current`), 런큐 등이 per-CPU로 관리된다. — `include/linux/percpu.h`; Love, *Linux Kernel Development* 3rd ed., Chapter 12. [^pt-regs]: `pt_regs`는 리눅스 커널에서 인터럽트나 시스템 콜 진입 시 CPU 레지스터 상태를 저장하는 아키텍처별 구조체다. x86-64에서는 `arch/x86/include/asm/ptrace.h`, AArch64에서는 `arch/arm64/include/asm/ptrace.h`에 정의되어 있다. [^mm-struct]: `mm_struct`는 리눅스에서 프로세스의 가상 주소 공간 전체를 기술하는 구조체다. 페이지 테이블 루트, VMA(Virtual Memory Area) 목록, 메모리 통계 등을 포함하며, 같은 프로세스의 스레드들은 하나의 `mm_struct`를 공유한다. — `include/linux/mm_types.h`; Bovet and Cesati, *Understanding the Linux Kernel* 3rd ed., Chapter 9. [^pml]: `PML4`(Page Map Level 4)와 `PML5`(Page Map Level 5)는 x86-64의 다단계 페이지 테이블 계층에서 최상위 테이블이다. `PML4`는 4단계 변환으로 48비트 가상 주소(256TB)를, `PML5`는 5단계 변환으로 57비트 가상 주소(128PB)를 지원한다. — Intel SDM Volume 3, Chapter 4 "Paging". [^tlb]: TLB(Translation Lookaside Buffer)는 가상 주소에서 물리 주소로의 변환 결과를 캐싱하는 하드웨어 버퍼다. TLB 히트 시 페이지 테이블 워크 없이 즉시 주소를 변환할 수 있어, 메모리 접근 성능에 큰 영향을 미친다. — Hennessy and Patterson, *Computer Architecture: A Quantitative Approach* 6th ed., Appendix B. [^ring]: Ring은 x86 아키텍처의 하드웨어 권한 수준 모델이다. Ring 0(커널 모드)은 모든 명령어와 하드웨어에 접근할 수 있고, Ring 3(사용자 모드)은 특권 명령어 실행이 제한된다. 현재 권한 수준(`CPL`)은 `CS` 세그먼트 셀렉터의 하위 2비트에 인코딩된다. — Intel SDM Volume 3, §5.5 "Privilege Levels". [^non-canonical]: x86-64의 48비트 가상 주소 모드에서, 유효 주소는 `0x0000000000000000`~`0x00007FFFFFFFFFFF`(사용자)과 `0xFFFF800000000000`~`0xFFFFFFFFFFFFFFFF`(커널) 범위에만 존재한다. 이 두 범위 사이의 주소를 비정규*non-canonical* 주소라 하며, 접근 시 General Protection Fault(`#GP`)가 발생한다. — Intel SDM Volume 1, §3.3.7.1; AMD64 APM Volume 2, §1.1. [^active-mm]: `active_mm`은 현재 CPU에서 실제로 활성화된 메모리 맵을 가리키는 `task_struct`의 필드다. 유저 프로세스에서는 `mm`과 동일하지만, 커널 스레드(`mm == NULL`)는 직전 태스크에서 빌려온 `active_mm`을 사용하여 불필요한 주소 공간 전환을 피한다. — `include/linux/sched.h`; Bovet and Cesati, *Understanding the Linux Kernel* 3rd ed., §3.2. [^callee-saved]: callee-saved 레지스터(또는 비휘발성*non-volatile* 레지스터)는 함수 호출 규약(ABI)에서 피호출 함수*callee*가 원래 값을 보존해야 하는 레지스터다. x86-64 System V ABI에서는 `RBX`, `RBP`, `R12`~`R15`가 이에 해당한다. — *System V Application Binary Interface — AMD64 Architecture Processor Supplement*, §3.2.1. [^runqueue]: 런큐*runqueue*는 특정 CPU에서 실행 대기 중인 태스크들을 관리하는 per-CPU 자료구조다. 리눅스에서는 `struct rq`로 구현되며, 스케줄링 클래스별 서브큐(`cfs_rq`, `rt_rq` 등)를 포함한다. — `kernel/sched/sched.h`; Love, *Linux Kernel Development* 3rd ed., Chapter 4. [^bpf]: BPF(Berkeley Packet Filter)는 원래 네트워크 패킷 필터링용 가상 머신이었으나, eBPF(extended BPF)로 확장되어 리눅스 커널의 범용 추적·모니터링 프레임워크가 되었다. 커널 함수에 프로브를 걸어 안전하게 실시간 성능 분석을 수행할 수 있다. — `Documentation/bpf/`; Gregg, B. (2019) *BPF Performance Tools*, Addison-Wesley. [^off-cpu]: off-CPU 분석은 태스크가 CPU 위에서 실행되지 않는 시간(I/O 대기, 잠금 대기, 슬립 등)을 측정하는 성능 분석 기법이다. `perf`나 BPF 도구로 `finish_task_switch()`를 추적하여 대기 원인과 소요 시간을 파악한다. — Gregg, B. (2019) *BPF Performance Tools*, Addison-Wesley, Chapter 14 "Kernel". ## 출처 - Silberschatz, A., Galvin, P. B., and Gagne, G. (2018) *Operating System Concepts*. 10th ed. Hoboken, NJ: Wiley. - Arpaci-Dusseau, R. H. and Arpaci-Dusseau, A. C. (2023) *Operating Systems: Three Easy Pieces*. 1.10 ed. Arpaci-Dusseau Books. - Tanenbaum, A. S. and Bos, H. (2023) *Modern Operating Systems*. 5th ed. London: Pearson. - Stallings, W. (2018) *Operating Systems: Internals and Design Principles*. 9th ed. London: Pearson. - Anderson, T. and Dahlin, M. (2012) *Operating Systems: Principles and Practice*. Recursive Books. - Bovet, D. P. and Cesati, M. (2008) *Understanding the Linux Kernel*. 3rd ed. Sebastopol, CA: O'Reilly. - Love, R. (2010) *Linux Kernel Development*. 3rd ed. Upper Saddle River, NJ: Addison-Wesley. - Gregg, B. (2019) *BPF Performance Tools*. Upper Saddle River, NJ: Addison-Wesley. - Hennessy, J. L. and Patterson, D. A. (2019) *Computer Architecture: A Quantitative Approach*. 6th ed. Cambridge, MA: Morgan Kaufmann. - Patterson, D. A. and Hennessy, J. L. (2021) *Computer Organization and Design MIPS Edition: The Hardware/Software Interface*. 6th ed. Cambridge, MA: Morgan Kaufmann. - Intel Corporation. *Intel® 64 and IA-32 Architectures Software Developer's Manual*. - AMD. *AMD64 Architecture Programmer's Manual — System Programming*. - Markstedter, M. (2023) *Blue Fox: Arm Assembly Internals and Reverse Engineering*. Indianapolis, IN: Wiley. - *System V Application Binary Interface — AMD64 Architecture Processor Supplement*. - Linux kernel source v6.19 — `kernel/sched/core.c`, `arch/x86/kernel/process_64.c`, `arch/arm64/kernel/process.c`.