KVM/ARM SVE虚拟化支持
ARMv8 SVE虚拟化软硬件架构分析,基于linux-v6.7-rc6

0 资料

1 ARMv8 SVE异常模型

1.1 ARMv8的硬件虚拟化支持

armv8在虚拟化扩展中提供了敏感指令和异常的捕获功能,可以通过配置hypervisor控制寄存器 hcr_el2 实现,它将vm执行的敏感指令或异常路由到EL2中,hypervisor捕获到相关异常后可为其模拟相关功能。其中 hcr_el2 寄存器的定义如下:

img

其中:TGE位只有在支持VHE的架构下才有效。当配置该位后所有需要路由到EL1的异常都会被路由到EL2。因此只要设置该位后,不再设置以上这些位,其对应的异常也会被路由到EL2下。

关于VHE特性支持

VHE是由HCR_EL2中的两个bits决定的。可以将这两个bits的功能总结如下:

  • E2H:控制VHE是否使能(用于访问重定向)
  • TGE:VHE使能后,控制EL0是host还是guest (用于异常重定向)

image-20231225150146421

通过以上扩展以后,前面的问题看来都已经完美解决了。但是我们再回忆一下type 2 hypervisor的原理,这种方案中hypervisor只是host os的一个组件,由于host os本身就是普通的操作系统(如linux),而其通常被设计为运行在EL1异常等级。

若将其运行在EL2下,则需要在代码层面做较多改动,如在linux中使用vbar_el1访问异常向量表基地址寄存器,此时需要修改为vbar_el2。更麻烦的是el1下支持两个页表基地址寄存器ttbr0_el1和ttbr1_el1,而在armv8.0的el2异常等级中只包含一个ttbr0_el2寄存器。

因此为了不修改host os本身的代码,最初type2 hypervisor方案中host os被设计为运行在el1下,而hypervisor作为它的一个模块运行在EL2下,其架构如下:

img

此时由于hypervisor和host os位于不同的异常等级,因此它们之间的调用都需要通过异常完成,该流程需要执行上下文的保存和恢复,因此会影响hypervisor的效率。为了解决该问题,arm在armv8.1架构之后增加了vhe特性,从而使得host os不经改动就能运行于EL2中。


vhe主要是为了支持将host os运行在EL2中,以提高hypervisor和虚拟机之间的切换代价。

由于os内核(如linux)被设计为访问EL1异常等级下的系统寄存器,因此为了在不修改代码的情况下使其实际访问EL2寄存器,vhe实现了**寄存器重定向功能。**即在使能vhe之后,所有EL1寄存器的访问操作都被硬件转换为对相应EL2寄存器的访问,如OS访问ttbr0_el1寄存器,则会按下图方式被重定向到ttbr0_el2:

img

当然这里还有一个问题,hypervisor实际上还是有访问实际el1寄存器的需求,因此vhe也对其做了扩展,当访问实际el1寄存器时,则需要使用新的特殊指令。如需要访问实际的ttbr0_el1时,则可通过下图所示访问ttbr0_el12的方式实现:

img

当支持vhe且hcr.tge被设置后,不管hcr_el2.imo/fmo/amo是否被设置,所有EL0和EL1的异常都会被路由到EL2中。有了以上扩展之后,host os就可以不经任何修改,而像运行在EL1中一样运行于EL2中,此时系统架构变为如下形式:

img


SVE在EL3、EL2和EL1级别上添加了分层陷阱和使能控制。这些控制是通过以下系统寄存器字段来实现的:

  • CPTR_EL3.EZ
  • CPTR_EL2.TZ, when HCR_EL2.E2H == 0
  • CPTR_EL2.ZEN, when HCR_EL2.E2H == 1
  • CPACR_EL1.ZEN

其中,guest os运行在EL1,hypervisor运行在EL2,即 CPACR_EL1 控制了从 guest user/EL0 到 guest os/EL1 的trap,而 CPTR 控制了从 guest os/EL1 到 hypervisor/EL2 的trap。


1.2 CPACR_EL1

作用:控制跟踪、SVE、高级SIMD和浮点功能的访问。

当在当前安全状态下实施并启用EL2,并且HCR_EL2.{E2H, TGE} == {1, 1}时,该寄存器中的字段对EL0和EL1的执行没有影响。在这种情况下,由CPTR_EL2提供的控制被使用。

FPEN

