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만 바꾸는지 구분한다.
Tsave = bytes / memory_bandwidth + serialization에 migration과 allocation 비용이 추가된다.02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | TIF_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-64 | XSAVE memory image와 TIF_NEED_FPU_LOAD의 lazy restore | XFEATURE mask/size가 맞지 않으면 XRSTOR fault 또는 보안 state 유출이 발생한다. | XCR0, XFD, xfeatures mask, fpstate size, last_cpu와 TIF_NEED_FPU_LOAD를 기록한다. |
| RISC-V | sstatus.FS/VS dirty bit와 VLEN별 vector buffer | VS DIRTY를 보지 않고 switch하면 next가 prev vector register를 관찰하거나 prev 계산 결과가 사라진다. | sstatus.FS/VS, VLENB, vtype/vl, datap pointer와 kernel_vstate nesting depth를 확인한다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
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를 확인한다.
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개 줄에 각각 설명을 붙였습니다.
* consumption.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (system_supports_sme())이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
sme_smstop();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
set_thread_flag(TIF_FOREIGN_FPSTATE);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void fpsimd_thread_switch(struct task_struct *next)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
bool wrong_task, wrong_cpu;이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (!system_supports_fpsimd())이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return;이 함수가 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
WARN_ON_ONCE(!irqs_disabled());불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.
(blank)빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Save unsaved fpsimd state, if any: */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (test_thread_flag(TIF_KERNEL_FPSTATE))이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
fpsimd_save_kernel_state(current);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
else앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.
fpsimd_save_user_state();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (test_tsk_thread_flag(next, TIF_KERNEL_FPSTATE)) {이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
fpsimd_flush_cpu_state();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
fpsimd_load_kernel_state(next);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
} else {이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Fix up TIF_FOREIGN_FPSTATE to correctly describe next'sLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* state. For kernel threads, FPSIMD registers are neverLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* loaded with user mode FPSIMD state and so wrong_task andLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* wrong_cpu will always be true.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
wrong_task = __this_cpu_read(fpsimd_last_state.st) !=계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
&next->thread.uw.fpsimd_state;이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
wrong_cpu = next->thread.fpsimd_cpu != smp_processor_id();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
update_tsk_thread_flag(next, TIF_FOREIGN_FPSTATE,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
wrong_task || wrong_cpu);이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void fpsimd_flush_thread_vl(enum vec_type type)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
int vl, supported_vl;이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Reset the task vector length as required. This is where weLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* ensure that all user tasks have a valid vector lengthLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* configured: no kernel task can become a user task withoutLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* an exec and hence a call to this function. By the time theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* first call to this function is made, all early hardwareLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* probing is complete, so __sve_default_vl should be valid.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* If a bug causes this to go wrong, we make some noise andLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* try to fudge thread.sve_vl to a safe value here.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
vl = task_get_vl_onexec(current, type);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (!vl)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
vl = get_default_vl(type);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (WARN_ON(!sve_vl_valid(vl)))이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
vl = vl_info[type].min_vl;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 arm64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
supported_vl = find_supported_vector_length(type, vl);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
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개 줄에 각각 설명을 붙였습니다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Once TIF_NEED_FPU_LOAD is set, it is required to load theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* registers before returning to userland or using the contentLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* otherwise.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The FPU context is only stored/restored for a user task andLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* PF_KTHREAD is used to distinguish between kernel and user threads.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
static inline void switch_fpu(struct task_struct *old, int cpu)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (!test_tsk_thread_flag(old, TIF_NEED_FPU_LOAD) &&이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
cpu_feature_enabled(X86_FEATURE_FPU) &&이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
!(old->flags & (PF_KTHREAD | PF_USER_WORKER))) {이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
struct fpu *old_fpu = x86_task_fpu(old);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
set_tsk_thread_flag(old, TIF_NEED_FPU_LOAD);이 task가 다시 user mode로 갈 때 hardware restore가 필요함을 표시한다.
save_fpregs_to_fpstate(old_fpu);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The save operation preserved register state, so theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* fpu_fpregs_owner_ctx is still @old_fpu. Store theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* current CPU number in @old_fpu, so the next returnLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* to user space can avoid the FPU register restoreLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* when is returns on the same CPU and still owns theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* context. See fpregs_restore_userregs().Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
old_fpu->last_cpu = cpu;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
trace_x86_fpu_regs_deactivated(old_fpu);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#endif /* _ASM_X86_FPU_SCHED_H */Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(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개 줄에 각각 설명을 붙였습니다.
#else /* !CONFIG_RISCV_ISA_V_PREEMPTIVE */Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
static 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를 분리해 해석한다.
static 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를 분리해 해석한다.
static 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를 분리해 해석한다.
#define riscv_preempt_v_clear_dirty(tsk) do {} while (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define riscv_preempt_v_set_restore(tsk) do {} while (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#endif /* CONFIG_RISCV_ISA_V_PREEMPTIVE */Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static 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를 분리해 해석한다.
struct task_struct *next)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct pt_regs *regs;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (riscv_preempt_v_started(prev)) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
if (riscv_v_is_on()) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
WARN_ON(prev->thread.riscv_v_flags & RISCV_V_CTX_DEPTH_MASK);불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.
riscv_v_disable();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
prev->thread.riscv_v_flags |= RISCV_PREEMPT_V_IN_SCHEDULE;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (riscv_preempt_v_dirty(prev)) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
__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를 분리해 해석한다.
prev->thread.kernel_vstate.datap);이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
riscv_preempt_v_clear_dirty(prev);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
} else {이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
regs = task_pt_regs(prev);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
riscv_v_vstate_save(&prev->thread.vstate, regs);prev의 DIRTY user vector register를 task buffer에 저장한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (riscv_preempt_v_started(next)) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
if (next->thread.riscv_v_flags & RISCV_PREEMPT_V_IN_SCHEDULE) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
next->thread.riscv_v_flags &= ~RISCV_PREEMPT_V_IN_SCHEDULE;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
riscv_v_enable();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
} else {이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
riscv_preempt_v_set_restore(next);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
} else {이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
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를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void 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를 연결해 본다.
bool riscv_v_vstate_ctrl_user_allowed(void);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#else /* ! CONFIG_RISCV_ISA_V */Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
struct pt_regs;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static inline int riscv_v_setup_vsize(void) { return -EOPNOTSUPP; }불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.
static __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를 분리해 해석한다.
static __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를 분리해 해석한다.
static __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를 분리해 해석한다.
static __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를 분리해 해석한다.
static 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를 분리해 해석한다.
static 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를 분리해 해석한다.
static 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를 분리해 해석한다.
#define riscv_v_vsize (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define riscv_v_vstate_discard(regs) do {} while (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define riscv_v_vstate_save(vstate, regs) do {} while (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define riscv_v_vstate_restore(vstate, regs) do {} while (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define __switch_to_vector(__prev, __next) do {} while (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define riscv_v_vstate_off(regs) do {} while (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define riscv_v_vstate_on(regs) do {} while (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define riscv_v_thread_free(tsk) do {} while (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define riscv_v_setup_ctx_cache() do {} while (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define riscv_v_thread_alloc(tsk) do {} while (0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#endif /* CONFIG_RISCV_ISA_V */Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V FPU, SIMD, SVE/SME, XSAVE와 RISC-V Vector state 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
05 · WORKED EXAMPLE
숫자로 검산하기
scalable vector state 크기 비교
arm64 SVE VL=256bit, x86 enabled XSAVE size=2688byte, RISC-V VLEN=256bit라고 가정한다.
- arm64Z register만 32x32=1024byte이고 P0-P15, FFR와 header가 추가된다. SME ZA를 쓰면 VLxVL 규모가 더해진다.
- x86CPUID leaf 0xD가 보고한 2688byte를 64-byte alignment로 배치하고 enabled XFEATURE만 XRSTOR한다.
- RISC-Vv0-v31은 32x32=1024byte이며 vstart/vl/vtype/vcsr와 alignment가 추가된다.
- 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
경계별 상세 분석
공통 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만 바꾸는지 구분한다.
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를 확인한다.
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를 기록한다.
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를 확인한다.
객체 수명과 소유권을 먼저 고정한다
live hardware register와 task의 memory-backed state 중 하나만 최신일 수 있다. owner CPU가 바뀌거나 signal/ptrace가 state를 읽기 전에는 반드시 memory image를 최신화해야 한다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
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
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | VL 변경이나 CPU migration 뒤 foreign flag가 빠지면 이전 CPU의 vector state를 user가 관찰한다. | VL, SVCR, TIF_SVE/TIF_SME, per-CPU fpsimd_last_state와 state buffer size를 확인한다. |
| x86-64 | XFEATURE mask/size가 맞지 않으면 XRSTOR fault 또는 보안 state 유출이 발생한다. | XCR0, XFD, xfeatures mask, fpstate size, last_cpu와 TIF_NEED_FPU_LOAD를 기록한다. |
| RISC-V | VS DIRTY를 보지 않고 switch하면 next가 prev vector register를 관찰하거나 prev 계산 결과가 사라진다. | sstatus.FS/VS, VLENB, vtype/vl, datap pointer와 kernel_vstate nesting depth를 확인한다. |
08 · LAB
재현과 계측 절차
- vector instruction을 쓰는 task와 쓰지 않는 task를 번갈아 실행해 switch latency distribution을 비교한다.
- signal handler와 ptrace가 extended state를 읽는 순간 memory image가 최신인지 검사한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/kernel/fpsimd.c:1604-1668
- x86-64arch/x86/include/asm/fpu/sched.h:24-56
- RISC-Varch/riscv/include/asm/vector.h:360-428
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.