← Architecture 비교

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개 · 원문 167줄
기준 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에서 함수 선언을 다시 찾아 발췌했습니다. 원문 전체 발췌를 보존하고, 개별 해설은 근거가 있는 구문에만 붙였습니다.

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줄은 그대로 표시하며, 검토한 30개 구문에 설명을 붙였습니다.

L1606 if (system_supports_sme())

이 CPU 시스템이 SME를 지원할 때만 streaming mode와 ZA를 중지합니다. 미지원 하드웨어에는 SME 전용 명령을 실행하지 않습니다.

L1607 sme_smstop();

SME streaming mode와 ZA 사용을 중지합니다. 남아 있는 streaming mode가 커널 NEON 동작에 영향을 주거나 불필요한 전력을 쓰지 않도록 CPU의 FP 상태 소유 정보를 비울 때 함께 정리합니다.

L1609 set_thread_flag(TIF_FOREIGN_FPSTATE);

현재 태스크의 FP 상태가 CPU 레지스터에 유효하게 올라와 있지 않음을 표시합니다. 다음 사용자 복귀에서 메모리에 저장된 상태를 다시 로드하게 하는 표시입니다.

L1612void fpsimd_thread_switch(struct task_struct *next)

다음 태스크 next로 전환할 FP/SIMD 상태를 준비하는 함수입니다. 현재 레지스터를 저장하고 next가 커널 FP 상태인지 사용자 FP 상태인지에 따라 즉시 복원 또는 지연 복원을 선택합니다.

L1614 bool wrong_task, wrong_cpu;

CPU에 남은 FP 레지스터의 소유 태스크가 다른지와 저장 당시 CPU가 다른지를 각각 담습니다. 둘 중 하나라도 참이면 next가 사용자 복귀 전에 상태를 다시 올려야 합니다.

L1616 if (!system_supports_fpsimd())

시스템에서 FP/SIMD를 사용할 수 없는지 검사합니다. 해당 경우 존재하지 않는 FP 문맥을 저장·복원하려고 접근하지 않습니다.

L1617 return;

FP/SIMD 미지원 시스템에서는 문맥 전환의 FP 처리를 즉시 끝냅니다. 일반 정수 레지스터 전환까지 취소하는 반환은 아닙니다.

L1619 WARN_ON_ONCE(!irqs_disabled());

IRQ가 켜진 상태로 호출되면 한 번 경고합니다. CPU FP 상태 소유 정보와 실제 레지스터를 바꾸는 중 인터럽트 사용이 끼어들지 않아야 합니다.

L1622 if (test_thread_flag(TIF_KERNEL_FPSTATE))

현재 태스크가 커널 FP 사용 구간에서 중단됐는지 검사합니다. 참이면 커널 FP 저장 영역에, 거짓이면 사용자 FP 문맥에 저장합니다.

L1623 fpsimd_save_kernel_state(current);

커널 안에서 FP/SIMD를 사용 중이던 현재 태스크의 레지스터를 커널 FP 저장 영역에 보관합니다. 스케줄링 뒤 그 커널 연산을 계속할 수 있게 합니다.

L1624 else

현재 태스크가 커널 FP 상태를 사용하지 않는 경우입니다. 아직 CPU에 남아 있는 사용자 FP/SIMD 상태를 필요에 따라 보관합니다.

L1625 fpsimd_save_user_state();

CPU에 아직 남아 있는 현재 태스크의 사용자 FP/SIMD 상태를 필요에 따라 저장합니다. 다음 태스크가 레지스터를 사용해도 사용자 계산 상태가 사라지지 않도록 합니다.