当执行状态为EL1和EL0的指令访问高级SIMD和浮点寄存器时,会被陷阱捕获。如果EL2被实现并在当前安全状态中启用,且HCR_EL2.TGE为1,则会报告为ESR_ELx.EC值0x00到EL2。如果EL2未启用,则报告为ESR_ELx.EC值0x07到EL1。

  • 在AArch64状态下,对FPCR、FPSR以及任何SIMD和浮点寄存器V0-V31的访问,包括它们作为D0-D31寄存器或S0-S31寄存器的视图;

将SVE指令在EL1和EL0的执行转移到EL1,或在当前安全状态下EL2已实现且启用时转移到EL2,此时HCR_EL2.TGE为1。异常使用ESR_ELx.EC值0x07进行报告。

由于CPACR_EL1.ZEN引发的陷阱具有优先权,优先于由CPACR_EL1.FPEN引发的陷阱。

image-20231225142237455

ZEN

将SVE指令和直接访问ZCR_EL1系统寄存器的指令在EL1和EL0执行时陷入EL1,或在当前安全状态下实现和启用EL2时陷入EL2,且HCR_EL2.TGE为1。异常使用ESR_ELx.EC值0x19进行报告。由于CPACR_EL1.ZEN引发的陷阱优先于由CPACR_EL1.FPEN引发的陷阱。

image-20231225142437755

MRS/MSR CPACR_EL1

image-20231225143056837

访问重定向。

1.3 CPTR_EL2

作用:控制将对CPACR、CPACR_EL1、跟踪、活动监视器、SVE以及高级SIMD和浮点功能的访问陷入到EL2。

image-20231225142700481

  • 在当前安全状态下,当EL2被启用时,对访问高级SIMD和浮点寄存器的指令在EL2、EL1和EL0的执行陷入到EL2。异常情况使用ESR_ELx.EC值0x07进行报告。

  • 在当前安全状态下,将SVE指令在EL2、EL1和EL0级别的执行陷入到EL2。异常使用ESR_ELx.EC值0x07进行报告。

由于CPTR_EL2.ZEN导致的陷阱优先于由CPTR_EL2.FPEN导致的陷阱:

image-20231225143808211

image-20231225144354227

疑问:CPTR_EL2.FPEN/ZEN = 0b01,这种配置应用于什么场景?

1
2
3
4
5
#define CPACR_EL1_FPEN_EL1EN	(BIT(20)) /* enable EL1 access */				//0b01
#define CPACR_EL1_FPEN_EL0EN (BIT(21)) /* enable EL0 access, if EL1EN set */ //0b11

#define CPACR_EL1_ZEN_EL1EN (BIT(16)) /* enable EL1 access */
#define CPACR_EL1_ZEN_EL0EN (BIT(17)) /* enable EL0 access, if EL1EN set */

允许EL1执行,但禁止EL0执行

2 KVM: FPSIMD/SVE虚拟化支持

2.1 数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
struct kvm_vcpu_arch {
struct kvm_cpu_context ctxt;
/*
* Guest floating point state
*
* The architecture has two main floating point extensions,
* the original FPSIMD and SVE. These have overlapping
* register views, with the FPSIMD V registers occupying the
* low 128 bits of the SVE Z registers. When the core
* floating point code saves the register state of a task it
* records which view it saved in fp_type.
*/
void *sve_state;
enum fp_type fp_type;
unsigned int sve_max_vl;
u64 svcr;

/* Values of trap registers for the guest. */
u64 hcr_el2;
u64 cptr_el2;

/* Ownership of the FP regs */
enum {
FP_STATE_FREE,
FP_STATE_HOST_OWNED,
FP_STATE_GUEST_OWNED,
} fp_state;

struct user_fpsimd_state *host_fpsimd_state; /* hyp VA */ // host_fpsimd_state
}

struct kvm_cpu_context {
struct user_pt_regs regs; /* sp = sp_el0 */
struct user_fpsimd_state fp_regs; // guest_fpsimd_state
u64 sys_regs[NR_SYS_REGS];
struct kvm_vcpu *__hyp_running_vcpu;
};

struct cpu_fp_state {
struct user_fpsimd_state *st;
void *sve_state;
void *sme_state;
u64 *svcr;
unsigned int sve_vl;
unsigned int sme_vl;
enum fp_type *fp_type;
enum fp_type to_save;
};

2.2 框架分析

相关函数:

1
2
3
4
5
6
7
8
9
10
11
12
// arch/arm64/kvm/fpsimd.c 

