Introduction

컨텍스트 스위치context switch는 운영체제가 멀티태스킹을 실현하는 핵심 원리입니다. CPU의 제어권을 현재 실행 중인 태스크에서 다른 태스크로 넘기는 이 과정은, 소프트웨어(커널)와 하드웨어(CPU 아키텍처)가 긴밀하게 협력해야 하는 대표적인 영역입니다. 이 글에서는 컨텍스트 스위치가 무엇이며 언제, 왜 발생하는지부터 리눅스 커널에서의 실제 구현, 그리고 x86-64와 AArch64 아키텍처에서의 하드웨어 동작까지를 종합적으로 살펴봅니다.

컨텍스트란 무엇인가

컨텍스트context란 CPU가 특정 태스크의 실행을 재개하기 위해 필요한 모든 상태 정보의 총합이다. 이 정보는 Process Control Block(이하 PCB)에 보관되며, 다음과 같은 요소들로 구성된다.

범주내용
범용 레지스터x86-64: RAXR15 (16개), AArch64: X0X30 (31개)
프로그램 상태플래그 레지스터(RFLAGS), 조건 코드
명령어 포인터RIP (x86-64), PC / ELR_EL1 (AArch64)
부동소수점/SIMD1FPU, SSE/AVX/AVX-512 (x86-64), NEON/SVE2/SME (AArch64)
메모리 관리페이지 테이블 베이스 — CR3 (x86-64), TTBR0_EL1 (AArch64)
커널 스택태스크별 커널 스택 포인터
스케줄링 메타데이터상태(RUNNING, READY, BLOCKED), 우선순위, 소비한 CPU 시간

컨텍스트의 크기는 아키텍처와 활용하는 확장 기능에 따라 달라진다. x86-64에서 AVX-512를 사용하면 XSAVE3 영역만 약 2.7KB에 달하고, AArch64에서 SVE를 최대 벡터 길이로 사용하면 레지스터 파일만 약 8KB까지 커진다.

컨텍스트 스위치는 언제 발생하는가

컨텍스트 스위치를 유발하는 이벤트는 크게 비자발적involuntary과 자발적voluntary으로 나뉜다.

  • 비자발적 전환은 태스크의 의사와 무관하게 커널이 강제로 CPU를 회수하는 경우다. 타이머 인터럽트에 의한 선점preemption4이 대표적이다. 스케줄러가 설정한 타임 퀀텀5이 만료되면, 타이머 인터럽트 핸들러가 TIF_NEED_RESCHED 플래그를 설정하고, 커널이 사용자 공간으로 복귀하기 직전 또는 선점 가능 지점에서 스케줄링이 발생한다. 더 높은 우선순위의 태스크가 깨어나는 경우에도 현재 태스크가 밀려난다.
  • 자발적 전환은 태스크가 스스로 CPU를 양보하는 경우다. read(), write() 같은 블로킹 시스템 콜을 호출하면 태스크는 I/O 완료를 기다리며 대기 상태로 전환된다. 뮤텍스나 세마포어 같은 동기화 프리미티브에서 블로킹되는 경우, sched_yield()를 명시적으로 호출하는 경우도 여기에 해당한다.

컨텍스트 스위치의 6단계

컨텍스트 스위치의 전체 과정은 커널 진입부터 사용자 공간 복귀까지 6개 단계로 구분할 수 있다.

1단계: 커널 진입

이벤트(인터럽트, 시스템 콜, 예외)가 발생하면 CPU는 하드웨어 수준에서 커널 모드로 전환한다. 이 과정에서 아키텍처마다 하드웨어가 자동으로 저장하는 상태의 범위가 다르다.

x86-64의 경우, 인터럽트 경로에서는 TSS(Task State Segment)로부터 커널 스택 포인터(RSP0)를 로드한 뒤, SS, RSP, RFLAGS, CS, RIP 다섯 개 값을 커널 스택에 푸시한다. SYSCALL 명령어를 통한 빠른 경로에서는 RIPRCX에, RFLAGSR11에 저장하고, LSTAR MSR6에서 커널 진입점을 로드한다. 이 경로에서는 스택 전환이 자동으로 이루어지지 않으므로, 커널 코드가 per-CPU7 자료구조에서 직접 커널 스택을 가져와야 한다.

AArch64는 하드웨어가 저장하는 것이 SPSR_EL1(프로그램 상태)과 ELR_EL1(복귀 주소) 단 두 개뿐이다. 나머지 모든 레지스터는 커널의 진입 스텁이 STP(Store Pair) 명령어로 직접 저장해야 한다.

2단계: 현재 태스크 상태 저장