L1627 if (test_tsk_thread_flag(next, TIF_KERNEL_FPSTATE)) {

next가 커널 FP 연산 도중 중단된 태스크인지 검사합니다. 그러면 커널에서 곧바로 계산을 이어야 하므로 레지스터를 즉시 복원합니다.

L1628 fpsimd_flush_cpu_state();

CPU에 남아 있는 사용자 FPSIMD 소유 정보를 무효화하고 SME 모드 등을 정리합니다. next의 커널 FP 상태를 로드하기 전에 기존 사용자 상태로 오인되지 않게 합니다.

L1629 fpsimd_load_kernel_state(next);

다음 태스크가 중단되기 전에 저장한 커널 FP 레지스터를 복원합니다. 사용자 복귀까지 미루지 않고 커널 내 FP 연산을 즉시 재개해야 하는 경로입니다.

L1630 } else {

next가 커널 FP 연산을 재개하는 경우가 아니면 사용자 FP 상태의 유효성만 판정합니다. 실제 복원은 사용자 복귀 경로가 필요할 때 수행하도록 표시합니다.

L1637 wrong_task = __this_cpu_read(fpsimd_last_state.st) !=

이 CPU에 마지막으로 올린 FP 상태 객체가 next의 저장 객체인지 비교하기 시작합니다. 같은 CPU라도 다른 태스크가 FP를 사용했다면 기존 레지스터는 next의 상태가 아닙니다.

L1638 &next->thread.uw.fpsimd_state;

next의 사용자 FPSIMD 저장 객체 주소와 비교하여 wrong_task를 정합니다. 내용 전체를 비교하는 대신 CPU가 추적한 소유 포인터를 사용합니다.

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

next의 FP 상태를 마지막으로 올렸던 CPU가 지금 CPU와 다른지 검사합니다. 다르면 현재 CPU 레지스터에 next의 상태가 남아 있다고 믿을 수 없으므로 복원이 필요합니다.

L1641 update_tsk_thread_flag(next, TIF_FOREIGN_FPSTATE,

next의 TIF_FOREIGN_FPSTATE 표시를 아래 조건에 맞춰 갱신합니다. 이 플래그는 현재 CPU 레지스터를 next의 유효한 사용자 상태로 볼 수 없는지 나타냅니다.

L1642 wrong_task || wrong_cpu);

소유 태스크 또는 마지막 사용 CPU가 다르면 복원 필요 표시를 켭니다. 둘 다 일치하면 레지스터가 그대로 남아 있어 복원 비용을 줄일 수 있습니다.

L1646static void fpsimd_flush_thread_vl(enum vec_type type)

exec 등에서 지정된 SVE·SME 종류의 벡터 길이를 유효한 값으로 다시 정하는 함수입니다. type이 어느 벡터 기능의 길이 정책을 적용할지 고릅니다.

L1648 int vl, supported_vl;

설정하려는 벡터 길이와 실제 지원되는 길이를 각각 담습니다. 요청값 검증과 지원값으로의 보정 결과를 구분합니다.

L1660 vl = task_get_vl_onexec(current, type);

다음 exec 시 적용하도록 예약한 SVE 또는 SME 벡터 길이를 읽습니다. type으로 어느 벡터 종류의 정책을 적용할지 선택합니다.

L1661 if (!vl)

exec용 예약 길이가 0이면 따로 지정하지 않은 경우로 봅니다. 이때 시스템 기본 길이를 사용하고 값이 있으면 그 요청을 검증합니다.

L1662 vl = get_default_vl(type);

exec용으로 지정한 길이가 없으면 해당 벡터 종류의 시스템 기본 길이를 선택합니다. 새 프로그램이 유효한 벡터 길이로 시작하도록 합니다.

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

벡터 길이가 아키텍처의 유효 범위·정렬 규칙을 만족하는지 검사합니다. 잘못된 값이면 경고하고 최소 길이로 낮춰 안전한 실행 상태를 만듭니다.

L1665 vl = vl_info[type].min_vl;

잘못된 길이를 해당 벡터 종류가 지원하는 최소 길이로 대체합니다. 새 사용자 태스크가 유효하지 않은 벡터 길이로 시작하지 않게 합니다.

L1667 supported_vl = find_supported_vector_length(type, vl);

선택한 벡터 길이에 대해 하드웨어가 실제 지원하는 길이를 구합니다. 요청값과 다르면 뒤에서 경고하고 지원되는 값으로 보정합니다.

L1668 if (WARN_ON(supported_vl != vl))

형식상 유효한 요청 길이가 실제 하드웨어 지원 길이와 같은지 검사합니다. 다르면 경고한 뒤 지원 가능한 길이로 보정합니다.

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줄은 그대로 표시하며, 검토한 10개 구문에 설명을 붙였습니다.

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

전환 전 태스크 old의 FP 레지스터를 필요하면 저장하는 함수입니다. cpu는 저장한 상태가 어느 CPU에 아직 남아 있는지 기록하여 재복원을 줄이는 데 사용합니다.

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

old의 FP 레지스터가 이미 복원 필요 상태인지 먼저 검사합니다. TIF_NEED_FPU_LOAD가 꺼져 있어 CPU에 유효한 상태가 남아 있을 때만 저장을 진행합니다.

L35 cpu_feature_enabled(X86_FEATURE_FPU) &&

CPU가 FPU 기능을 제공하는지도 저장 조건에 포함합니다. 하드웨어 FPU 상태가 없는 시스템에서는 FP 레지스터 저장 명령을 수행하지 않습니다.

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

커널 스레드와 사용자 작업자 태스크는 이 사용자 FPU 저장 경로에서 제외합니다. 앞 조건들과 함께 실제 사용자 FP 상태를 가진 태스크만 대상으로 삼습니다.

L37 struct fpu *old_fpu = x86_task_fpu(old);

전환 전 태스크의 FPU 관리 구조체를 얻습니다. FP/SIMD 확장 상태의 메모리 저장 영역과 소유 정보를 뒤의 저장 경로에서 사용합니다.

L39 set_tsk_thread_flag(old, TIF_NEED_FPU_LOAD);

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

L40 save_fpregs_to_fpstate(old_fpu);

CPU의 현재 FP 레지스터 상태를 old_fpu의 fpstate에 저장합니다. 다음 태스크가 FPU를 사용하기 전에 이전 태스크의 계산 상태를 보존합니다.

L49 old_fpu->last_cpu = cpu;

FP 상태를 저장한 현재 CPU 번호를 old_fpu에 기록합니다. 같은 CPU로 돌아왔고 그 레지스터의 소유도 여전히 old이면 메모리에서 중복 복원하지 않을 수 있습니다.

L51 trace_x86_fpu_regs_deactivated(old_fpu);

old_fpu의 레지스터 활성 상태를 해제한 지점을 추적 이벤트로 남깁니다. 상태 저장 자체가 아니라 문맥 전환 중 FPU 소유 변화의 관측 기록입니다.

L55#endif /* _ASM_X86_FPU_SCHED_H */

2행에서 시작한 선택 구간을 마칩니다. FPU task 전환 helper 헤더의 중복 포함을 막는 조건입니다. FPU 기능을 켜거나 끄는 설정이 아니라 같은 정의가 두 번 나타나지 않게 합니다.

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줄은 그대로 표시하며, 검토한 53개 구문에 설명을 붙였습니다.

L360#else /* !CONFIG_RISCV_ISA_V_PREEMPTIVE */

커널 벡터 사용 중 선점을 허용하지 않는 구성입니다. 선점 벡터 문맥의 dirty·복원·시작 여부 조회는 false로, 표식 변경은 빈 구현으로 정의합니다. 사용자 Vector 확장 전체가 없는 경우와는 다릅니다.

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

선점 가능한 커널 벡터 지원이 없는 빌드에서는 커널 벡터 dirty 상태를 항상 false로 답합니다. 저장할 선점형 커널 벡터 문맥 자체가 없기 때문입니다.

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

선점형 커널 벡터 지원을 빌드하지 않으면 지연 복원 필요 상태를 항상 false로 반환합니다. 사용자 벡터 상태 지원 여부와는 별개인 커널 선점형 경로의 대체 함수입니다.

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

선점형 커널 벡터 구간이 시작됐는지 항상 false로 답하는 미지원 구성의 함수입니다. 문맥 전환은 아래에서 사용자 벡터 경로만 선택하게 됩니다.

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

선점 가능한 커널 벡터 사용을 빌드하지 않은 구성의 빈 구현입니다. 그 모드의 RISCV_PREEMPT_V_DIRTY 상태를 유지하지 않으므로 지울 비트도 없으며, 일반 사용자 벡터 지원까지 모두 껐다는 뜻은 아닙니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

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

CONFIG_RISCV_ISA_V_PREEMPTIVE가 꺼져 있어 커널 벡터 문맥의 지연 복원 표시를 설정할 필요가 없습니다. 호출 문장 형태만 남겨 공통 문맥 전환 코드가 같은 이름을 사용할 수 있게 합니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L366#endif /* CONFIG_RISCV_ISA_V_PREEMPTIVE */

332행에서 시작한 선택 구간을 마칩니다. 커널 벡터 계산 중 선점을 허용하는 구성은 task별 벡터 상태의 dirty·복원 필요·사용 시작 표식을 관리합니다. 미지원 구성은 이 조회를 false, 변경을 빈 매크로로 정의하여 추가 선점 문맥을 만들지 않습니다.

L368static inline void __switch_to_vector(struct task_struct *prev,

이전 태스크 prev와 다음 태스크 사이의 벡터 문맥을 전환하는 인라인 함수입니다. 커널 선점형 상태와 사용자 벡터 상태를 구분해 보관·복원 표시를 처리합니다.

L369 struct task_struct *next)

다음 태스크 next를 받는 함수 정의를 마칩니다. next의 플래그에 따라 커널 벡터를 재활성화하거나 복원 필요 상태를 표시합니다.

L371 struct pt_regs *regs;

태스크의 저장 사용자 레지스터 문맥을 가리킬 포인터입니다. pt_regs의 status에 있는 VS 상태로 사용자 벡터 레지스터 저장 필요 여부를 판단합니다.

L373 if (riscv_preempt_v_started(prev)) {

prev가 선점 가능한 커널 벡터 사용 구간 안에 있는지 검사합니다. 참이면 kernel_vstate를 관리하고, 아니면 사용자 vstate를 저장하는 경로를 택합니다.

L374 if (riscv_v_is_on()) {

현재 CPU에서 벡터 접근이 켜져 있는지 검사합니다. 켜져 있으면 스케줄링 동안 이를 끄고 복귀 시 다시 켤 표시를 남깁니다.

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

벡터 접근이 켜진 채 전환하는데 중첩 문맥 깊이도 남아 있으면 경고합니다. 예상한 최상위 선점형 벡터 사용 상태인지 검증하는 진단입니다.

L376 riscv_v_disable();

벡터를 사용 중인 커널 태스크가 스케줄링에 들어가므로 현재 벡터 접근을 끕니다. 이어지는 IN_SCHEDULE 표시와 함께 복귀할 때 다시 켜야 함을 기록합니다.

L377 prev->thread.riscv_v_flags |= RISCV_PREEMPT_V_IN_SCHEDULE;

벡터를 켠 상태에서 스케줄링으로 중단되었다는 표시를 prev에 남깁니다. 이 태스크가 다시 선택되면 같은 사용 구간을 이어갈 수 있도록 활성 상태를 복원합니다.

L379 if (riscv_preempt_v_dirty(prev)) {

현재 커널 벡터 레지스터가 저장본 이후 변경됐는지 검사합니다. dirty일 때만 실제 벡터 레지스터를 메모리에 저장하여 불필요한 복사를 줄입니다.

L380 __riscv_v_vstate_save(&prev->thread.kernel_vstate,

현재 벡터 레지스터와 제어 CSR을 prev의 커널 벡터 저장 객체에 보관하는 호출을 시작합니다. 사용자 vstate와 별도 영역을 사용합니다.

L381 prev->thread.kernel_vstate.datap);

커널 벡터 데이터가 들어갈 실제 버퍼 주소를 두 번째 인자로 넘깁니다. 앞의 kernel_vstate는 제어 상태, datap는 큰 벡터 레지스터 내용의 보관 공간입니다.

L382 riscv_preempt_v_clear_dirty(prev);

현재 커널 벡터 상태를 메모리에 저장했으므로 prev의 dirty 표시를 지웁니다. 저장 이후 레지스터가 다시 변경되기 전에는 같은 상태를 중복 저장할 필요가 없습니다.

L384 } else {

prev가 선점형 커널 벡터 사용 구간이 아니면 사용자 벡터 저장 경로를 선택합니다. pt_regs의 VS 상태를 보고 사용자 문맥이 dirty일 때 저장합니다.

L385 regs = task_pt_regs(prev);

이전 태스크의 사용자 pt_regs를 얻습니다. 저장된 상태 비트와 함께 사용자 벡터 레지스터를 보존할지 판단하는 데 사용합니다.

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

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

L389 if (riscv_preempt_v_started(next)) {

next가 커널 선점형 벡터 사용 중 중단된 태스크인지 검사합니다. 그러면 커널 사용을 이어갈 준비를 하고, 아니면 사용자 복귀용 복원 표시를 처리합니다.

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

next가 벡터를 켠 상태에서 스케줄링으로 빠졌다는 표시를 검사합니다. 참이면 그 표시를 지우고 벡터 접근을 다시 켭니다.

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

스케줄링 중단 표시를 지워 해당 중단 상태가 끝났음을 반영합니다. 다른 벡터 상태 비트는 유지합니다.

L392 riscv_v_enable();

벡터 사용 도중 스케줄링되었던 next로 돌아오므로 벡터 접근을 다시 켭니다. 바로 앞에서 IN_SCHEDULE 표시를 지워 해당 중단 상태가 끝났음을 반영합니다.

L393 } else {

next가 켜진 벡터 구간에서 직접 스케줄링된 경우가 아니면 지연 복원 표시를 남기는 경로입니다. 적절한 커널 벡터 재개 지점에서 저장 상태를 가져오게 합니다.

L394 riscv_preempt_v_set_restore(next);

next의 커널 벡터 상태를 나중에 복원해야 한다고 표시합니다. 지금 바로 복원하는 경로가 아닌 경우에도 다음 벡터 사용 전에 저장값을 가져오게 합니다.

L396 } else {

next가 선점형 커널 벡터 태스크가 아니면 사용자 벡터 복원 경로를 사용합니다. 실제 레지스터 적재는 사용자 복귀 전에 필요한 경우 수행합니다.

L397 riscv_v_vstate_set_restore(next, task_pt_regs(next));

다음 태스크의 사용자 벡터 상태를 복원 대상으로 표시합니다. 실제 사용자 실행으로 돌아가기 전에 pt_regs의 상태와 저장된 벡터 문맥을 맞추게 합니다.

L401void riscv_v_vstate_ctrl_init(struct task_struct *tsk);

태스크의 사용자 벡터 사용 정책을 초기화하는 함수의 선언입니다. tsk가 대상 태스크이며 이 줄에서는 초기화 함수를 실행하지 않습니다.

L402bool riscv_v_vstate_ctrl_user_allowed(void);

현재 태스크의 정책상 사용자 벡터 사용이 허용되는지 bool로 알려 주는 함수의 선언입니다. 하드웨어 벡터 연산을 실행하거나 상태를 복원하는 문장이 아닙니다.

L404#else /* ! CONFIG_RISCV_ISA_V */

Vector 확장을 관리하지 않는 커널 구성입니다. 기능 조회는 false, 저장 크기 준비는 EOPNOTSUPP, 상태 저장·복원·할당은 빈 구현을 제공하므로 실제 벡터 문맥을 만들지 않습니다.

L406struct pt_regs;

pt_regs 구조체 이름만 미리 선언합니다. 아래의 미지원 기능 대체 함수들이 구조체 포인터를 받을 수 있게 하며 레지스터 저장 공간을 할당하지 않습니다.

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

벡터 기능을 빌드하지 않은 구성에서 벡터 저장 크기 초기화 요청을 -EOPNOTSUPP로 거절합니다. 사용할 벡터 문맥을 실제로 만들지는 않습니다.

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

벡터 미지원 빌드에서는 표준 RISC-V 벡터 기능 사용 가능 여부를 항상 false로 답합니다. 호출자가 벡터 명령 경로를 선택하지 않게 합니다.

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

벡터 미지원 빌드에서는 입력 명령을 처리 가능한 벡터 명령으로 인정하지 않습니다. 실제 명령 비트열과 관계없이 false를 반환하는 대체 함수입니다.

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

벡터 미지원 빌드의 T-Head 벡터 탐지 대체 함수입니다. alternatives 패치 없이 검사하는 호출에서도 사용할 수 없음을 false로 반환합니다.

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

벡터 기능이 없는 빌드에서는 T-Head 전용 벡터 기능도 사용할 수 없다고 답합니다. 하드웨어 명령을 시험 실행하는 검사가 아닙니다.

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

벡터 미지원 빌드에서는 첫 사용 트랩을 처리하지 않았음을 false로 반환합니다. 예외 처리기는 이 결과에 따라 다른 미정의 명령 처리로 진행합니다.

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

벡터 미지원 빌드에서는 저장 pt_regs에 대해 사용할 벡터 상태가 없다고 답합니다. status를 실제로 조사하거나 변경하지 않습니다.

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

벡터 기능이 없는 빌드에서는 사용자 벡터 사용을 허용하지 않는다고 false를 반환합니다. 태스크 정책만으로 미지원 커널 기능을 켤 수 없게 합니다.

L416#define riscv_v_vsize (0)

벡터 확장 지원을 빌드하지 않으면 저장할 32개 벡터 레지스터의 전체 바이트 수를 0으로 둡니다. 이 값은 하드웨어 벡터 길이를 측정한 결과가 아니라 CONFIG_RISCV_ISA_V가 꺼진 구성의 상수입니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

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

벡터 지원이 없는 빌드에서는 폐기할 벡터 레지스터 문맥이 없으므로 아무 동작도 하지 않습니다. regs의 일반 레지스터 프레임을 지우는 매크로가 아닙니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

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

CONFIG_RISCV_ISA_V가 꺼진 구성에서는 벡터 레지스터를 저장하지 않습니다. vstate와 regs 인자를 받는 호출 형태를 유지하여 공통 태스크 전환 코드를 따로 나누지 않도록 합니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

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

벡터 확장 지원을 빌드하지 않아 복원할 벡터 문맥이 없으므로 빈 문장으로 치환합니다. 일반 정수 레지스터나 부동소수점 상태의 복원까지 생략한다는 뜻은 아닙니다.

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

벡터 지원이 없는 빌드의 태스크 전환에서는 벡터 레지스터의 저장·복원 작업을 하지 않습니다. prev와 next 사이의 전체 태스크 전환은 다른 코드에서 계속 수행됩니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

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

벡터 지원을 빌드하지 않았으므로 저장된 상태의 VS 비트를 벡터 사용 금지 상태로 바꿀 작업도 생략합니다. 실제 지원 구성의 vstate_off와 같은 호출 자리를 유지하는 대체 정의입니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

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

벡터 확장 지원이 없는 빌드에서 이 이름을 호출해도 벡터 기능을 켜지 않습니다. 지원 구성에서 수행하는 저장 프레임의 VS 상태 변경을 공통 코드에서 무해하게 생략합니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

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

벡터 문맥용 메모리를 애초에 할당하지 않는 구성이므로 태스크 종료 때 해제할 벡터 버퍼도 없습니다. task_struct 자체를 해제하는 호출은 아닙니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

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

CONFIG_RISCV_ISA_V가 꺼져 벡터 문맥 버퍼를 공급할 캐시가 필요하지 않으므로 캐시 생성이 빈 동작이 됩니다. 다른 커널 객체 캐시의 초기화에는 영향을 주지 않습니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

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

벡터 지원이 없는 빌드에서는 태스크마다 벡터 문맥 버퍼를 만들지 않습니다. 인자 tsk의 일반 스택이나 다른 레지스터 저장 공간은 해당 공통 경로에서 별도로 준비됩니다. 이 이름과 인자는 전처리 단계에 정의된 내용으로 치환됩니다. 매크로 정의 자체가 CPU 명령 실행은 아닙니다.

L427#endif /* CONFIG_RISCV_ISA_V */

12행에서 시작한 선택 구간을 마칩니다. RISC-V Vector 확장을 커널에서 관리하는 빌드입니다. 켜지면 벡터 상태의 저장·복원·정책 함수를 제공하고, 꺼지면 지원 조회는 false, 크기 설정은 미지원 오류, 상태 변경은 빈 구현이 됩니다.

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 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.