← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 02/21

FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state

integer context보다 훨씬 큰 extended register state를 언제 저장하고 언제 복원하는지, lazy ownership과 signal frame까지 연결합니다.

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

01 · QUESTION

무엇을 확인할 것인가

task가 vector instruction을 사용하지 않았을 때 수천 byte state copy를 피하면서도 다른 task의 register가 새지 않게 하는 방법은 무엇인가?

extended state는 항상 switch 때 전부 복사하지 않는다. CPU register의 현재 owner, task memory image의 최신성, user return 전에 restore가 필요한지 나타내는 flag를 조합해 eager와 lazy 경로를 선택한다.

context switch, kernel-mode vector 사용, signal delivery, ptrace와 CPU migration이 같은 state를 만진다. 각 경로가 memory copy를 소유하는지 또는 단지 dirty/foreign flag만 바꾸는지 구분한다.

지연 시간 관점state 크기는 arm64 SVE VL/SME ZA, x86 XFEATURE mask와 RISC-V VLEN에 따라 달라진다. Tsave = bytes / memory_bandwidth + serialization에 migration과 allocation 비용이 추가된다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64TIF_FOREIGN_FPSTATE와 per-CPU last state로 FPSIMD ownership 관리VL 변경이나 CPU migration 뒤 foreign flag가 빠지면 이전 CPU의 vector state를 user가 관찰한다.VL, SVCR, TIF_SVE/TIF_SME, per-CPU fpsimd_last_state와 state buffer size를 확인한다.
x86-64XSAVE memory image와 TIF_NEED_FPU_LOAD의 lazy restoreXFEATURE mask/size가 맞지 않으면 XRSTOR fault 또는 보안 state 유출이 발생한다.XCR0, XFD, xfeatures mask, fpstate size, last_cpu와 TIF_NEED_FPU_LOAD를 기록한다.
RISC-Vsstatus.FS/VS dirty bit와 VLEN별 vector bufferVS DIRTY를 보지 않고 switch하면 next가 prev vector register를 관찰하거나 prev 계산 결과가 사라진다.sstatus.FS/VS, VLENB, vtype/vl, datap pointer와 kernel_vstate nesting depth를 확인한다.

03 · DIAGRAMS

세 그림으로 먼저 읽기

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

arm64

mechanism
TIF_FOREIGN_FPSTATE와 per-CPU last state로 FPSIMD ownership 관리
state
FPSIMD 32x128-bit 외에 SVE의 scalable Z/P/FFR, SME streaming mode와 ZA가 task state를 확장한다. fpsimd_thread_switch()는 next state가 CPU에 이미 live한지 판단해 user return restore 여부를 표시한다.
checkpoint
VL, SVCR, TIF_SVE/TIF_SME, per-CPU fpsimd_last_state와 state buffer size를 확인한다.

x86-64

mechanism
XSAVE memory image와 TIF_NEED_FPU_LOAD의 lazy restore
state
x87, SSE, AVX, AVX-512, PKRU, AMX tile처럼 CPUID가 열거한 XFEATURE를 xstate buffer가 담는다. switch에서는 prev state를 저장하고 next hardware load는 user return까지 미룰 수 있다.
checkpoint
XCR0, XFD, xfeatures mask, fpstate size, last_cpu와 TIF_NEED_FPU_LOAD를 기록한다.

RISC-V

