跟我一起写操作系统

    返回首页    发表留言
本文作者:李德强
          第三节 处理FPU异常
 
 

        目前为止我们所操作的数字仍然是整数,如果在程序中试图操作一个浮点数将得到一个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

 

    返回首页    返回顶部
#1楼  匿名  于 2019年05月25日00:44:09 发表
 
kernel/sys_call.c: 在函数‘int_no_fpu’中:

kernel/sys_call.c:122:3: 错误: 内存输入 0 不可直接寻址

请教一下大神,编译时出现这种错误是什么原因?
  看不清?点击刷新

 

  Copyright © 2015-2023 问渠网 辽ICP备15013245号