01 · QUESTION
무엇을 확인할 것인가
CPU 하나의 실행 소유권이 prev task에서 next task로 넘어갔다고 판정하려면 어떤 state가 함께 바뀌어야 하는가?
scheduler core는 prev와 next를 선택하지만 실제 SP, callee-saved register, TLS, address-space와 architecture control register의 저장 형식은 switch_to hook에 맡긴다. 반환값 last는 단순한 prev pointer가 아니라 switch를 거쳐 다시 실행된 task가 관찰하는 control-flow 계약이다.
공통 prepare_task_switch()와 finish_task_switch() 사이에서 architecture code가 실행된다. interrupt-disabled 여부, runqueue lock 인계와 stack lifetime을 함께 보지 않으면 함수 호출처럼 보이는 비선형 실행을 잘못 해석한다.
Tswitch = Tmm + Tcallee + Ttls + Textended + Tmitigation으로 나눈다. x86의 speculation mitigation과 debug register, arm64의 TLS/MTE, RISC-V의 envcfg/vector 조건이 config에 따라 긴 tail을 만든다.02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | C hook 뒤 cpu_switch_to가 x19-x29, SP와 LR을 교체 | TLS 또는 contextidr가 prev 값으로 남아 user return 직후 다른 task의 상태를 관찰한다. | prev/next, SP, x19-x29, TPIDR_EL0, CONTEXTIDR_EL1과 last를 한 checkpoint에서 비교한다. |
| x86-64 | inactive_task_frame과 per-CPU/TSS state를 교체 | GS base 또는 TSS.sp0가 prev task 값이면 다음 user exception이 잘못된 kernel stack이나 TLS로 진입한다. | RSP, FSBASE, GSBASE, TSS.sp0, PKRU, debug register와 TIF_NEED_FPU_LOAD를 기록한다. |
| RISC-V | assembly가 ra, sp와 s0-s11을 thread_struct에 저장 | s-register 한 칸의 asm-offset 불일치가 return address나 frame pointer를 다른 field로 복원한다. | TASK_THREAD_* generated offset, ra, sp, s0-s11, tp와 task kernel stack 범위를 대조한다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
arm64
- mechanism
- C hook 뒤
cpu_switch_to가 x19-x29, SP와 LR을 교체 - state
__switch_to()는 FPSIMD, TLS, hw breakpoint, contextidr, entry task와 MTE state를 순서대로 갱신한 뒤 assembly switch로 내려간다. x18 platform register와 PAC/SCS 설정도 build option에 따라 경계에 포함된다.- checkpoint
prev/next, SP, x19-x29, TPIDR_EL0, CONTEXTIDR_EL1과last를 한 checkpoint에서 비교한다.
x86-64
- mechanism
- inactive_task_frame과 per-CPU/TSS state를 교체
- state
__switch_to()는 FPU lazy load 표시, FS/GS segment와 base, TLS array, TSS.sp0, IO bitmap, PKRU와 speculation control을 처리한다. 일반 GPR 대부분은 상위 switch assembly의 inactive frame이 담당한다.- checkpoint
- RSP, FSBASE, GSBASE, TSS.sp0, PKRU, debug register와
TIF_NEED_FPU_LOAD를 기록한다.
RISC-V
- mechanism
- assembly가 ra, sp와 s0-s11을 thread_struct에 저장
- state
__switch_to는 prev의 callee-saved integer register를 저장하고 next 값을 복원한 뒤tp를 next task로 바꾼다. FPU/vector는 switch macro의 별도 helper가 status dirty bit를 보고 처리한다.- checkpoint
- TASK_THREAD_* generated offset, ra, sp, s0-s11, tp와 task kernel stack 범위를 대조한다.
prev와 next를 선택하지만 실제 SP, callee-saved register, TLS, address-space와 architecture control register의 저장 형식은 switch_to hook에 맡긴다. 반환값 last는 단순한 prev pointer가 아니라 switch를 거쳐 다시 실행된 task가 관찰하는 control-flow 계약이다.cpu_switch_to가 x19-x29, SP와 LR을 교체새 task state가 CPU에 보이기 전에 pending asynchronous MTE fault와 user register state를 정리한다.prev/next, SP, x19-x29, TPIDR_EL0, CONTEXTIDR_EL1과 last를 한 checkpoint에서 비교한다.TIF_NEED_FPU_LOAD를 기록한다.04 · SOURCE
Linux 6.18.37 원본 코드와 줄별 설명
소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.
arm64 · Linux 6.18.37
C hook 뒤 cpu_switch_to가 x19-x29, SP와 LR을 교체
__switch_to()는 FPSIMD, TLS, hw breakpoint, contextidr, entry task와 MTE state를 순서대로 갱신한 뒤 assembly switch로 내려간다. x18 platform register와 PAC/SCS 설정도 build option에 따라 경계에 포함된다.
원본 코드: arch/arm64/kernel/process.c:734-784
734 /* ISB required for the kernel uaccess routines when setting TCF0. */
735 isb();
736}
737
738/*
739 * Thread switching.
740 */
741__notrace_funcgraph __sched
742struct task_struct *__switch_to(struct task_struct *prev,
743 struct task_struct *next)
744{
745 struct task_struct *last;
746
747 fpsimd_thread_switch(next);
748 tls_thread_switch(next);
749 hw_breakpoint_thread_switch(next);
750 contextidr_thread_switch(next);
751 entry_task_switch(next);
752 ssbs_thread_switch(next);
753 cntkctl_thread_switch(prev, next);
754 ptrauth_thread_switch_user(next);
755 permission_overlay_switch(next);
756 gcs_thread_switch(next);
757
758 /*
759 * Complete any pending TLB or cache maintenance on this CPU in case the
760 * thread migrates to a different CPU. This full barrier is also
761 * required by the membarrier system call. Additionally it makes any
762 * in-progress pgtable writes visible to the table walker; See
763 * emit_pte_barriers().
764 */
765 dsb(ish);
766
767 /*
768 * MTE thread switching must happen after the DSB above to ensure that
769 * any asynchronous tag check faults have been logged in the TFSR*_EL1
770 * registers.
771 */
772 mte_thread_switch(next);
773 /* avoid expensive SCTLR_EL1 accesses if no change */
774 if (prev->thread.sctlr_user != next->thread.sctlr_user)
775 update_sctlr_el1(next->thread.sctlr_user);
776
777 /* the actual thread switch */
778 last = cpu_switch_to(prev, next);
779
780 return last;
781}
782
783struct wchan_info {
784 unsigned long pc;라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 51개 줄에 각각 설명을 붙였습니다.
/* ISB required for the kernel uaccess routines when setting TCF0. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
isb();memory, translation 또는 instruction-fetch 관찰 순서를 확정하는 architecture 명령이다. 앞에서 publish한 상태와 뒤에서 재사용하는 상태의 경계를 이 줄에 둔다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Thread switching.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
__notrace_funcgraph __sched선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
struct task_struct *__switch_to(struct task_struct *prev,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
struct task_struct *next)이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct task_struct *last;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
fpsimd_thread_switch(next);lazy FPSIMD ownership을 next 기준으로 바꾸는 첫 extended-state 경계다.
tls_thread_switch(next);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
hw_breakpoint_thread_switch(next);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
contextidr_thread_switch(next);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
entry_task_switch(next);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
ssbs_thread_switch(next);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
cntkctl_thread_switch(prev, next);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
ptrauth_thread_switch_user(next);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
permission_overlay_switch(next);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
gcs_thread_switch(next);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Complete any pending TLB or cache maintenance on this CPU in case theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* thread migrates to a different CPU. This full barrier is alsoLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* required by the membarrier system call. Additionally it makes anyLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* in-progress pgtable writes visible to the table walker; SeeLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* emit_pte_barriers().Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
dsb(ish);memory, translation 또는 instruction-fetch 관찰 순서를 확정하는 architecture 명령이다. 앞에서 publish한 상태와 뒤에서 재사용하는 상태의 경계를 이 줄에 둔다.
(blank)빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* MTE thread switching must happen after the DSB above to ensure thatLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* any asynchronous tag check faults have been logged in the TFSR*_EL1Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* registers.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
mte_thread_switch(next);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
/* avoid expensive SCTLR_EL1 accesses if no change */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (prev->thread.sctlr_user != next->thread.sctlr_user)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
update_sctlr_el1(next->thread.sctlr_user);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* the actual thread switch */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
last = cpu_switch_to(prev, next);assembly가 callee-saved register와 SP를 실제로 교체하며 이후 실행 stack의 소유자가 바뀐다.
(blank)빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return last;이 함수가 Context switch: callee-saved register와 task state 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
struct wchan_info {이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned long pc;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
x86-64 · Linux 6.18.37
inactive_task_frame과 per-CPU/TSS state를 교체
__switch_to()는 FPU lazy load 표시, FS/GS segment와 base, TLS array, TSS.sp0, IO bitmap, PKRU와 speculation control을 처리한다. 일반 GPR 대부분은 상위 switch assembly의 inactive frame이 담당한다.
원본 코드: arch/x86/kernel/process_64.c:601-673
601 *
602 * This could still be optimized:
603 * - fold all the options into a flag word and test it with a single test.
604 * - could test fs/gs bitsliced
605 *
606 * Kprobes not supported here. Set the probe on schedule instead.
607 * Function graph tracer not supported too.
608 */
609__no_kmsan_checks
610__visible __notrace_funcgraph struct task_struct *
611__switch_to(struct task_struct *prev_p, struct task_struct *next_p)
612{
613 struct thread_struct *prev = &prev_p->thread;
614 struct thread_struct *next = &next_p->thread;
615 int cpu = smp_processor_id();
616
617 WARN_ON_ONCE(IS_ENABLED(CONFIG_DEBUG_ENTRY) &&
618 this_cpu_read(hardirq_stack_inuse));
619
620 switch_fpu(prev_p, cpu);
621
622 /* We must save %fs and %gs before load_TLS() because
623 * %fs and %gs may be cleared by load_TLS().
624 *
625 * (e.g. xen_load_tls())
626 */
627 save_fsgs(prev_p);
628
629 /*
630 * Load TLS before restoring any segments so that segment loads
631 * reference the correct GDT entries.
632 */
633 load_TLS(next, cpu);
634
635 /*
636 * Leave lazy mode, flushing any hypercalls made here. This
637 * must be done after loading TLS entries in the GDT but before
638 * loading segments that might reference them.
639 */
640 arch_end_context_switch(next_p);
641
642 /* Switch DS and ES.
643 *
644 * Reading them only returns the selectors, but writing them (if
645 * nonzero) loads the full descriptor from the GDT or LDT. The
646 * LDT for next is loaded in switch_mm, and the GDT is loaded
647 * above.
648 *
649 * We therefore need to write new values to the segment
650 * registers on every context switch unless both the new and old
651 * values are zero.
652 *
653 * Note that we don't need to do anything for CS and SS, as
654 * those are saved and restored as part of pt_regs.
655 */
656 savesegment(es, prev->es);
657 if (unlikely(next->es | prev->es))
658 loadsegment(es, next->es);
659
660 savesegment(ds, prev->ds);
661 if (unlikely(next->ds | prev->ds))
662 loadsegment(ds, next->ds);
663
664 x86_fsgsbase_load(prev, next);
665
666 x86_pkru_load(prev, next);
667
668 /*
669 * Switch the PDA and FPU contexts.
670 */
671 raw_cpu_write(current_task, next_p);
672 raw_cpu_write(cpu_current_top_of_stack, task_top_of_stack(next_p));
673 라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 73개 줄에 각각 설명을 붙였습니다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* This could still be optimized:Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* - fold all the options into a flag word and test it with a single test.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* - could test fs/gs bitslicedLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Kprobes not supported here. Set the probe on schedule instead.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Function graph tracer not supported too.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
__no_kmsan_checks선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
__visible __notrace_funcgraph struct task_struct *선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
__switch_to(struct task_struct *prev_p, struct task_struct *next_p)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct thread_struct *prev = &prev_p->thread;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
struct thread_struct *next = &next_p->thread;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
int cpu = smp_processor_id();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
WARN_ON_ONCE(IS_ENABLED(CONFIG_DEBUG_ENTRY) &&불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.
this_cpu_read(hardirq_stack_inuse));helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
switch_fpu(prev_p, cpu);prev의 live xstate ownership을 정리하고 next user return에 필요한 lazy-load flag를 만든다.
(blank)빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* We must save %fs and %gs before load_TLS() becauseLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* %fs and %gs may be cleared by load_TLS().Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* (e.g. xen_load_tls())Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
save_fsgs(prev_p);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Load TLS before restoring any segments so that segment loadsLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* reference the correct GDT entries.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
load_TLS(next, cpu);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Leave lazy mode, flushing any hypercalls made here. ThisLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* must be done after loading TLS entries in the GDT but beforeLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* loading segments that might reference them.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
arch_end_context_switch(next_p);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Switch DS and ES.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Reading them only returns the selectors, but writing them (ifLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* nonzero) loads the full descriptor from the GDT or LDT. TheLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* LDT for next is loaded in switch_mm, and the GDT is loadedLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* above.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* We therefore need to write new values to the segmentLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* registers on every context switch unless both the new and oldLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* values are zero.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Note that we don't need to do anything for CS and SS, asLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* those are saved and restored as part of pt_regs.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
savesegment(es, prev->es);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (unlikely(next->es | prev->es))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
loadsegment(es, next->es);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
savesegment(ds, prev->ds);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (unlikely(next->ds | prev->ds))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
loadsegment(ds, next->ds);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
x86_fsgsbase_load(prev, next);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
x86_pkru_load(prev, next);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Switch the PDA and FPU contexts.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
raw_cpu_write(current_task, next_p);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
raw_cpu_write(cpu_current_top_of_stack, task_top_of_stack(next_p));helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
RISC-V · Linux 6.18.37
assembly가 ra, sp와 s0-s11을 thread_struct에 저장
__switch_to는 prev의 callee-saved integer register를 저장하고 next 값을 복원한 뒤 tp를 next task로 바꾼다. FPU/vector는 switch macro의 별도 helper가 status dirty bit를 보고 처리한다.
원본 코드: arch/riscv/kernel/entry.S:380-440
380 * a0: previous task_struct (must be preserved across the switch)
381 * a1: next task_struct
382 *
383 * The value of a0 and a1 must be preserved by this function, as that's how
384 * arguments are passed to schedule_tail.
385 */
386SYM_FUNC_START(__switch_to)
387 /* Save context into prev->thread */
388 li a4, TASK_THREAD_RA
389 add a3, a0, a4
390 add a4, a1, a4
391 REG_S ra, TASK_THREAD_RA_RA(a3)
392 REG_S sp, TASK_THREAD_SP_RA(a3)
393 REG_S s0, TASK_THREAD_S0_RA(a3)
394 REG_S s1, TASK_THREAD_S1_RA(a3)
395 REG_S s2, TASK_THREAD_S2_RA(a3)
396 REG_S s3, TASK_THREAD_S3_RA(a3)
397 REG_S s4, TASK_THREAD_S4_RA(a3)
398 REG_S s5, TASK_THREAD_S5_RA(a3)
399 REG_S s6, TASK_THREAD_S6_RA(a3)
400 REG_S s7, TASK_THREAD_S7_RA(a3)
401 REG_S s8, TASK_THREAD_S8_RA(a3)
402 REG_S s9, TASK_THREAD_S9_RA(a3)
403 REG_S s10, TASK_THREAD_S10_RA(a3)
404 REG_S s11, TASK_THREAD_S11_RA(a3)
405
406 /* save the user space access flag */
407 csrr s0, CSR_STATUS
408 REG_S s0, TASK_THREAD_SUM_RA(a3)
409
410 /* Save the kernel shadow call stack pointer */
411 scs_save_current
412 /* Restore context from next->thread */
413 REG_L s0, TASK_THREAD_SUM_RA(a4)
414 li s1, SR_SUM
415 and s0, s0, s1
416 csrs CSR_STATUS, s0
417 REG_L ra, TASK_THREAD_RA_RA(a4)
418 REG_L sp, TASK_THREAD_SP_RA(a4)
419 REG_L s0, TASK_THREAD_S0_RA(a4)
420 REG_L s1, TASK_THREAD_S1_RA(a4)
421 REG_L s2, TASK_THREAD_S2_RA(a4)
422 REG_L s3, TASK_THREAD_S3_RA(a4)
423 REG_L s4, TASK_THREAD_S4_RA(a4)
424 REG_L s5, TASK_THREAD_S5_RA(a4)
425 REG_L s6, TASK_THREAD_S6_RA(a4)
426 REG_L s7, TASK_THREAD_S7_RA(a4)
427 REG_L s8, TASK_THREAD_S8_RA(a4)
428 REG_L s9, TASK_THREAD_S9_RA(a4)
429 REG_L s10, TASK_THREAD_S10_RA(a4)
430 REG_L s11, TASK_THREAD_S11_RA(a4)
431 /* The offset of thread_info in task_struct is zero. */
432 move tp, a1
433 /* Switch to the next shadow call stack */
434 scs_load_current
435 ret
436SYM_FUNC_END(__switch_to)
437
438#ifndef CONFIG_MMU
439#define do_page_fault do_trap_unknown
440#endif라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 61개 줄에 각각 설명을 붙였습니다.
* a0: previous task_struct (must be preserved across the switch)Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* a1: next task_structLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The value of a0 and a1 must be preserved by this function, as that's howLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* arguments are passed to schedule_tail.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
SYM_FUNC_START(__switch_to)assembler/linker가 symbol, section과 정렬을 정의한다. runtime instruction은 아니지만 entry 주소와 relocation 가능한 범위를 결정한다.
/* Save context into prev->thread */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
li a4, TASK_THREAD_RA이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
add a3, a0, a4이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
add a4, a1, a4이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
REG_S ra, TASK_THREAD_RA_RA(a3)prev의 복귀 PC를 thread_struct의 RA slot에 저장한다.
REG_S sp, TASK_THREAD_SP_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s0, TASK_THREAD_S0_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s1, TASK_THREAD_S1_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s2, TASK_THREAD_S2_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s3, TASK_THREAD_S3_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s4, TASK_THREAD_S4_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s5, TASK_THREAD_S5_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s6, TASK_THREAD_S6_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s7, TASK_THREAD_S7_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s8, TASK_THREAD_S8_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s9, TASK_THREAD_S9_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s10, TASK_THREAD_S10_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s11, TASK_THREAD_S11_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* save the user space access flag */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
csrr s0, CSR_STATUSprivileged system register를 읽거나 쓴다. 일반 변수와 달리 write side effect, privilege level과 serialization 조건을 함께 확인한다.
REG_S s0, TASK_THREAD_SUM_RA(a3)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Save the kernel shadow call stack pointer */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
scs_save_current선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
/* Restore context from next->thread */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
REG_L s0, TASK_THREAD_SUM_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
li s1, SR_SUM이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
and s0, s0, s1이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
csrs CSR_STATUS, s0이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
REG_L ra, TASK_THREAD_RA_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L sp, TASK_THREAD_SP_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s0, TASK_THREAD_S0_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s1, TASK_THREAD_S1_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s2, TASK_THREAD_S2_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s3, TASK_THREAD_S3_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s4, TASK_THREAD_S4_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s5, TASK_THREAD_S5_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s6, TASK_THREAD_S6_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s7, TASK_THREAD_S7_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s8, TASK_THREAD_S8_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s9, TASK_THREAD_S9_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s10, TASK_THREAD_S10_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L s11, TASK_THREAD_S11_RA(a4)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
/* The offset of thread_info in task_struct is zero. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
move tp, a1이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
/* Switch to the next shadow call stack */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
scs_load_current선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
ret저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
SYM_FUNC_END(__switch_to)assembler/linker가 symbol, section과 정렬을 정의한다. runtime instruction은 아니지만 entry 주소와 relocation 가능한 범위를 결정한다.
(blank)빈 줄은 RISC-V Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifndef CONFIG_MMUKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define do_page_fault do_trap_unknowncompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
05 · WORKED EXAMPLE
숫자로 검산하기
두 task의 stack과 복귀 PC를 수치로 검산
prev SP=0xffff80001234fe00, next->thread.sp=0xffff80004567fdc0, next->thread.pc=0xffff800080123400이라고 가정한다.
- saveprev SP와 callee-saved register가 prev->thread 또는 inactive frame 범위에 기록되는지 확인한다.
- restoreCPU SP가 0xffff80004567fdc0으로 바뀐 뒤 모든 local access는 next stack 범위여야 한다.
- resumenext 복귀 PC 0xffff800080123400이 text mapping에 있고 shadow stack/PAC/ORC 조건을 만족하는지 확인한다.
- ownershipper-CPU current, address-space root와 TLS가 모두 next를 가리킬 때 switch 완료로 판정한다.
결론PC만 next code에 있어도 TSS/TLS/MMU가 prev 상태면 완료가 아니다. 최소 SP, current, mm, TLS 네 축을 묶어 판정한다.
06 · DEEP DIVE
경계별 상세 분석
공통 kernel core와 architecture hook의 경계
scheduler core는 prev와 next를 선택하지만 실제 SP, callee-saved register, TLS, address-space와 architecture control register의 저장 형식은 switch_to hook에 맡긴다. 반환값 last는 단순한 prev pointer가 아니라 switch를 거쳐 다시 실행된 task가 관찰하는 control-flow 계약이다.
공통 prepare_task_switch()와 finish_task_switch() 사이에서 architecture code가 실행된다. interrupt-disabled 여부, runqueue lock 인계와 stack lifetime을 함께 보지 않으면 함수 호출처럼 보이는 비선형 실행을 잘못 해석한다.
arm64: C hook 뒤 cpu_switch_to가 x19-x29, SP와 LR을 교체
__switch_to()는 FPSIMD, TLS, hw breakpoint, contextidr, entry task와 MTE state를 순서대로 갱신한 뒤 assembly switch로 내려간다. x18 platform register와 PAC/SCS 설정도 build option에 따라 경계에 포함된다.
새 task state가 CPU에 보이기 전에 pending asynchronous MTE fault와 user register state를 정리한다. 디버깅할 때는 prev/next, SP, x19-x29, TPIDR_EL0, CONTEXTIDR_EL1과 last를 한 checkpoint에서 비교한다.
x86-64: inactive_task_frame과 per-CPU/TSS state를 교체
__switch_to()는 FPU lazy load 표시, FS/GS segment와 base, TLS array, TSS.sp0, IO bitmap, PKRU와 speculation control을 처리한다. 일반 GPR 대부분은 상위 switch assembly의 inactive frame이 담당한다.
FS/GS base write와 per-CPU current 갱신, TSS stack 게시 순서는 NMI와 user return이 중간 상태를 보지 않게 해야 한다. 디버깅할 때는 RSP, FSBASE, GSBASE, TSS.sp0, PKRU, debug register와 TIF_NEED_FPU_LOAD를 기록한다.
RISC-V: assembly가 ra, sp와 s0-s11을 thread_struct에 저장
__switch_to는 prev의 callee-saved integer register를 저장하고 next 값을 복원한 뒤 tp를 next task로 바꾼다. FPU/vector는 switch macro의 별도 helper가 status dirty bit를 보고 처리한다.
저장과 복원 사이에 tp가 바뀌므로 per-task field 접근이 어느 task 기준인지 instruction 순서로 확인해야 한다. 디버깅할 때는 TASK_THREAD_* generated offset, ra, sp, s0-s11, tp와 task kernel stack 범위를 대조한다.
객체 수명과 소유권을 먼저 고정한다
prev의 live register는 prev->thread와 prev kernel stack이 소유하고, next의 저장 state는 restore 직후 CPU register가 소유한다. switch가 완료된 뒤 old stack의 local pointer를 다른 CPU가 임의로 사용해서는 안 된다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
latency upper bound는 hardware instruction 하나가 아니다
Tswitch = Tmm + Tcallee + Ttls + Textended + Tmitigation으로 나눈다. x86의 speculation mitigation과 debug register, arm64의 TLS/MTE, RISC-V의 envcfg/vector 조건이 config에 따라 긴 tail을 만든다.
평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.
07 · FAILURE
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | TLS 또는 contextidr가 prev 값으로 남아 user return 직후 다른 task의 상태를 관찰한다. | prev/next, SP, x19-x29, TPIDR_EL0, CONTEXTIDR_EL1과 last를 한 checkpoint에서 비교한다. |
| x86-64 | GS base 또는 TSS.sp0가 prev task 값이면 다음 user exception이 잘못된 kernel stack이나 TLS로 진입한다. | RSP, FSBASE, GSBASE, TSS.sp0, PKRU, debug register와 TIF_NEED_FPU_LOAD를 기록한다. |
| RISC-V | s-register 한 칸의 asm-offset 불일치가 return address나 frame pointer를 다른 field로 복원한다. | TASK_THREAD_* generated offset, ra, sp, s0-s11, tp와 task kernel stack 범위를 대조한다. |
08 · LAB
재현과 계측 절차
sched_switchtracepoint와 architecture switch function graph를 동시에 수집해 core와 hook 시간을 분리한다.- GDB에서 prev/next의 thread_struct와 switch 직후 CPU register를 대조한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/kernel/process.c:734-784
- x86-64arch/x86/kernel/process_64.c:601-673
- RISC-Varch/riscv/kernel/entry.S:380-440
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.