/* Guest/host FPSIMD coordination helpers */
int kvm_arch_vcpu_run_map_fp(struct kvm_vcpu *vcpu);
void kvm_arch_vcpu_load_fp(struct kvm_vcpu *vcpu);
void kvm_arch_vcpu_ctxflush_fp(struct kvm_vcpu *vcpu);
void kvm_arch_vcpu_ctxsync_fp(struct kvm_vcpu *vcpu);
void kvm_arch_vcpu_put_fp(struct kvm_vcpu *vcpu);
void kvm_vcpu_unshare_task_fp(struct kvm_vcpu *vcpu);




代码流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// vcpu首次运行和再次进入循环
kvm_arch_vcpu_ioctl_run
+-> vcpu_load -> kvm_vcpu_arch_load
+-> kvm_arch_vcpu_load_fp
+-> //goto LOAD_FP
+-> while(ret > 0) //kvm第一层循环
+-> kvm_arch_vcpu_ctxflush_fp
+-> //goto FLUSH_FP
/* Enter the guest... */
+-> kvm_arm_vcpu_enter_exit
// arch/arm64/kvm/hyp/vhe/switch.c
+-> __kvm_vcpu_run_vhe
+-> __activate_traps
+-> //AC_TRAPS
+-> do { //kvm第二层循环
/* Jump in the fire! */
exit_code = __guest_enter(vcpu);
/* And we're baaack! */
} while (fixup_guest_exit(vcpu, &exit_code));
+-> __guest_enter
+-> fixup_guest_exit
+-> kvm_hyp_handle_exit
/*
[ESR_ELx_EC_SVE] = kvm_hyp_handle_fpsimd,
[ESR_ELx_EC_FP_ASIMD] = kvm_hyp_handle_fpsimd,
*/
+-> kvm_hyp_handle_fpsimd //guest触发fpsimd/sve_trap
+-> __deactivate_traps
+-> //DEAC_TRAPS
+-> if (vcpu->arch.fp_state == FP_STATE_GUEST_OWNED)
__fpsimd_save_fpexc32(vcpu);
/* Back from guest... */
+-> kvm_arch_vcpu_ctxsync_fp
+-> //SYNC_FP
+-> ret = handle_exit(vcpu, ret);
+-> vcpu_put -> kvm_vcpu_arch_put
+-> kvm_arch_vcpu_put_fp
+-> //PUT_FP

//LOAD_FP
/*
* Prepare vcpu for saving the host's FPSIMD state and loading the guest's.
* The actual loading is done by the FPSIMD access trap taken to hyp.
*
* Here, we just set the correct metadata to indicate that the FPSIMD
* state in the cpu regs (if any) belongs to current on the host.
*/
kvm_arch_vcpu_load_fp
+-> system_supports_fpsimd() == TRUE
+-> fpsimd_kvm_prepare
/*
* We will check TIF_FOREIGN_FPSTATE just before entering the
* guest in kvm_arch_vcpu_ctxflush_fp() and override this to
* FP_STATE_FREE if the flag set.
*/
+-> if (test_and_clear_thread_flag(TIF_SVE)) {
sve_to_fpsimd(current);
current->thread.fp_type = FP_STATE_FPSIMD;
}
+-> vcpu->arch.fp_state = FP_STATE_HOST_OWNED;
+-> vcpu_clear_flag(vcpu, HOST_SVE_ENABLED);
if (read_sysreg(cpacr_el1) & CPACR_EL1_ZEN_EL0EN)
vcpu_set_flag(vcpu, HOST_SVE_ENABLED);
+-> //假设不支持SME

//FLUSH_FP
//如果进入guest前触发中断,TIF_FOREIGN_FPSTATE被清理掉,当前CPU上实时保存的是一份和host/guest无关的fpsimd状态
//注意,该函数执行前需要preempt_disable()/local_irq_disable()
/*
* Called just before entering the guest once we are no longer preemptable
* and interrupts are disabled. If we have managed to run anything using
* FP while we were preemptible (such as off the back of an interrupt),
* then neither the host nor the guest own the FP hardware (and it was the
* responsibility of the code that used FP to save the existing state).
*/
void kvm_arch_vcpu_ctxflush_fp(struct kvm_vcpu *vcpu)
{
if (test_thread_flag(TIF_FOREIGN_FPSTATE))
vcpu->arch.fp_state = FP_STATE_FREE;
}

