That pesky FPU again
Posted: Mon Dec 30, 2019 5:05 am
Hi all,
I was just designing the task-switching for my OS, and as part of that, there is the question of FPU handling. That is, the handling of any register set besides the general purpose ones (for simplicity referred to as FPU from here on). First, there is the question of laziness or eagerness. In an age of increasing logical CPU counts, lazy FPU save seems like a bad idea, since a sleeping task with an unsaved FPU cannot be transferred to another CPU. So lazy FPU save would make the scheduler more complicated, and it already is plenty complicated.
The other thing is FPU restoration. I know that Linux uses lazy FPU restore, and this seems prudent, since apart from a slight simplification of the task-switching code, eager FPU restore doesn't really help anything, and lazy FPU restore makes for faster code (nothing is faster than the line not executed, right?). So I would argue for eager FPU save and lazy FPU restore. Any thoughts on this?
And finally, there is the matter of X86's implementation of this stuff. It is horrible. So there are at least three implementations, FSAVE, FXSAVE and XSAVE, and the latter is also extensible. My kernel is AMD64-only, so FSAVE can be ignored. My idea at the moment is to determine the size of the XSAVE area at boot time, and allocate one XSAVE area behind each task descriptor (that is, when allocated, the task descriptor will be sandwiched between the kernel stack and the XSAVE area), but the problem is that this burdens every process with the memory necessary to save FPU, but they may not need it, or at least not need all of it. And thanks to AVX-512, the register file is getting to be quite big.
However, I cannot allocate memory in the FPU fault handler, because that might fail. And if it does I have to kill the current process. Which is bad because I have no interface to tell the user that he process died for running out of memory. If allocation fails during task creation, I do. So I'm stuck between a rock (allocating too much memory, but always being able to tell the user what is going on) and a hard place (allocating too little memory, and if push comes to shove, suddenly killing processes).
More interesting still would be the question how to deal with all the different components of the XSAVE area. Only a handful of processes is ever going to use AVX-512, so why save and restore those registers all the time? My current design is to enable all the bits I know of in XCR0 at boot time and then just save and restore everything, but another possibility would be to change XCR0 on task switch, and only enable those parts the process is actually using. But that is only saving on run time, and as the Intel SDM explains, that is not going to be a lot, since the XSAVE and XRESTORE instructions already optimize for those parts of the state that are unchanged.
So, how do you guys deal with FPU in your OSes?
I was just designing the task-switching for my OS, and as part of that, there is the question of FPU handling. That is, the handling of any register set besides the general purpose ones (for simplicity referred to as FPU from here on). First, there is the question of laziness or eagerness. In an age of increasing logical CPU counts, lazy FPU save seems like a bad idea, since a sleeping task with an unsaved FPU cannot be transferred to another CPU. So lazy FPU save would make the scheduler more complicated, and it already is plenty complicated.
The other thing is FPU restoration. I know that Linux uses lazy FPU restore, and this seems prudent, since apart from a slight simplification of the task-switching code, eager FPU restore doesn't really help anything, and lazy FPU restore makes for faster code (nothing is faster than the line not executed, right?). So I would argue for eager FPU save and lazy FPU restore. Any thoughts on this?
And finally, there is the matter of X86's implementation of this stuff. It is horrible. So there are at least three implementations, FSAVE, FXSAVE and XSAVE, and the latter is also extensible. My kernel is AMD64-only, so FSAVE can be ignored. My idea at the moment is to determine the size of the XSAVE area at boot time, and allocate one XSAVE area behind each task descriptor (that is, when allocated, the task descriptor will be sandwiched between the kernel stack and the XSAVE area), but the problem is that this burdens every process with the memory necessary to save FPU, but they may not need it, or at least not need all of it. And thanks to AVX-512, the register file is getting to be quite big.
However, I cannot allocate memory in the FPU fault handler, because that might fail. And if it does I have to kill the current process. Which is bad because I have no interface to tell the user that he process died for running out of memory. If allocation fails during task creation, I do. So I'm stuck between a rock (allocating too much memory, but always being able to tell the user what is going on) and a hard place (allocating too little memory, and if push comes to shove, suddenly killing processes).
More interesting still would be the question how to deal with all the different components of the XSAVE area. Only a handful of processes is ever going to use AVX-512, so why save and restore those registers all the time? My current design is to enable all the bits I know of in XCR0 at boot time and then just save and restore everything, but another possibility would be to change XCR0 on task switch, and only enable those parts the process is actually using. But that is only saving on run time, and as the Intel SDM explains, that is not going to be a lot, since the XSAVE and XRESTORE instructions already optimize for those parts of the state that are unchanged.
So, how do you guys deal with FPU in your OSes?