커널 진입 코드가 나머지 범용 레지스터를 커널 스택의 pt_regs8 구조체에 저장한다. 부동소수점/SIMD 상태는 switch_fpu() 함수를 통해 태스크의 스레드 정보 구조체에 저장된다. 리눅스는 여기서 TIF_NEED_FPU_LOAD 플래그를 활용한 지연 복원lazy restore 전략을 사용한다. FPU 상태는 사용자 공간으로 돌아갈 때 switch_fpu_return()에서 실제로 필요한 시점에만 복원한다.

이어서 나가는 태스크의 PCB가 갱신된다. 상태가 RUNNING에서 READY(또는 BLOCKED)로 변경되고, 스케줄링 통계가 기록되며, 커널 스택 포인터가 PCB에 저장된다.

3단계: 다음 태스크 선택

스케줄러가 호출된다. 리눅스 v6.6 이후에는 Earliest Eligible Virtual Deadline First(Earliest Eligible Virtual Deadline First)가 기본 스케줄링 알고리즘으로, 가상 데드라인이 가장 이른 적격 태스크를 선택한다. 선택된 태스크가 현재 태스크와 동일하면 전환 없이 바로 복귀한다.

4단계: 주소 공간 전환

들어오는 태스크가 다른 프로세스에 속하면 — 즉 mm_struct9가 다르면 — 주소 공간을 전환해야 한다. 이 단계가 프로세스 간 컨텍스트 스위치에서 가장 비용이 큰 부분이다.

x86-64에서는 새 프로세스의 PML4/PML510 물리 주소를 CR3 레지스터에 기록한다. PCID(Process Context Identifier) 없이는 이 쓰기 자체가 TLB11 전체를 플러시한다. 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개의 주소 공간을 동시에 태깅할 수 있어 롤오버가 거의 발생하지 않는다.

같은 프로세스 내의 스레드 간 전환이라면 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(약 4080 사이클) 또는 SYSRET(약 2030 사이클)를 통해 사용자 모드(Ring 312)로 복귀한다. SYSRET가 더 빠르지만, RCX에 비정규non-canonical13 주소가 들어 있으면 커널 스택을 가진 채 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()에서 스케줄러가 다음 태스크를 선택한 후 호출된다.