mechanism
sstatus.FS/VS dirty bit와 VLEN별 vector buffer
state
FS/VS field가 OFF, INITIAL, CLEAN, DIRTY를 나타낸다. vector state는 32개 v register와 vstart, vl, vtype, vcsr를 포함하고 VLEN에 따라 task buffer 크기가 달라진다.
checkpoint
sstatus.FS/VS, VLENB, vtype/vl, datap pointer와 kernel_vstate nesting depth를 확인한다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contractextended state는 항상 switch 때 전부 복사하지 않는다. CPU register의 현재 owner, task memory image의 최신성, user return 전에 restore가 필요한지 나타내는 flag를 조합해 eager와 lazy 경로를 선택한다.
arm64TIF_FOREIGN_FPSTATE와 per-CPU last state로 FPSIMD ownership 관리kernel_neon_begin, preemption disable과 softirq context가 user state 보존 순서를 제약한다.
x86-64XSAVE memory image와 TIF_NEED_FPU_LOAD의 lazy restoreXFD와 fpregs_owner_ctx가 state component 사용과 owner CPU를 추적하며 NMI에서 fpregs_lock 규칙을 지켜야 한다.
RISC-Vsstatus.FS/VS dirty bit와 VLEN별 vector bufferkernel-mode vector nesting과 preemption은 user vector owner를 임시로 밀어내므로 nesting start/end가 restore 책임을 가진다.
lifetime boundarylive hardware register와 task의 memory-backed state 중 하나만 최신일 수 있다. owner CPU가 바뀌거나 signal/ptrace가 state를 읽기 전에는 반드시 memory image를 최신화해야 한다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비kernel_neon_begin, preemption disable과 softirq context가 user state 보존 순서를 제약한다.관찰: VL, SVCR, TIF_SVE/TIF_SME, per-CPU fpsimd_last_state와 state buffer size를 확인한다.
x86-64state 준비XFD와 fpregs_owner_ctx가 state component 사용과 owner CPU를 추적하며 NMI에서 fpregs_lock 규칙을 지켜야 한다.관찰: XCR0, XFD, xfeatures mask, fpstate size, last_cpu와 TIF_NEED_FPU_LOAD를 기록한다.
RISC-Vstate 준비kernel-mode vector nesting과 preemption은 user vector owner를 임시로 밀어내므로 nesting start/end가 restore 책임을 가진다.관찰: sstatus.FS/VS, VLENB, vtype/vl, datap pointer와 kernel_vstate nesting depth를 확인한다.

04 · SOURCE

Linux 6.18.37 원본 코드와 줄별 설명

소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.

arm64 · Linux 6.18.37

TIF_FOREIGN_FPSTATE와 per-CPU last state로 FPSIMD ownership 관리

FPSIMD 32x128-bit 외에 SVE의 scalable Z/P/FFR, SME streaming mode와 ZA가 task state를 확장한다. fpsimd_thread_switch()는 next state가 CPU에 이미 live한지 판단해 user return restore 여부를 표시한다.

원본 코드: arch/arm64/kernel/fpsimd.c:1604-1668

1604	 * consumption.
1605	 */
1606	if (system_supports_sme())
1607		sme_smstop();
1608 
1609	set_thread_flag(TIF_FOREIGN_FPSTATE);
1610}
1611 
1612void fpsimd_thread_switch(struct task_struct *next)
1613{
1614	bool wrong_task, wrong_cpu;
1615 
1616	if (!system_supports_fpsimd())
1617		return;
1618 
1619	WARN_ON_ONCE(!irqs_disabled());
1620 
1621	/* Save unsaved fpsimd state, if any: */
1622	if (test_thread_flag(TIF_KERNEL_FPSTATE))
1623		fpsimd_save_kernel_state(current);
1624	else
1625		fpsimd_save_user_state();
1626 
1627	if (test_tsk_thread_flag(next, TIF_KERNEL_FPSTATE)) {
1628		fpsimd_flush_cpu_state();
1629		fpsimd_load_kernel_state(next);
1630	} else {
1631		/*
1632		 * Fix up TIF_FOREIGN_FPSTATE to correctly describe next's
1633		 * state.  For kernel threads, FPSIMD registers are never
1634		 * loaded with user mode FPSIMD state and so wrong_task and
1635		 * wrong_cpu will always be true.
1636		 */
1637		wrong_task = __this_cpu_read(fpsimd_last_state.st) !=
1638			&next->thread.uw.fpsimd_state;
1639		wrong_cpu = next->thread.fpsimd_cpu != smp_processor_id();
1640 
1641		update_tsk_thread_flag(next, TIF_FOREIGN_FPSTATE,
1642				       wrong_task || wrong_cpu);
1643	}
1644}
1645 
1646static void fpsimd_flush_thread_vl(enum vec_type type)
1647{
1648	int vl, supported_vl;
1649 
1650	/*
1651	 * Reset the task vector length as required.  This is where we
1652	 * ensure that all user tasks have a valid vector length
1653	 * configured: no kernel task can become a user task without
1654	 * an exec and hence a call to this function.  By the time the
1655	 * first call to this function is made, all early hardware
1656	 * probing is complete, so __sve_default_vl should be valid.
1657	 * If a bug causes this to go wrong, we make some noise and
1658	 * try to fudge thread.sve_vl to a safe value here.
1659	 */
1660	vl = task_get_vl_onexec(current, type);
1661	if (!vl)
1662		vl = get_default_vl(type);
1663 
1664	if (WARN_ON(!sve_vl_valid(vl)))
1665		vl = vl_info[type].min_vl;
1666 
1667	supported_vl = find_supported_vector_length(type, vl);
1668	if (WARN_ON(supported_vl != vl))

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 65개 줄에 각각 설명을 붙였습니다.

L1604 * consumption.

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

L1605 */

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

L1606 if (system_supports_sme())

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

L1607 sme_smstop();

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

L1608(blank)

빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1609 set_thread_flag(TIF_FOREIGN_FPSTATE);

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

L1610}

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

