1.硬件方面:
Armv8提供了两个寄存器用于控制“FPU虚拟化”——CPTR_EL2、CPACR_EL1。我们截取手册上关键位置。
CPTR_EL2
当CPTR_EL2寄存器的bit10为0时,FPU相关操作不被trapped,相反为1,则所有意欲访问FPU部件的指令(不管是EL0、EL1还是EL2)都会被trap到EL2。
CPACR_EL1
CPACR_EL1寄存器则主要控制EL0和EL1访问FPU的行为,如果FPEN位为11则不被trapped,其他值则会被trap到EL1或者EL2,这里EL1寄存器能控制trap到EL2则要得益于HCR_EL2寄存器的TGE位,如下:
如果此位设置为1则所有路由到EL1的异常会被路由到EL2。
2. 软件实现方面
搜索fiasco代码,FPU控制的地方主要有3处:
- arm_ext_vcpu_switch_to_host,当要陷出到uvmm的时候会调用此函数。通过一段嵌入式汇编保存guest的寄存器,加载host寄存器,与FPU相关的主要是以下两条。
asm volatile ("mrs %0, CPACR_EL1" : "=r"(v->guest_regs.cpacr)); asm volatile ("msr CPACR_EL1, %0" : : "r"(3UL << 20)); |
这里的“v”就是上文cpu虚拟化提到的保存虚拟机状态域的Vm_state结构体,它会在arch_init_vcpu_state中对guest和host虚拟化相关寄存器成员进行初始化(这里我们发现v->guest_regs.cpacr成员并没有初始化!!)。
- arm_ext_vcpu_switch_to_guest,当要陷入到guestos时会调用此函数。FPU相关的主要是这条语句:asm volatile ("msr CPACR_EL1, %0" : : "r"(v->guest_regs.cpacr));利用保存在Vm_state中的cpacr值恢复寄存器。
- Contex switch中不但会保存通用寄存器、虚拟化相关所有寄存器还有一个专门控制浮点虚拟化的switch_fpu函数。这个函数中通过调用Fpu类的disable和enable操控FPU开关,下面是这两个函数的实现:
PUBLIC inline void Fpu::enable() { Mword dummy; __asm__ __volatile__ ( "mrs %0, CPTR_EL2 \n" //保存CPTR_EL2值到dummy "bic %0, %0, #(1 << 10) \n" //清除dummy第10位(设置EL2不trap) "msr CPTR_EL2, %0 \n" //存储dummy值到CPTR_EL2 "msr CPACR_EL1, %1 \n" //将_capcr_el1值载入CPACR_EL1(EL1不trap) : "=&r" (dummy) : "r" (_capcr_el1) ); } |
PUBLIC inline void Fpu::disable() { Mword dummy, tmp; __asm__ __volatile__ ( "mrs %0, CPTR_EL2 \n" //保存CPTR_EL2值到dummy "tbnz %0, #10, 1f \n" //如果第10位为1,跳到前面标号“1” "mrs %1, CPACR_EL1 \n" //保存CPACR_EL1到tmp "str %1, %[cpacr] \n" //存储tmp值到_capcr_el1 "msr CPACR_EL1, %[cpacr_on] \n" //设置CPACR_EL1 “不trap” "orr %0, %0, #(1 << 10) \n" //设置dummy第10位为1 "msr CPTR_EL2, %0 \n"//dummy存入 CPTR_EL2(设置EL2 trap) "1: \n" : "=&r" (dummy), "=&r"(tmp), [cpacr]"+m"(_capcr_el1) : [cpacr_on]"r"(3UL << 20));h } |
Mword _capcr_el1 = (3UL << 20); |
switch_fpu函数定义如下:
void Context::switch_fpu(Context *t) { Fpu &f = Fpu::fpu.current(); if (f.is_owner(this)) //如果当前contex拥有FPU,在切换之前要把它禁掉 f.disable(); else if (f.is_owner(t) && !(t->state() & Thread_vcpu_fpu_disabled)) f.enable(); //如果对端contex是拥有者且未设置vcpu_fpu禁能标志,则将其使能 } |
解释一下:一个CPU对应一个FPU部件,所以在一个CPU上实现上下文切换时,同一时间只能有一个contex拥有FPU。所以,如果当前是拥有者,则disable,但是“拥有者”属性可以暂时不去掉,等下一个contex真正访问的时候再做ower切换。同理,浮点寄存器保存也是这样,在上下文切换时并不保存,等下一个真正需要访问这些寄存器时,再做保存修改。这叫做“lazy store”惰性保存,可以节约contex切换的时间。
当一个contex第一次访问FPU时会产生fpu异常(ec号为7),fiasco会捕捉到此异常并做好处理,如下:
Thread::switchin_fpu(bool alloc_new_fpu = true) { …… // Enable the FPU before accessing it, otherwise recursive trap f.enable();
// Save the FPU state of the previous FPU owner (lazy) if applicable if (f.owner()) f.owner()->spill_fpu(); //保存前拥有者FPU相关寄存器
// Become FPU owner and restore own FPU state f.restore_state(fpu_state()); //恢复当前contex的FPU相关寄存器
state_add_dirty(Thread_fpu_owner); f.set_owner(this); //设置当前contex为拥有者 return 1; } |