← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 01/21

Context switch: callee-saved register와 task state

scheduler의 공통 context_switch() 아래에서 arm64, x86-64, RISC-V가 어떤 register와 thread state를 저장하고 다음 task에 넘기는지 읽습니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
3 files · 185 annotated lines
기준 tag
Linux v6.18.37
분석 축
state · ordering · lifetime · latency

01 · QUESTION

무엇을 확인할 것인가

CPU 하나의 실행 소유권이 prev task에서 next task로 넘어갔다고 판정하려면 어떤 state가 함께 바뀌어야 하는가?

scheduler core는 prevnext를 선택하지만 실제 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실패 형태확인할 상태
arm64C 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-64inactive_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-Vassembly가 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

세 그림으로 먼저 읽기

그림 1. 같은 목적, 서로 다른 mechanism각 ISA에서 실제로 추적할 state와 checkpoint를 한 줄에 맞췄습니다.

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 범위를 대조한다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contractscheduler core는 prevnext를 선택하지만 실제 SP, callee-saved register, TLS, address-space와 architecture control register의 저장 형식은 switch_to hook에 맡긴다. 반환값 last는 단순한 prev pointer가 아니라 switch를 거쳐 다시 실행된 task가 관찰하는 control-flow 계약이다.
arm64C hook 뒤 cpu_switch_to가 x19-x29, SP와 LR을 교체새 task state가 CPU에 보이기 전에 pending asynchronous MTE fault와 user register state를 정리한다.
x86-64inactive_task_frame과 per-CPU/TSS state를 교체FS/GS base write와 per-CPU current 갱신, TSS stack 게시 순서는 NMI와 user return이 중간 상태를 보지 않게 해야 한다.
RISC-Vassembly가 ra, sp와 s0-s11을 thread_struct에 저장저장과 복원 사이에 tp가 바뀌므로 per-task field 접근이 어느 task 기준인지 instruction 순서로 확인해야 한다.
lifetime boundaryprev의 live register는 prev->thread와 prev kernel stack이 소유하고, next의 저장 state는 restore 직후 CPU register가 소유한다. switch가 완료된 뒤 old stack의 local pointer를 다른 CPU가 임의로 사용해서는 안 된다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비새 task state가 CPU에 보이기 전에 pending asynchronous MTE fault와 user register state를 정리한다.관찰: prev/next, SP, x19-x29, TPIDR_EL0, CONTEXTIDR_EL1과 last를 한 checkpoint에서 비교한다.
x86-64state 준비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-Vstate 준비저장과 복원 사이에 tp가 바뀌므로 per-task field 접근이 어느 task 기준인지 instruction 순서로 확인해야 한다.관찰: TASK_THREAD_* generated offset, ra, sp, s0-s11, tp와 task kernel stack 범위를 대조한다.

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개 줄에 각각 설명을 붙였습니다.

L734 /* ISB required for the kernel uaccess routines when setting TCF0. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L735 isb();

memory, translation 또는 instruction-fetch 관찰 순서를 확정하는 architecture 명령이다. 앞에서 publish한 상태와 뒤에서 재사용하는 상태의 경계를 이 줄에 둔다.

L736}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L737(blank)

빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L738/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L739 * Thread switching.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L740 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L741__notrace_funcgraph __sched

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L742struct task_struct *__switch_to(struct task_struct *prev,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L743 struct task_struct *next)

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L744{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L745 struct task_struct *last;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L746(blank)

빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L747 fpsimd_thread_switch(next);

lazy FPSIMD ownership을 next 기준으로 바꾸는 첫 extended-state 경계다.

L748 tls_thread_switch(next);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L749 hw_breakpoint_thread_switch(next);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L750 contextidr_thread_switch(next);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L751 entry_task_switch(next);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L752 ssbs_thread_switch(next);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L753 cntkctl_thread_switch(prev, next);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L754 ptrauth_thread_switch_user(next);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L755 permission_overlay_switch(next);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L756 gcs_thread_switch(next);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L757(blank)

빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L758 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L759 * Complete any pending TLB or cache maintenance on this CPU in case the

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L760 * thread migrates to a different CPU. This full barrier is also

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L761 * required by the membarrier system call. Additionally it makes any

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L762 * in-progress pgtable writes visible to the table walker; See

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L763 * emit_pte_barriers().

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L764 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L765 dsb(ish);

memory, translation 또는 instruction-fetch 관찰 순서를 확정하는 architecture 명령이다. 앞에서 publish한 상태와 뒤에서 재사용하는 상태의 경계를 이 줄에 둔다.

L766(blank)

빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L767 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L768 * MTE thread switching must happen after the DSB above to ensure that

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L769 * any asynchronous tag check faults have been logged in the TFSR*_EL1

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L770 * registers.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L771 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L772 mte_thread_switch(next);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L773 /* avoid expensive SCTLR_EL1 accesses if no change */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L774 if (prev->thread.sctlr_user != next->thread.sctlr_user)

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L775 update_sctlr_el1(next->thread.sctlr_user);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L776(blank)

빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L777 /* the actual thread switch */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L778 last = cpu_switch_to(prev, next);

assembly가 callee-saved register와 SP를 실제로 교체하며 이후 실행 stack의 소유자가 바뀐다.

L779(blank)

빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L780 return last;

이 함수가 Context switch: callee-saved register와 task state 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L781}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L782(blank)

빈 줄은 arm64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L783struct wchan_info {

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L784 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개 줄에 각각 설명을 붙였습니다.

L601 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L602 * This could still be optimized:

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L603 * - fold all the options into a flag word and test it with a single test.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L604 * - could test fs/gs bitsliced

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L605 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L606 * Kprobes not supported here. Set the probe on schedule instead.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L607 * Function graph tracer not supported too.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L608 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L609__no_kmsan_checks

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L610__visible __notrace_funcgraph struct task_struct *

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L611__switch_to(struct task_struct *prev_p, struct task_struct *next_p)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L612{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L613 struct thread_struct *prev = &prev_p->thread;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L614 struct thread_struct *next = &next_p->thread;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L615 int cpu = smp_processor_id();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L616(blank)

빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L617 WARN_ON_ONCE(IS_ENABLED(CONFIG_DEBUG_ENTRY) &&

불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.

L618 this_cpu_read(hardirq_stack_inuse));

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L619(blank)

빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L620 switch_fpu(prev_p, cpu);

prev의 live xstate ownership을 정리하고 next user return에 필요한 lazy-load flag를 만든다.

L621(blank)

빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L622 /* We must save %fs and %gs before load_TLS() because

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L623 * %fs and %gs may be cleared by load_TLS().

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L624 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L625 * (e.g. xen_load_tls())

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L626 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L627 save_fsgs(prev_p);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L628(blank)

빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L629 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L630 * Load TLS before restoring any segments so that segment loads

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L631 * reference the correct GDT entries.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L632 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L633 load_TLS(next, cpu);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L634(blank)

빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L635 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L636 * Leave lazy mode, flushing any hypercalls made here. This

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L637 * must be done after loading TLS entries in the GDT but before

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L638 * loading segments that might reference them.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L639 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L640 arch_end_context_switch(next_p);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L641(blank)

빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L642 /* Switch DS and ES.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L643 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L644 * Reading them only returns the selectors, but writing them (if

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L645 * nonzero) loads the full descriptor from the GDT or LDT. The

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L646 * LDT for next is loaded in switch_mm, and the GDT is loaded

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L647 * above.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L648 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L649 * We therefore need to write new values to the segment

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L650 * registers on every context switch unless both the new and old

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L651 * values are zero.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L652 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L653 * Note that we don't need to do anything for CS and SS, as

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L654 * those are saved and restored as part of pt_regs.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L655 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L656 savesegment(es, prev->es);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L657 if (unlikely(next->es | prev->es))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L658 loadsegment(es, next->es);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L659(blank)

빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L660 savesegment(ds, prev->ds);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L661 if (unlikely(next->ds | prev->ds))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L662 loadsegment(ds, next->ds);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L663(blank)

빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L664 x86_fsgsbase_load(prev, next);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L665(blank)

빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L666 x86_pkru_load(prev, next);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L667(blank)

빈 줄은 x86-64 Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L668 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L669 * Switch the PDA and FPU contexts.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L670 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L671 raw_cpu_write(current_task, next_p);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L672 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를 연결해 본다.

L673(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개 줄에 각각 설명을 붙였습니다.

L380 * a0: previous task_struct (must be preserved across the switch)

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L381 * a1: next task_struct

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L382 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L383 * The value of a0 and a1 must be preserved by this function, as that's how

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L384 * arguments are passed to schedule_tail.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L385 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L386SYM_FUNC_START(__switch_to)

assembler/linker가 symbol, section과 정렬을 정의한다. runtime instruction은 아니지만 entry 주소와 relocation 가능한 범위를 결정한다.

L387 /* Save context into prev->thread */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L388 li a4, TASK_THREAD_RA

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L389 add a3, a0, a4

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L390 add a4, a1, a4

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L391 REG_S ra, TASK_THREAD_RA_RA(a3)

prev의 복귀 PC를 thread_struct의 RA slot에 저장한다.

L392 REG_S sp, TASK_THREAD_SP_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L393 REG_S s0, TASK_THREAD_S0_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L394 REG_S s1, TASK_THREAD_S1_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L395 REG_S s2, TASK_THREAD_S2_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L396 REG_S s3, TASK_THREAD_S3_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L397 REG_S s4, TASK_THREAD_S4_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L398 REG_S s5, TASK_THREAD_S5_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L399 REG_S s6, TASK_THREAD_S6_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L400 REG_S s7, TASK_THREAD_S7_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L401 REG_S s8, TASK_THREAD_S8_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L402 REG_S s9, TASK_THREAD_S9_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L403 REG_S s10, TASK_THREAD_S10_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L404 REG_S s11, TASK_THREAD_S11_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L405(blank)

빈 줄은 RISC-V Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L406 /* save the user space access flag */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L407 csrr s0, CSR_STATUS

privileged system register를 읽거나 쓴다. 일반 변수와 달리 write side effect, privilege level과 serialization 조건을 함께 확인한다.

L408 REG_S s0, TASK_THREAD_SUM_RA(a3)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L409(blank)

빈 줄은 RISC-V Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L410 /* Save the kernel shadow call stack pointer */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L411 scs_save_current

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L412 /* Restore context from next->thread */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L413 REG_L s0, TASK_THREAD_SUM_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L414 li s1, SR_SUM

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L415 and s0, s0, s1

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L416 csrs CSR_STATUS, s0

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L417 REG_L ra, TASK_THREAD_RA_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L418 REG_L sp, TASK_THREAD_SP_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L419 REG_L s0, TASK_THREAD_S0_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L420 REG_L s1, TASK_THREAD_S1_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L421 REG_L s2, TASK_THREAD_S2_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L422 REG_L s3, TASK_THREAD_S3_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L423 REG_L s4, TASK_THREAD_S4_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L424 REG_L s5, TASK_THREAD_S5_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L425 REG_L s6, TASK_THREAD_S6_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L426 REG_L s7, TASK_THREAD_S7_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L427 REG_L s8, TASK_THREAD_S8_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L428 REG_L s9, TASK_THREAD_S9_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L429 REG_L s10, TASK_THREAD_S10_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L430 REG_L s11, TASK_THREAD_S11_RA(a4)

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L431 /* The offset of thread_info in task_struct is zero. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L432 move tp, a1

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Context switch: callee-saved register와 task state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L433 /* Switch to the next shadow call stack */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L434 scs_load_current

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L435 ret

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L436SYM_FUNC_END(__switch_to)

assembler/linker가 symbol, section과 정렬을 정의한다. runtime instruction은 아니지만 entry 주소와 relocation 가능한 범위를 결정한다.

L437(blank)

빈 줄은 RISC-V Context switch: callee-saved register와 task state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L438#ifndef CONFIG_MMU

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L439#define do_page_fault do_trap_unknown

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L440#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

두 task의 stack과 복귀 PC를 수치로 검산

prev SP=0xffff80001234fe00, next->thread.sp=0xffff80004567fdc0, next->thread.pc=0xffff800080123400이라고 가정한다.

  1. saveprev SP와 callee-saved register가 prev->thread 또는 inactive frame 범위에 기록되는지 확인한다.
  2. restoreCPU SP가 0xffff80004567fdc0으로 바뀐 뒤 모든 local access는 next stack 범위여야 한다.
  3. resumenext 복귀 PC 0xffff800080123400이 text mapping에 있고 shadow stack/PAC/ORC 조건을 만족하는지 확인한다.
  4. ownershipper-CPU current, address-space root와 TLS가 모두 next를 가리킬 때 switch 완료로 판정한다.

결론PC만 next code에 있어도 TSS/TLS/MMU가 prev 상태면 완료가 아니다. 최소 SP, current, mm, TLS 네 축을 묶어 판정한다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 kernel core와 architecture hook의 경계

scheduler core는 prevnext를 선택하지만 실제 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을 함께 보지 않으면 함수 호출처럼 보이는 비선형 실행을 잘못 해석한다.

02

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에서 비교한다.

03

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를 기록한다.

04

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 범위를 대조한다.

05

객체 수명과 소유권을 먼저 고정한다

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와 폐기 지점을 같은 표에 기록한다.

06

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

실패를 어떤 증거로 나눌 것인가

분류관찰되는 결과첫 확인값
arm64TLS 또는 contextidr가 prev 값으로 남아 user return 직후 다른 task의 상태를 관찰한다.prev/next, SP, x19-x29, TPIDR_EL0, CONTEXTIDR_EL1과 last를 한 checkpoint에서 비교한다.
x86-64GS base 또는 TSS.sp0가 prev task 값이면 다음 user exception이 잘못된 kernel stack이나 TLS로 진입한다.RSP, FSBASE, GSBASE, TSS.sp0, PKRU, debug register와 TIF_NEED_FPU_LOAD를 기록한다.
RISC-Vs-register 한 칸의 asm-offset 불일치가 return address나 frame pointer를 다른 field로 복원한다.TASK_THREAD_* generated offset, ra, sp, s0-s11, tp와 task kernel stack 범위를 대조한다.

08 · LAB

재현과 계측 절차

  1. sched_switch tracepoint와 architecture switch function graph를 동시에 수집해 core와 hook 시간을 분리한다.
  2. GDB에서 prev/next의 thread_struct와 switch 직후 CPU register를 대조한다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinuxobjdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 좌표

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.