Link Search Menu Expand Document

Initialization

Unlike GPRs, the x86 FPU is different since it doesn’t have specific floating point registers that are independently addressable. Instead, it has a stack of registers, and the programmer interacts with the FPU by pushing and popping from the stack. For example, to compute the square root of \(\pi\), you would first push \(\pi\) onto the stack, and then run the fsqrt instruction. This instruction will pop the top of the stack, compute the square root of that value, and push it onto the top of the stack. Now, \(\sqrt{\pi}\) is at the top of the stack.

Newly-created threads and processes generally cannot assume anything about the contents of GPRs, meaning GPRs do not have to be initialized to any particular values when a new thread or process is created. On the contrary, newly-created threads and processes in general should be able to assume that the FPU is clean (i.e. initialized/reset properly), meaning the FPU needs to be initialized at the operating system startup, thread/process creation, and thread/context switches (e.g. interrupts, syscalls).

The idea of saving FPU registers is very similar to how the GPRs are saved during a thread switch or context switches. As a result, it will be helpful to understand interrupt handling code in threads/interrupt.h and threads/intr-stubs.S for context switches, threads/switch.h and threads/switch.S for thread switching. In particular, focus on the data structures struct intr_frame and struct switch_threads_frame and methods switch_threads, intr_entry, and intr_exit.

Implementation wise, saving GPRs and floating-point registers will differ since you will not be interacting with each FPU register individually. Instead, you will be working with the 108-byte FPU save state as a whole which looks like this:

SizeData
2Control Word
2(unused)
2Status Word
2(unusued)
2Tag Word
2(unused)
4Instruction Pointer
2Code Segment
2(unused)
4Operand address
2Data Segment
2(unused)
10ST(0)
10ST(1)
10ST(2)
10ST(3)
10ST(4)
10ST(5)
10ST(6)
10ST(7)

This diagram is mainly for reference purposes, meaning it’s not necessary to understand each individual component of the FPU save state. Instead, you should figure out ways to interact with this FPU save state as a whole.

During the operating system startup, the existing set of FPU registers don’t need to be saved since no other process or thread is using them. However, when initializing the FPU for another thread/process, you must make sure the current thread (i.e. the thread that is creating the new thread/process) does not lose it’s own floating point data. A simple way to accomplish this is by saving the current FPU registers into a temporary location (e.g. local variable), initializing a clean state of FPU registers, save these FPU registers into the desired destination, and restore the registers from the aforementioned temporary location.