L4_fiasco中FPU虚拟化实现介绍

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;

}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值