L1611(blank)

빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1612void fpsimd_thread_switch(struct task_struct *next)

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

L1613{

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

L1614 bool wrong_task, wrong_cpu;

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1615(blank)

빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1616 if (!system_supports_fpsimd())

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

L1617 return;

이 함수가 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1618(blank)

빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1619 WARN_ON_ONCE(!irqs_disabled());

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

L1620(blank)

빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1621 /* Save unsaved fpsimd state, if any: */

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

L1622 if (test_thread_flag(TIF_KERNEL_FPSTATE))

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

L1623 fpsimd_save_kernel_state(current);

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

L1624 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L1625 fpsimd_save_user_state();

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

L1626(blank)

빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1627 if (test_tsk_thread_flag(next, TIF_KERNEL_FPSTATE)) {

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

L1628 fpsimd_flush_cpu_state();

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

L1629 fpsimd_load_kernel_state(next);

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

L1630 } else {

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1631 /*

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

L1632 * Fix up TIF_FOREIGN_FPSTATE to correctly describe next's

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

L1633 * state. For kernel threads, FPSIMD registers are never

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

L1634 * loaded with user mode FPSIMD state and so wrong_task and

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

L1635 * wrong_cpu will always be true.

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

L1636 */

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

L1637 wrong_task = __this_cpu_read(fpsimd_last_state.st) !=

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

L1638 &next->thread.uw.fpsimd_state;

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1639 wrong_cpu = next->thread.fpsimd_cpu != smp_processor_id();

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

L1640(blank)

빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1641 update_tsk_thread_flag(next, TIF_FOREIGN_FPSTATE,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1642 wrong_task || wrong_cpu);

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1643 }

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

L1644}

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

L1645(blank)

빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1646static void fpsimd_flush_thread_vl(enum vec_type type)

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

