前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux内核13_1-进程切换是对FPU单元的处理_X86

Linux内核13_1-进程切换是对FPU单元的处理_X86

作者头像
Tupelo
发布2022-08-15 15:48:42
7130
发布2022-08-15 15:48:42
举报
文章被收录于专栏:嵌入式ARM和Linux

每一种技术的出现必然是因为某种需求。正因为人的本性是贪婪的,所以科技的创新才能日新月异。

1 简介

从英特尔80486DX开始,算术浮点单元(FPU)就已经被集成到CPU中了。但是之所以还继续使用数学协处理器,是因为以前使用专用芯片进行浮点运算,所以这算是旧习惯的沿用吧。为了与旧CPU架构模型兼容,指令的使用方式与整数运算一样,只是使用了转义指令,也就是在原有的指令基础上加上前缀,组成新的指令,这些前缀的范围是0xd8-0xdf。使用这些指令可以操作CPU中的浮点寄存器。很显然,使用这些浮点运算指令的进程在进程切换的时候,需要保存属于它的硬件上下文中的浮点寄存器内容。

随后的奔腾系列处理器,因特尔引入了一组新的汇编指令。它们被称为MMX指令,用来支持多媒体应用的加速执行。MMX指令也是作用于FPU单元的浮点寄存器。这样的设计缺点是,内核开发者无法混合使用转义浮点指令和MMX指令;优点是内核开发者可以使用相同的进程切换代码来保存浮点单元和MMX的状态。

MMX指令之所以能够加速多媒体应用的执行,是因为它们在处理器中专门引入了单指令多数据(SIMD)指令流水线。奔腾III扩展了SIMD指令:引入了SSE扩展(单指令多数据流扩展),包含8个128位的寄存器,称为XMM寄存器,通过它们可以大大增加浮点数的处理。这些寄存器是独立的,和FPU和MMX寄存器没有重叠,所以SSE扩展和FPU/MMX指令可以混合使用。奔腾4又又引入了新的扩展:SSE2扩展,是在SSE基础上的扩展,支持更高精度的浮点数。SSE2扩展和SSE扩展使用相同的XMM寄存器。

X86微处理器不会自动在TSS中保存FPU、MMX和XMM寄存器。但是,从硬件上,支持内核只保存所需要的寄存器。具体方法就是在cr0寄存器中包含一个TS(任务切换)标志,标志设置的时机如下所示:

  • 每次执行硬件上下文切换,TS标志被置。
  • 每次执行ESCAPE、MMX、SSE或SSE2指令,同时TS标志被置,则控制单元就会发出Device not available的异常。

从上面可以看出,只有执行浮点运算的时候才需要保存FPU、MMX和XMM相关寄存器。假设进程A正在使用协处理器,当进程A切换到进程B的时候,内核设置TS标志,且把浮点寄存器保存到进程A的任务状态段(TSS)中。如果进程B没有使用协处理器,内核不需要恢复浮点寄存器的内容。但只要进程B想要执行浮点运算或多媒体指令,CPU就会发出Device not available异常,这个异常对应的处理程序就会把浮点寄存器中的值加载到进程B的TSS段中。

2 FPU相关数据结构

Linux内核是使用什么数据结构表示FPU、MMX和XMM这些需要保存的寄存器值呢?基于x86架构的Linux内核使用i387_union类型的变量thread.i387存储这些值,该变量位于进程描述符中。i387_union如下所示:

代码语言:javascript
复制
union i387_union {
    struct i387_fsave_struct fsave;
    struct i387_fxsave_struct fxsave;
    struct i387_soft_struct soft;
};

如代码所示,这个联合体包含三个不同类型的数据结构。没有协处理器的CPU模型使用i387_soft_struct类型数据结构,这是Linux为了兼容那些使用软件模拟协处理器的旧芯片。故我们在此,不做过多描述。带有协处理器和MMX单元的CPU模型使用i387_fsave_struct数据类型。带有SSE和SSE2扩展的CPU模型使用i387_fxsave_struct数据类型。

除此之外,进程描述符还包含另外2个标志:

  • TS_USEDFPU标志 位于thread_info描述符的status成员中。表示正在进行的进程是否使用FPU、MMX或XMM寄存器。
  • PF_USED_MATH标志 位于task_struct描述符中的flags成员中。表示存储在thread.i387中的数据是否有意义。该标志被清除的时候有两种情况:
    • 调用execve()系统调用,启动新进程的时候。因为控制单元绝不会再返回到之前的程序中,所以存储在thread.i387中的数据就没有了意义。
    • 用户态正在执行的程序开始执行信号处理程序的时候。因为信号处理程序相对于正在执行的程序流来说是异步的,此时的浮点寄存器对于信号处理程序也就没有了意义。但是需要内核为进程保存thread.i387中的浮点寄存器值,等到信号处理程序终止时再恢复这些寄存器值。也就是说,允许信号处理程序使用协处理器。