/*
 * 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_mm14을 빌려 쓰는 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-saved15 레지스터(RBX, RBP, R12~R15)를 푸시하고 RSP를 교체한 뒤 __switch_to()를 호출한다. switch_to()가 반환된 시점에서 CPU는 이미 next 태스크의 컨텍스트에서 실행되고 있다.

finish_task_switch()는 전환 완료 후 마무리 작업을 담당한다. 런큐16 잠금 해제, 죽은 태스크의 자원 회수, perf 이벤트 처리 등을 수행한다. 이 함수는 BPF17perf에서 off-CPU18 분석의 핵심 추적 지점이기도 하다.

프로세스 전환 vs. 스레드 전환

항목프로세스 전환스레드 전환
레지스터 저장/복원OO
페이지 테이블 전환OX (공유 주소 공간)
TLB 플러시O (PCID/ASID 없으면)X
FPU 상태 전환OO
캐시 영향높음 (cold cache)낮음 (공유 캐시)
대략적 비용약 1~10 μs약 0.5~5 μs

스레드 간 전환이 가벼운 이유는 명확하다. 같은 프로세스에 속한 스레드들은 동일한 mm_struct를 공유하므로, 주소 공간 전환과 그에 따른 TLB 무효화가 생략된다.

성능 비용 분석

컨텍스트 스위치의 비용은 직접 비용과 간접 비용으로 나뉘며, 실무에서는 간접 비용이 지배적인 경우가 많다.

직접 비용

직접 비용은 전환 과정 자체에서 소비되는 시간이다. 범용 레지스터 저장/복원에 약 50ns, CR3 쓰기와 TLB 플러시에 약 200500ns, FPU의 XSAVE/XRSTOR에 약 200ns, 스케줄러의 다음 태스크 선택에 약 100500ns가 소요된다. 합산하면 대략 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-64AArch64
권한 모델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 스케줄링을 활용할 수 없다는 트레이드오프가 있다.

출처

  • 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.

Footnotes

  1. SIMD(Single Instruction, Multiple Data)는 하나의 명령어로 여러 데이터를 동시에 처리하는 병렬 연산 방식이다. x86의 SSE/AVX, ARM의 NEON/SVE가 대표적이다. — Intel SDM Volume 1, Chapter 10; ARM Architecture Reference Manual, Chapter C1.

  2. 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).

  3. XSAVE/XRSTOR는 x86의 확장 상태 저장/복원 명령어로, FPU, SSE, AVX, AVX-512 등 프로세서 확장 기능의 상태를 메모리에 일괄 저장하거나 복원한다. 비트마스크(XCR0, XSTATE_BV)로 필요한 컴포넌트만 선택적으로 처리할 수 있다. — Intel SDM Volume 1, Chapter 13 "Managing State Using the XSAVE Feature Set".

  4. 선점preemption이란 현재 실행 중인 태스크의 동의 없이 운영체제가 CPU를 강제로 회수하는 동작이다. 선점형 스케줄링에서는 타이머 인터럽트나 높은 우선순위 태스크 도착 시 자동으로 발생한다. — Silberschatz et al., Operating System Concepts 10th ed., §5.1.

  5. 타임 퀀텀time quantum(또는 타임 슬라이스time slice)은 스케줄러가 하나의 태스크에 연속으로 허용하는 최대 CPU 사용 시간이다. 이 시간이 만료되면 타이머 인터럽트가 발생하여 스케줄링 판단이 이루어진다. — Silberschatz et al., Operating System Concepts 10th ed., §5.3.4.

  6. 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.

  7. per-CPU 자료구조는 각 CPU 코어마다 독립된 복사본을 두는 데이터로, 잠금 없이 접근할 수 있어 성능이 좋다. 커널 스택 포인터, 현재 태스크 포인터(current), 런큐 등이 per-CPU로 관리된다. — include/linux/percpu.h; Love, Linux Kernel Development 3rd ed., Chapter 12.

  8. pt_regs는 리눅스 커널에서 인터럽트나 시스템 콜 진입 시 CPU 레지스터 상태를 저장하는 아키텍처별 구조체다. x86-64에서는 arch/x86/include/asm/ptrace.h, AArch64에서는 arch/arm64/include/asm/ptrace.h에 정의되어 있다.

  9. mm_struct는 리눅스에서 프로세스의 가상 주소 공간 전체를 기술하는 구조체다. 페이지 테이블 루트, VMA(Virtual Memory Area) 목록, 메모리 통계 등을 포함하며, 같은 프로세스의 스레드들은 하나의 mm_struct를 공유한다. — include/linux/mm_types.h; Bovet and Cesati, Understanding the Linux Kernel 3rd ed., Chapter 9.

  10. 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".

  11. TLB(Translation Lookaside Buffer)는 가상 주소에서 물리 주소로의 변환 결과를 캐싱하는 하드웨어 버퍼다. TLB 히트 시 페이지 테이블 워크 없이 즉시 주소를 변환할 수 있어, 메모리 접근 성능에 큰 영향을 미친다. — Hennessy and Patterson, Computer Architecture: A Quantitative Approach 6th ed., Appendix B.

  12. Ring은 x86 아키텍처의 하드웨어 권한 수준 모델이다. Ring 0(커널 모드)은 모든 명령어와 하드웨어에 접근할 수 있고, Ring 3(사용자 모드)은 특권 명령어 실행이 제한된다. 현재 권한 수준(CPL)은 CS 세그먼트 셀렉터의 하위 2비트에 인코딩된다. — Intel SDM Volume 3, §5.5 "Privilege Levels".

  13. x86-64의 48비트 가상 주소 모드에서, 유효 주소는 0x00000000000000000x00007FFFFFFFFFFF(사용자)과 0xFFFF8000000000000xFFFFFFFFFFFFFFFF(커널) 범위에만 존재한다. 이 두 범위 사이의 주소를 비정규non-canonical 주소라 하며, 접근 시 General Protection Fault(#GP)가 발생한다. — Intel SDM Volume 1, §3.3.7.1; AMD64 APM Volume 2, §1.1.

  14. 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.

  15. 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.

  16. 런큐runqueue는 특정 CPU에서 실행 대기 중인 태스크들을 관리하는 per-CPU 자료구조다. 리눅스에서는 struct rq로 구현되며, 스케줄링 클래스별 서브큐(cfs_rq, rt_rq 등)를 포함한다. — kernel/sched/sched.h; Love, Linux Kernel Development 3rd ed., Chapter 4.

  17. BPF(Berkeley Packet Filter)는 원래 네트워크 패킷 필터링용 가상 머신이었으나, eBPF(extended BPF)로 확장되어 리눅스 커널의 범용 추적·모니터링 프레임워크가 되었다. 커널 함수에 프로브를 걸어 안전하게 실시간 성능 분석을 수행할 수 있다. — Documentation/bpf/; Gregg, B. (2019) BPF Performance Tools, Addison-Wesley.

  18. off-CPU 분석은 태스크가 CPU 위에서 실행되지 않는 시간(I/O 대기, 잠금 대기, 슬립 등)을 측정하는 성능 분석 기법이다. perf나 BPF 도구로 finish_task_switch()를 추적하여 대기 원인과 소요 시간을 파악한다. — Gregg, B. (2019) BPF Performance Tools, Addison-Wesley, Chapter 14 "Kernel".