L1647{

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

L1648 int vl, supported_vl;

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1649(blank)

빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1650 /*

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

L1651 * Reset the task vector length as required. This is where we

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

L1652 * ensure that all user tasks have a valid vector length

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

L1653 * configured: no kernel task can become a user task without

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

L1654 * an exec and hence a call to this function. By the time the

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

L1655 * first call to this function is made, all early hardware

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

L1656 * probing is complete, so __sve_default_vl should be valid.

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

L1657 * If a bug causes this to go wrong, we make some noise and

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

L1658 * try to fudge thread.sve_vl to a safe value here.

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

L1659 */

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

L1660 vl = task_get_vl_onexec(current, type);

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

L1661 if (!vl)

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

L1662 vl = get_default_vl(type);

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

L1663(blank)

빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1664 if (WARN_ON(!sve_vl_valid(vl)))

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

L1665 vl = vl_info[type].min_vl;

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

L1666(blank)

빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1667 supported_vl = find_supported_vector_length(type, vl);

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

L1668 if (WARN_ON(supported_vl != vl))

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

x86-64 · Linux 6.18.37

XSAVE memory image와 TIF_NEED_FPU_LOAD의 lazy restore

x87, SSE, AVX, AVX-512, PKRU, AMX tile처럼 CPUID가 열거한 XFEATURE를 xstate buffer가 담는다. switch에서는 prev state를 저장하고 next hardware load는 user return까지 미룰 수 있다.

원본 코드: arch/x86/include/asm/fpu/sched.h:24-56

24 *
25 * Once TIF_NEED_FPU_LOAD is set, it is required to load the
26 * registers before returning to userland or using the content
27 * otherwise.
28 *
29 * The FPU context is only stored/restored for a user task and
30 * PF_KTHREAD is used to distinguish between kernel and user threads.
31 */
32static inline void switch_fpu(struct task_struct *old, int cpu)
33{
34	if (!test_tsk_thread_flag(old, TIF_NEED_FPU_LOAD) &&
35	    cpu_feature_enabled(X86_FEATURE_FPU) &&
36	    !(old->flags & (PF_KTHREAD | PF_USER_WORKER))) {
37		struct fpu *old_fpu = x86_task_fpu(old);
38 
39		set_tsk_thread_flag(old, TIF_NEED_FPU_LOAD);
40		save_fpregs_to_fpstate(old_fpu);
41		/*
42		 * The save operation preserved register state, so the
43		 * fpu_fpregs_owner_ctx is still @old_fpu. Store the
44		 * current CPU number in @old_fpu, so the next return
45		 * to user space can avoid the FPU register restore
46		 * when is returns on the same CPU and still owns the
47		 * context. See fpregs_restore_userregs().
48		 */
49		old_fpu->last_cpu = cpu;
50 
51		trace_x86_fpu_regs_deactivated(old_fpu);
52	}
53}
54 
55#endif /* _ASM_X86_FPU_SCHED_H */
56 

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 33개 줄에 각각 설명을 붙였습니다.

L24 *

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

L25 * Once TIF_NEED_FPU_LOAD is set, it is required to load the

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

L26 * registers before returning to userland or using the content

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

L27 * otherwise.

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

L28 *

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

L29 * The FPU context is only stored/restored for a user task and

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

L30 * PF_KTHREAD is used to distinguish between kernel and user threads.

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

L31 */

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

L32static inline void switch_fpu(struct task_struct *old, int cpu)

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

L33{

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

L34 if (!test_tsk_thread_flag(old, TIF_NEED_FPU_LOAD) &&

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

L35 cpu_feature_enabled(X86_FEATURE_FPU) &&

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L36 !(old->flags & (PF_KTHREAD | PF_USER_WORKER))) {

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L37 struct fpu *old_fpu = x86_task_fpu(old);

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

L38(blank)

빈 줄은 x86-64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L39 set_tsk_thread_flag(old, TIF_NEED_FPU_LOAD);

이 task가 다시 user mode로 갈 때 hardware restore가 필요함을 표시한다.

L40 save_fpregs_to_fpstate(old_fpu);

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

L41 /*

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

L42 * The save operation preserved register state, so the

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

L43 * fpu_fpregs_owner_ctx is still @old_fpu. Store the

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

L44 * current CPU number in @old_fpu, so the next return

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

L45 * to user space can avoid the FPU register restore

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

L46 * when is returns on the same CPU and still owns the

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

L47 * context. See fpregs_restore_userregs().

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

L48 */

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

L49 old_fpu->last_cpu = cpu;

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

L50(blank)

빈 줄은 x86-64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L51 trace_x86_fpu_regs_deactivated(old_fpu);

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

L52 }

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

L53}

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

L54(blank)

빈 줄은 x86-64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L55#endif /* _ASM_X86_FPU_SCHED_H */

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

L56(blank)

빈 줄은 x86-64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

RISC-V · Linux 6.18.37

sstatus.FS/VS dirty bit와 VLEN별 vector buffer

FS/VS field가 OFF, INITIAL, CLEAN, DIRTY를 나타낸다. vector state는 32개 v register와 vstart, vl, vtype, vcsr를 포함하고 VLEN에 따라 task buffer 크기가 달라진다.

원본 코드: arch/riscv/include/asm/vector.h:360-428

360#else /* !CONFIG_RISCV_ISA_V_PREEMPTIVE */
361static inline bool riscv_preempt_v_dirty(struct task_struct *task) { return false; }
362static inline bool riscv_preempt_v_restore(struct task_struct *task) { return false; }
363static inline bool riscv_preempt_v_started(struct task_struct *task) { return false; }
364#define riscv_preempt_v_clear_dirty(tsk)	do {} while (0)
365#define riscv_preempt_v_set_restore(tsk)	do {} while (0)
366#endif /* CONFIG_RISCV_ISA_V_PREEMPTIVE */
367 
368static inline void __switch_to_vector(struct task_struct *prev,
369				      struct task_struct *next)
370{
371	struct pt_regs *regs;
372 
373	if (riscv_preempt_v_started(prev)) {
374		if (riscv_v_is_on()) {
375			WARN_ON(prev->thread.riscv_v_flags & RISCV_V_CTX_DEPTH_MASK);
376			riscv_v_disable();
377			prev->thread.riscv_v_flags |= RISCV_PREEMPT_V_IN_SCHEDULE;
378		}
379		if (riscv_preempt_v_dirty(prev)) {
380			__riscv_v_vstate_save(&prev->thread.kernel_vstate,
381					      prev->thread.kernel_vstate.datap);
382			riscv_preempt_v_clear_dirty(prev);
383		}
384	} else {
385		regs = task_pt_regs(prev);
386		riscv_v_vstate_save(&prev->thread.vstate, regs);
387	}
388 
389	if (riscv_preempt_v_started(next)) {
390		if (next->thread.riscv_v_flags & RISCV_PREEMPT_V_IN_SCHEDULE) {
391			next->thread.riscv_v_flags &= ~RISCV_PREEMPT_V_IN_SCHEDULE;
392			riscv_v_enable();
393		} else {
394			riscv_preempt_v_set_restore(next);
395		}
396	} else {
397		riscv_v_vstate_set_restore(next, task_pt_regs(next));
398	}
399}
400 
401void riscv_v_vstate_ctrl_init(struct task_struct *tsk);
402bool riscv_v_vstate_ctrl_user_allowed(void);
403 
404#else /* ! CONFIG_RISCV_ISA_V  */
405 
406struct pt_regs;
407 
408static inline int riscv_v_setup_vsize(void) { return -EOPNOTSUPP; }
409static __always_inline bool has_vector(void) { return false; }
410static __always_inline bool insn_is_vector(u32 insn_buf) { return false; }
411static __always_inline bool has_xtheadvector_no_alternatives(void) { return false; }
412static __always_inline bool has_xtheadvector(void) { return false; }
413static inline bool riscv_v_first_use_handler(struct pt_regs *regs) { return false; }
414static inline bool riscv_v_vstate_query(struct pt_regs *regs) { return false; }
415static inline bool riscv_v_vstate_ctrl_user_allowed(void) { return false; }
416#define riscv_v_vsize (0)
417#define riscv_v_vstate_discard(regs)		do {} while (0)
418#define riscv_v_vstate_save(vstate, regs)	do {} while (0)
419#define riscv_v_vstate_restore(vstate, regs)	do {} while (0)
420#define __switch_to_vector(__prev, __next)	do {} while (0)
421#define riscv_v_vstate_off(regs)		do {} while (0)
422#define riscv_v_vstate_on(regs)			do {} while (0)
423#define riscv_v_thread_free(tsk)		do {} while (0)
424#define  riscv_v_setup_ctx_cache()		do {} while (0)
425#define riscv_v_thread_alloc(tsk)		do {} while (0)
426 
427#endif /* CONFIG_RISCV_ISA_V */
428 

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 69개 줄에 각각 설명을 붙였습니다.

L360#else /* !CONFIG_RISCV_ISA_V_PREEMPTIVE */

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

L361static inline bool riscv_preempt_v_dirty(struct task_struct *task) { return false; }

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L362static inline bool riscv_preempt_v_restore(struct task_struct *task) { return false; }

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L363static inline bool riscv_preempt_v_started(struct task_struct *task) { return false; }

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L364#define riscv_preempt_v_clear_dirty(tsk) do {} while (0)

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

L365#define riscv_preempt_v_set_restore(tsk) do {} while (0)

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

L366#endif /* CONFIG_RISCV_ISA_V_PREEMPTIVE */

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

L367(blank)

빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L368static inline void __switch_to_vector(struct task_struct *prev,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L369 struct task_struct *next)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L370{

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

L371 struct pt_regs *regs;

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

L372(blank)

빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L373 if (riscv_preempt_v_started(prev)) {

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

L374 if (riscv_v_is_on()) {

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

L375 WARN_ON(prev->thread.riscv_v_flags & RISCV_V_CTX_DEPTH_MASK);

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

L376 riscv_v_disable();

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

L377 prev->thread.riscv_v_flags |= RISCV_PREEMPT_V_IN_SCHEDULE;

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

L378 }

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

L379 if (riscv_preempt_v_dirty(prev)) {

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

L380 __riscv_v_vstate_save(&prev->thread.kernel_vstate,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L381 prev->thread.kernel_vstate.datap);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L382 riscv_preempt_v_clear_dirty(prev);

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

L383 }

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

L384 } else {

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L385 regs = task_pt_regs(prev);

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

L386 riscv_v_vstate_save(&prev->thread.vstate, regs);

prev의 DIRTY user vector register를 task buffer에 저장한다.

L387 }

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

L388(blank)

빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L389 if (riscv_preempt_v_started(next)) {

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

L390 if (next->thread.riscv_v_flags & RISCV_PREEMPT_V_IN_SCHEDULE) {

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

L391 next->thread.riscv_v_flags &= ~RISCV_PREEMPT_V_IN_SCHEDULE;

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

L392 riscv_v_enable();

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

L393 } else {

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L394 riscv_preempt_v_set_restore(next);

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

L395 }

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

L396 } else {

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L397 riscv_v_vstate_set_restore(next, task_pt_regs(next));

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

L398 }

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

L399}

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

L400(blank)

빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L401void riscv_v_vstate_ctrl_init(struct task_struct *tsk);

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

L402bool riscv_v_vstate_ctrl_user_allowed(void);

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

L403(blank)

빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L404#else /* ! CONFIG_RISCV_ISA_V */

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

L405(blank)

빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L406struct pt_regs;

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

L407(blank)

빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L408static inline int riscv_v_setup_vsize(void) { return -EOPNOTSUPP; }

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

L409static __always_inline bool has_vector(void) { return false; }

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L410static __always_inline bool insn_is_vector(u32 insn_buf) { return false; }

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L411static __always_inline bool has_xtheadvector_no_alternatives(void) { return false; }

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L412static __always_inline bool has_xtheadvector(void) { return false; }

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L413static inline bool riscv_v_first_use_handler(struct pt_regs *regs) { return false; }

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L414static inline bool riscv_v_vstate_query(struct pt_regs *regs) { return false; }

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L415static inline bool riscv_v_vstate_ctrl_user_allowed(void) { return false; }

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L416#define riscv_v_vsize (0)

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

L417#define riscv_v_vstate_discard(regs) do {} while (0)

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

L418#define riscv_v_vstate_save(vstate, regs) do {} while (0)

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

L419#define riscv_v_vstate_restore(vstate, regs) do {} while (0)

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

L420#define __switch_to_vector(__prev, __next) do {} while (0)

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

L421#define riscv_v_vstate_off(regs) do {} while (0)

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

L422#define riscv_v_vstate_on(regs) do {} while (0)

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

L423#define riscv_v_thread_free(tsk) do {} while (0)

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

L424#define riscv_v_setup_ctx_cache() do {} while (0)

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

L425#define riscv_v_thread_alloc(tsk) do {} while (0)

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

L426(blank)

빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L427#endif /* CONFIG_RISCV_ISA_V */

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

L428(blank)

빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

scalable vector state 크기 비교

arm64 SVE VL=256bit, x86 enabled XSAVE size=2688byte, RISC-V VLEN=256bit라고 가정한다.

  1. arm64Z register만 32x32=1024byte이고 P0-P15, FFR와 header가 추가된다. SME ZA를 쓰면 VLxVL 규모가 더해진다.
  2. x86CPUID leaf 0xD가 보고한 2688byte를 64-byte alignment로 배치하고 enabled XFEATURE만 XRSTOR한다.
  3. RISC-Vv0-v31은 32x32=1024byte이며 vstart/vl/vtype/vcsr와 alignment가 추가된다.
  4. budget10GB/s 유효 copy bandwidth에서 2KiB save+restore의 순수 copy 하한은 약 0.2us지만 serialization과 cache miss가 실제 시간을 키운다.

결론ISA 이름으로 고정 크기를 가정하지 않는다. boot CPU feature, task VL과 enabled component로 매 switch의 실제 state 크기를 계산한다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 kernel core와 architecture hook의 경계

extended state는 항상 switch 때 전부 복사하지 않는다. CPU register의 현재 owner, task memory image의 최신성, user return 전에 restore가 필요한지 나타내는 flag를 조합해 eager와 lazy 경로를 선택한다.

context switch, kernel-mode vector 사용, signal delivery, ptrace와 CPU migration이 같은 state를 만진다. 각 경로가 memory copy를 소유하는지 또는 단지 dirty/foreign flag만 바꾸는지 구분한다.

02

arm64: TIF_FOREIGN_FPSTATE와 per-CPU last state로 FPSIMD ownership 관리

FPSIMD 32x128-bit 외에 SVE의 scalable Z/P/FFR, SME streaming mode와 ZA가 task state를 확장한다. fpsimd_thread_switch()는 next state가 CPU에 이미 live한지 판단해 user return restore 여부를 표시한다.

kernel_neon_begin, preemption disable과 softirq context가 user state 보존 순서를 제약한다. 디버깅할 때는 VL, SVCR, TIF_SVE/TIF_SME, per-CPU fpsimd_last_state와 state buffer size를 확인한다.

03

x86-64: XSAVE memory image와 TIF_NEED_FPU_LOAD의 lazy restore

x87, SSE, AVX, AVX-512, PKRU, AMX tile처럼 CPUID가 열거한 XFEATURE를 xstate buffer가 담는다. switch에서는 prev state를 저장하고 next hardware load는 user return까지 미룰 수 있다.

XFD와 fpregs_owner_ctx가 state component 사용과 owner CPU를 추적하며 NMI에서 fpregs_lock 규칙을 지켜야 한다. 디버깅할 때는 XCR0, XFD, xfeatures mask, fpstate size, last_cpu와 TIF_NEED_FPU_LOAD를 기록한다.

04

RISC-V: sstatus.FS/VS dirty bit와 VLEN별 vector buffer

FS/VS field가 OFF, INITIAL, CLEAN, DIRTY를 나타낸다. vector state는 32개 v register와 vstart, vl, vtype, vcsr를 포함하고 VLEN에 따라 task buffer 크기가 달라진다.

kernel-mode vector nesting과 preemption은 user vector owner를 임시로 밀어내므로 nesting start/end가 restore 책임을 가진다. 디버깅할 때는 sstatus.FS/VS, VLENB, vtype/vl, datap pointer와 kernel_vstate nesting depth를 확인한다.

05

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

live hardware register와 task의 memory-backed state 중 하나만 최신일 수 있다. owner CPU가 바뀌거나 signal/ptrace가 state를 읽기 전에는 반드시 memory image를 최신화해야 한다.

주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.

06

latency upper bound는 hardware instruction 하나가 아니다

state 크기는 arm64 SVE VL/SME ZA, x86 XFEATURE mask와 RISC-V VLEN에 따라 달라진다. Tsave = bytes / memory_bandwidth + serialization에 migration과 allocation 비용이 추가된다.

평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.

07 · FAILURE

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

분류관찰되는 결과첫 확인값
arm64VL 변경이나 CPU migration 뒤 foreign flag가 빠지면 이전 CPU의 vector state를 user가 관찰한다.VL, SVCR, TIF_SVE/TIF_SME, per-CPU fpsimd_last_state와 state buffer size를 확인한다.
x86-64XFEATURE mask/size가 맞지 않으면 XRSTOR fault 또는 보안 state 유출이 발생한다.XCR0, XFD, xfeatures mask, fpstate size, last_cpu와 TIF_NEED_FPU_LOAD를 기록한다.
RISC-VVS DIRTY를 보지 않고 switch하면 next가 prev vector register를 관찰하거나 prev 계산 결과가 사라진다.sstatus.FS/VS, VLENB, vtype/vl, datap pointer와 kernel_vstate nesting depth를 확인한다.

08 · LAB

재현과 계측 절차

  1. vector instruction을 쓰는 task와 쓰지 않는 task를 번갈아 실행해 switch latency distribution을 비교한다.
  2. signal handler와 ptrace가 extended state를 읽는 순간 memory image가 최신인지 검사한다.
  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 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.