目前为止我们所操作的数字仍然是整数,如果在程序中试图操作一个浮点数将得到一个FPU异常。例如编写一个程序:
//example_fpu.c int main(int argc, char **args) { float i = 3.14; return 0; }
在这个程序中只有一个浮点变量i,为它初始化为3.14。当运行这个程序的时候我们会得到一个叫作“无协处理器”的异常:
产生这个异常的原因是:在内核启动之后协处理器(fpu)是可用的。但是在内核开启了多任务并通过call tss切换任务时,fpu都被置为不可用。如果当前程序需要使用fpu则需要通过重置cr0的1-3位来重新开启fpu。在io.h中编写一个开启fpu的宏:
#define open_fpu() \ ({__asm__ volatile( "movl %cr0, %eax \n\t" \ "andl $0xfffffff1, %eax \n\t" \ "movl %eax, %cr0 \n\t" \ );})
在任务切换时每个任务在自己的tss中都有一套完整的cpu寄存器的值,在切换时将些值恢复到cpu的寄存器当中。这一过程是在切换任务时cpu自动完成的,但是fpu就没有自动完成切换的功能。fpu的设计者们考虑到不是每一个程序有浮点数运算,所以在切换任务时并不主动的为每个程序都保存、恢复fpu寄存器的值,而当程序需要使用fpu进程浮点数运算时则会触发一个no fpu的异常,操作系统内核需要在这个异常中断服务中开启fpu并为程序恢复fpu中寄存器的值。fpu中有很多浮点数寄存器,我们可以不用逐个的了解它们,我们可以通过汇编指令fxsave和fxrstor来将它们保存和恢复。保存和恢复的过程非常简单,共需要有512字节的内存空间来存放这些数值:
0-1 |
FCW |
80-89 |
ST3/MM3 |
2-3 |
FSW |
90-95 |
Reserved |
4 |
FTW |
96-105 |
ST4/MM4 |
5 |
Reserved |
106-111 |
Reserved |
6-7 |
FOP |
112-121 |
ST5/MM5 |
8-11 |
FPU IP |
122-127 |
Reserved |
12-13 |
CS |
128-137 |
ST6/MM6 |
14-15 |
Reserved |
138-143 |
Reserved |
16-19 |
FPU DP |
144-153 |
ST7/MM7 |
20-21 |
DS |
154-159 |
Reserved |
22-23 |
Reserved |
160-175 |
XMM0 |
24-27 |
MXCSR |
176-191 |
XMM1 |
28-31 |
MXCSR_MASK |
192-207 |
XMM2 |
32-41 |
ST0/MM0 |
208-223 |
XMM3 |
42-47 |
Reserved |
224-239 |
XMM4 |
48-57 |
ST1/MM1 |
240-255 |
XMM5 |
58-63 |
Reserved |
256-271 |
XMM6 |
64-73 |
ST2/MM2 |
272-287 |
XMM7 |
74-79 |
Reserved |
288-512 |
Reserved |
这一共512字节的数据需要用来从fpu寄存器中读出到内存,或写入到fpu寄存器。首先编写保存和恢复fpu的宏:
//save FPU #define save_fpu(fpu_addr) \ ({__asm__ volatile( "fxsave %0;" \ "finit;" :: "m"(fpu_addr) \ );}) //restore FPU #define restore_fpu(fpu_addr) \ ({__asm__ volatile("finit;fxrstor %0;" ::"m"(fpu_addr) \ );})
在pcb中加入浮点寄存器需求标识和存储空间:
typedef struct process_control_block { //进程号 u32 process_id; //任务描述段 s_tss tss; ... … //是否需要使用fpu int is_need_fpu; //浮点寄存器数据保存区 u8 *fpu_data; } s_pcb;
当出现了0x7号异常“no fpu”时说明当前pcb需要使用fpu则将is_need_fpu置为1并从内存中恢复fpu的值:
//没有浮点运算器 void int_no_fpu() { set_ds(GDT_INDEX_KERNEL_DS); set_cr3(PAGE_DIR); //打开浮点运算器 open_fpu(); //如果是第一次使用fpu不需要从内存恢复 if (pcb_cur->is_need_fpu == 0) { //设置为需要fpu pcb_cur->is_need_fpu = 1; } else { //恢复内存区数据到浮点寄存器 mmcopy(pcb_cur->fpu_data, fpu_d, FPU_SIZE); //恢复内存区数据到浮点寄存器 restore_fpu(fpu_d); } u32 cr3 = pcb_cur->tss.cr3; set_cr3(cr3); set_ds(0xf); }
在调度算法中判断准备切换的pcb是否需要fpu,如果需要则为其打开fpu,并从pcb内存中恢复fpu寄存器的值:
//如果上一次运行的pcb需要fpu,把当前fpu状态保存到上一次运行的pcb内存中 if (pcb_last_run != NULL && pcb_last_run->is_need_fpu == 1) { //打开浮点运算器 open_fpu(); //保存当前浮点寄存器内容到内存 save_fpu(fpu_d); //保存当前浮点寄存器内容到pcb mmcopy(fpu_d, pcb_last_run->fpu_data, FPU_SIZE); //关闭浮点运算器 close_fpu(); }
编写两个程序来测试fpu:
//example_fpu.c int main(int argc, char **args) { for (;;) { float i = 3.14; float j = 5.78; float k = i * j; printf("%f x %f = %f\n", i, j, k); } return 0; } //example_fpu2.c int main(int argc, char **args) { for (;;) { float i = 36.83; float j = 7.19; float k = i / j; printf("%f / %f = %f\n", i, j, k); } return 0; }
编译运行并查看结果,不再出现no fpu异常:
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git git git@github.com:magicworldos/lidqos.git subverion https://github.com/magicworldos/lidqos branch v0.26
Copyright © 2015-2023 问渠网 辽ICP备15013245号