Initialization

Unlike General Purpose Registers (GPRs), the x86 Floating Point Unit (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 to and popping from the stack. For example, to compute the square root of π, you would first push π 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{π}\) 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.

For more information about thread switching and the corresponding stack layout, please see the section about Context Switching in the PintOS documentation.

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:

Size Data
2 Control Word
2 (unused)
2 Status Word
2 (unused)
2 Tag Word
2 (unused)
4 Instruction Pointer
2 Code Segment
2 (unused)
4 Operand address
2 Data Segment
2 (unused)
10 ST(0)
10 ST(1)
10 ST(2)
10 ST(3)
10 ST(4)
10 ST(5)
10 ST(6)
10 ST(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. As a hint, go read about the x86 FNSAVE and FRSTORE instructions.

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.