2 保存FPU寄存器

我们在分析进程切换的时候,知道主要的工作都是在__switch_to()宏中完成的。而在__switch_to()宏中,执行__unlazy_fpu宏,并将先前进程的进程描述符作为参数进行传递。这个宏会检查旧进程的TS_USEDFPU标志:如果标志被设置,说明旧进程使用了FPU、MMX、SSE或SSE2指令。因此,内核必须保存相关的硬件上下文,如下所示:

代码语言:javascript
复制
if (prev->thread_info->status & TS_USEDFPU)
    save_init_fpu(prev);

函数save_init_fpu()完成保存这些寄存器的基本工作,如下所示:

  1. 将FPU寄存器的内容保存到旧进程的描述符中,然后重新初始化FPU。如果CPU还使用了SSE/SSE2扩展,还需要保存XMM寄存器的内容,然后重新初始化SSE/SSE2单元。下面的2条GNU伪汇编语言实现: asm volatile( "fxsave %0 ; fnclex" : "=m" (prev->thread.i387.fxsave) ); 开启SSE/SSE2扩展,使用下面这条汇编语言: asm volatile( "fnsave %0 ; fwait" : "=m" (prev->thread.i387.fsave) );
  2. 清除旧进程的TS_USEDFPU标志: prev->thread_info->status &= ~TS_USEDFPU;
  3. 设置cr0协处理器的TS标志。使用stts()宏实现,该宏转换成汇编语言如下所示: movl %cr0, %eax orl $8,%eax movl %eax, %cr0

4 加载FPU寄存器

新进程恢复执行的时候,浮点寄存器不能立即恢复。但是通过__unlazy_fpu()宏已经设置了cr0协处理器中的TS标志。所以,新进程第一次尝试执行ESCAPE、MMX或SSE/SSE2指令的时候,控制单元就会发出Device not available异常,内核中相关的异常处理程序就会执行math_state_restore()函数加载浮点寄存器等。新进程被标记为当前进程。

代码语言:javascript
复制
void math_state_restore()
{
    asm volatile ("clts");  /* 清除TS标志 */
    if (!(current->flags & PF_USED_MATH))
        init_fpu(current);
    restore_fpu(current);
    current->thread.status |= TS_USEDFPU;
}

该函数还会清除TS标志,以至于后来再执行FPU、MMX或SSE/SSE2指令的时候,不会再发出Device not available异常。如果PF_USED_MATH标志等于0,说明thread.i387中的内容没有意义了,init_fpu()就会复位thread.i387,并设置当前进程的PF_USED_MATH为1。然后,restore_fpu()就会把正确的值加载到FPU寄存器中。这个加载过程需要调用汇编指令fxrstor或frstor,使用哪一个取决于CPU是否支持SSE/SSE2扩展。最后,设置TS_USEDFPU标志,表示使用了浮点运算单元。

5 在内核中使用FPU、MMX和SSE/SSE2单元

当然了,内核中也可以使用FPU、MMX或SSE/SSE2硬件单元(虽然,大部分时候没有意义)。这样做的话,应该避免干扰当前用户进程执行的任何浮点运算。因此:

  • 在使用协处理器之前,内核必须调用kernel_fpu_begin(),继而调用save_init_fpu(),保存用户进程的浮点相关寄存器的内容。然后,清除cr0协处理器中的TS标志。
  • 使用完了之后,内核必须调用kernel_fpu_end(),设置TS标志。

之后,如果用户进程再执行协处理器指令的时候,math_state_restore()就会像进程切换时那样,恢复这些寄存器的内容。

但是,需要特别指出的是,如果当前用户进程正在使用协处理器时,kernel_fpu_begin()的执行时间相当长,甚至抵消了使用FPU、MMX或SSE/SSE2这些硬件单元带来的加速效果。事实上,内核只在几处地方使用它们,通常是搬动或清除大内存块或当计算校验的时候。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式ARM和Linux 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 简介
  • 2 FPU相关数据结构
  • 2 保存FPU寄存器
  • 4 加载FPU寄存器
  • 5 在内核中使用FPU、MMX和SSE/SSE2单元
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档