//AC_TRAPS/DEAC_TRAPS
/* Check whether the FP regs are owned by the guest */
static inline bool guest_owns_fp_regs(struct kvm_vcpu *vcpu)
{
return vcpu->arch.fp_state == FP_STATE_GUEST_OWNED;
}

/*
进入guest前,检查CPU的fp_state的持有者是否为guest:
1) 首次进入fp_state持有者为host或free,禁止guest在EL0/EL1执行fpsimd/sve指令
2) 如果为guest,其支持SVE,则允许guest在EL0/EL1执行fpsimd/sve指令
*/
__activate_traps
val &= ~(CPACR_EL1_ZEN_EL0EN | CPACR_EL1_ZEN_EL1EN;
if (guest_owns_fp_regs(vcpu)) {
if (vcpu_has_sve(vcpu))
val |= CPACR_EL1_ZEN_EL0EN | CPACR_EL1_ZEN_EL1EN;
} else {
val &= ~(CPACR_EL1_FPEN_EL0EN | CPACR_EL1_FPEN_EL1EN);
__activate_traps_fpsimd32(vcpu);
}
__deactivate_traps


//SYNC_FP
/*
* Called just after exiting the guest. If the guest FPSIMD state
* was loaded, update the host's context tracking data mark the CPU
* FPSIMD regs as dirty and belonging to vcpu so that they will be
* written back if the kernel clobbers them due to kernel-mode NEON
* before re-entry into the guest.
*/
/*
在退出客户机后立即调用。如果加载了客户机的FPSIMD状态,更新主机的上下文跟踪数据,
将CPU的FPSIMD寄存器标记为脏的,并属于虚拟CPU,
这样如果内核在重新进入客户机之前由于内核模式NEON而破坏它们,它们将被写回。
*/
void kvm_arch_vcpu_ctxsync_fp(struct kvm_vcpu *vcpu)
{
struct cpu_fp_state fp_state;

WARN_ON_ONCE(!irqs_disabled());

//如果guest作为CPU fpsimd/sve寄存器组持有者,将vcpu->arch的相关内容全部拉取下来,保存至本地cpu_fp_state
if (vcpu->arch.fp_state == FP_STATE_GUEST_OWNED) {

/*
* Currently we do not support SME guests so SVCR is
* always 0 and we just need a variable to point to.
*/
fp_state.st = &vcpu->arch.ctxt.fp_regs;
fp_state.sve_state = vcpu->arch.sve_state;
fp_state.sve_vl = vcpu->arch.sve_max_vl;
fp_state.sme_state = NULL;
fp_state.svcr = &vcpu->arch.svcr;
fp_state.fp_type = &vcpu->arch.fp_type;

if (vcpu_has_sve(vcpu))
fp_state.to_save = FP_STATE_SVE;
else
fp_state.to_save = FP_STATE_FPSIMD;

//用本地cpu_fp_state刷新per-cpu变量`fpsimd_last_state`,让其指向最近的上下文 (guest)
fpsimd_bind_state_to_cpu(&fp_state);

//清除TIF_FOREIGN_FPSTATE,该标识用于控制vcpu线程的两种状态(host/guest)
clear_thread_flag(TIF_FOREIGN_FPSTATE);
}
}

//PUT_FP
/*
* Write back the vcpu FPSIMD regs if they are dirty, and invalidate the
* cpu FPSIMD regs so that they can't be spuriously reused if this vcpu
* disappears and another task or vcpu appears that recycles the same
* struct fpsimd_state.
*/
/*
如果虚拟中央处理器(vCPU)的浮点/ SIMD 寄存器(FPSIMD regs)已被修改,将其内容写回,
并使该 CPU 的 FPSIMD 寄存器无效。
这样做的目的是防止在该 vCPU 消失后,出现另一个任务或 vCPU 并且重新使用相同的 fpsimd_state 结构时发生误用
*/
void kvm_arch_vcpu_put_fp(struct kvm_vcpu *vcpu)
{
if (vcpu->arch.fp_state == FP_STATE_GUEST_OWNED) {
if (vcpu_has_sve(vcpu)) {
__vcpu_sys_reg(vcpu, ZCR_EL1) = read_sysreg_el1(SYS_ZCR);

/* Restore the VL that was saved when bound to the CPU */
if (!has_vhe())
sve_cond_update_zcr_vq(vcpu_sve_max_vq(vcpu) - 1,
SYS_ZCR_EL1);
}

fpsimd_save_and_flush_cpu_state(); //??? why not defer to userland
} else if (has_vhe() && system_supports_sve()) {
/*
* The FPSIMD/SVE state in the CPU has not been touched, and we
* have SVE (and VHE): CPACR_EL1 (alias CPTR_EL2) has been
* reset by kvm_reset_cptr_el2() in the Hyp code, disabling SVE
* for EL0. To avoid spurious traps, restore the trap state
* seen by kvm_arch_vcpu_load_fp():
*/
if (vcpu_get_flag(vcpu, HOST_SVE_ENABLED))
sysreg_clear_set(CPACR_EL1, 0, CPACR_EL1_ZEN_EL0EN);
else
sysreg_clear_set(CPACR_EL1, CPACR_EL1_ZEN_EL0EN, 0);
}
}

// guest首次fpsimd/sve_trap陷入hyp
/*
* We trap the first access to the FP/SIMD to save the host context and
* restore the guest context lazily.
* If FP/SIMD is not implemented, handle the trap and inject an undefined
* instruction exception to the guest. Similarly for trapped SVE accesses.
*/
static bool kvm_hyp_handle_fpsimd(struct kvm_vcpu *vcpu, u64 *exit_code)
{
bool sve_guest;
u8 esr_ec;
u64 reg;

if (!system_supports_fpsimd())
return false;

sve_guest = vcpu_has_sve(vcpu);
esr_ec = kvm_vcpu_trap_get_class(vcpu);

/* Only handle traps the vCPU can support here: */
switch (esr_ec) {
case ESR_ELx_EC_FP_ASIMD:
break;
case ESR_ELx_EC_SVE:
if (!sve_guest)
return false;
break;
default:
return false;
}

/* Valid trap. Switch the context: */

/* First disable enough traps to allow us to update the registers */
if (has_vhe() || has_hvhe()) {
reg = CPACR_EL1_FPEN_EL0EN | CPACR_EL1_FPEN_EL1EN;
if (sve_guest)
reg |= CPACR_EL1_ZEN_EL0EN | CPACR_EL1_ZEN_EL1EN;

sysreg_clear_set(cpacr_el1, 0, reg);
} else {
reg = CPTR_EL2_TFP;
if (sve_guest)
reg |= CPTR_EL2_TZ;

sysreg_clear_set(cptr_el2, reg, 0);
}
isb();

/* Write out the host state if it's in the registers */
// host_sve_sate 为什么不保存?
if (vcpu->arch.fp_state == FP_STATE_HOST_OWNED)
__fpsimd_save_state(vcpu->arch.host_fpsimd_state);

/* Restore the guest state */
if (sve_guest)
__hyp_sve_restore_guest(vcpu);
else
__fpsimd_restore_state(&vcpu->arch.ctxt.fp_regs);

/* Skip restoring fpexc32 for AArch64 guests */
if (!(read_sysreg(hcr_el2) & HCR_RW))
write_sysreg(__vcpu_sys_reg(vcpu, FPEXC32_EL2), fpexc32_el2);

vcpu->arch.fp_state = FP_STATE_GUEST_OWNED;

return true;
}

  • 关于TIF_FOREIGN_FPSTATE在哪些场景下设置、取消、更新?

该标识的作用是:判断当前pCPU上加载的是否为被调度线程的最近用户态上下文,其值为FALSE时可以减少一次上下文的恢复工作。该标识的引入可以将调度执行流中的SIMD恢复工作解耦出来并推迟到返回用户态之前。

一些情况会影响TIF_FOREIGN_FPSTATE,除了该标识的合法设置方式外(通过两个变量以判断同步情况)。因为除了用户态,内核态也可能使用FPSIMD能力,此时硬件FPSIMD状态的持有者就不再是用户了,用户应该即时保存这部分状态,同时设置TIF_FOREIGN_FPSTATE。


  • 内核态中断/抢占场景下,FPSIMD状态应该如何维护?

调用链:preempt_enable => _schedule => context_switch => __switch_to => fpsimd_thread_switch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>void fpsimd_thread_switch(struct task_struct *next)
>{
bool wrong_task, wrong_cpu;

if (!system_supports_fpsimd())
return;

__get_cpu_fpsimd_context();

/* Save unsaved fpsimd state, if any: */
fpsimd_save();

/*
* Fix up TIF_FOREIGN_FPSTATE to correctly describe next's
* state. For kernel threads, FPSIMD registers are never loaded
* and wrong_task and wrong_cpu will always be true.
*/
wrong_task = __this_cpu_read(fpsimd_last_state.st) !=
&next->thread.uw.fpsimd_state;
wrong_cpu = next->thread.fpsimd_cpu != smp_processor_id();

update_tsk_thread_flag(next, TIF_FOREIGN_FPSTATE,
wrong_task || wrong_cpu);

__put_cpu_fpsimd_context();
>}

  • vcpu首次进入guest,guest首次执行FPSIMD指令陷入hypervisor,保存恢复上下文后再次返回guest

  • guest已持有FPSIMD状态,再次陷入hypervisor,处理后返回guest执行:(陷入原因)

  • vcpu线程退出,hypervisor处理后直接返回

  • 内层循环

  • 外层循环

  • vcpu线程退出,需要在用户态处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
kvm_arch_vcpu_put_fp
+-> fpsimd_save_and_flush_cpu_state
+-> fpsimd_save()
+-> fpsimd_flush_cpu_state()

/*
* Ensure FPSIMD/SVE storage in memory for the loaded context is up to
* date with respect to the CPU registers. Note carefully that the
* current context is the context last bound to the CPU stored in
* last, if KVM is involved this may be the guest VM context rather
* than the host thread for the VM pointed to by current. This means
* that we must always reference the state storage via last rather
* than via current, if we are saving KVM state then it will have
* ensured that the type of registers to save is set in last->to_save.
*/
static void fpsimd_save(void);
这段话大致意思是要确保内存中加载的上下文中的FPSIMD/SVE存储与CPU寄存器的状态是最新的。需要注意的是,当前上下文是最后绑定到CPU的上下文,存储在last中。如果涉及到KVM,这可能是指与current指向的VM主线程相关的guest VM上下文,而不是主机线程的上下文。这意味着我们必须始终通过last引用状态存储,而不是通过current引用。如果正在保存KVM状态,那么last->to_save中将确保设置要保存的寄存器类型。

/*
* Invalidate any task's FPSIMD state that is present on this cpu.
* The FPSIMD context should be acquired with get_cpu_fpsimd_context()
* before calling this function.
*/
static void fpsimd_flush_cpu_state(void)
{
WARN_ON(!system_supports_fpsimd());
__this_cpu_write(fpsimd_last_state.st, NULL);

/*
* Leaving streaming mode enabled will cause issues for any kernel
* NEON and leaving streaming mode or ZA enabled may increase power
* consumption.
*/
if (system_supports_sme())
sme_smstop();

set_thread_flag(TIF_FOREIGN_FPSTATE);
}

do_notify_resume
+-> fpsimd_restore_current_state
/*
* Load the userland FPSIMD state of 'current' from memory, but only if the
* FPSIMD state already held in the registers is /not/ the most recent FPSIMD
* state of 'current'. This is called when we are preparing to return to
* userspace to ensure that userspace sees a good register state.
*/
void fpsimd_restore_current_state(void)
{
/*
* TIF_FOREIGN_FPSTATE is set on the init task and copied by
* arch_dup_task_struct() regardless of whether FP/SIMD is detected.
* Thus user threads can have this set even when FP/SIMD hasn't been
* detected.
*
* When FP/SIMD is detected, begin_new_exec() will set
* TIF_FOREIGN_FPSTATE via flush_thread() -> fpsimd_flush_thread(),
* and fpsimd_thread_switch() will set TIF_FOREIGN_FPSTATE when
* switching tasks. We detect FP/SIMD before we exec the first user
* process, ensuring this has TIF_FOREIGN_FPSTATE set and
* do_notify_resume() will call fpsimd_restore_current_state() to
* install the user FP/SIMD context.
*
* When FP/SIMD is not detected, nothing else will clear or set
* TIF_FOREIGN_FPSTATE prior to the first return to userspace, and
* we must clear TIF_FOREIGN_FPSTATE to avoid do_notify_resume()
* looping forever calling fpsimd_restore_current_state().
*/
if (!system_supports_fpsimd()) {
clear_thread_flag(TIF_FOREIGN_FPSTATE);
return;
}

get_cpu_fpsimd_context();

if (test_and_clear_thread_flag(TIF_FOREIGN_FPSTATE)) {
task_fpsimd_load();
fpsimd_bind_task_to_cpu();
}

put_cpu_fpsimd_context();
}
  • vcpu线程被host调度

//TODO


最后整理于 2025-11-11