Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
mrjbom wrote:By default, I load GDT only for the kernel (see load_gdt:).
When I want to run a userland task, I will need to load gdt_userland.
Is that right?
You load kernel segments. The GDT stays the same. Before you switch to user mode, load user segments into DS, ES, FS, and GS (though I do not know the significance of user data segments). SS and CS are set by the IRET into user space. You should probably put the code to switch segments into the code that enters and leaves the kernel, i.e. the interrupt/syscall code.
nullplan wrote:Before you switch to user mode, load user segments into DS, ES, FS, and GS (though I do not know the significance of user data segments).
IRET will set DS/ES/FS/GS to the null segment if you don't load them with user segments. FS and GS are typically used as pointers to per-thread data; the details depend on the ABI.
PeterX wrote:Are you sure LDT is obsolete?
I'm sure. There's usually only one per-thread descriptor that needs to be updated, so it's faster to have the per-thread descriptor in the (per-CPU) GDT and update it there instead of dealing with a LDT.
In my OS, I ignore segmentation as much as possible. I have 5 segments in the GDT, which are kernel code, kernel data, user code, user data (which are flat segements), and one TSS. The LDT has been obsolete for 35 years now (since the release of the 386 and paging). Modern compiler are not aware of segmentation. I love OS development, but there is one word that makes me cringe, and it is segmentation.
"How did you do this?"
"It's very simple — you read the protocol and write the code." - Bill Joy
Projects: NexNix | libnex | nnpkg
nexos wrote: I have 5 segments in the GDT, which are kernel code, kernel data, user code, user data (which are flat segements), and one TSS.
If I were you, I would add an FS and GS segment, switching out the base address when switching tasks, and provide a syscall to set at least the base address (limit isn't really needed). This allows user space to implement a very cheap thread pointer. In 64-bit mode, the actual FS and GS selector doesn't matter, either, there you have FS and GS base as MSR. Actually, you could probably just set FS and GS to their respective selectors at the start, and then just reload them when switching tasks. Unlike DS and ES, FS and GS are not used normally by the C compiler, so you can just leave them whatever they were when switching to kernel mode.
Octocontrabass wrote:They are used to implement thread-local variables, following the appropriate ABI.
Yes, but that is typically not supported (and not reasonably supportable) in a kernel. You need to use special syntax for this (namely "__thread"), so if you avoid that, the code will never use FS or GS. Now, in long mode, I use GS to store the CPU pointer (pointer to CPU-local stuff), but then, I can get that pointer loaded quickly with "swapgs". Which is an option not available to the OP, since their base ISA does not include "swapgs".
However, I just realized that with the proposed scheme, they will need a CPU-local GDT, anyway, since multiple CPUs will run different processes with different FS/GS base addresses. So you might as well add a kernel data segment with a base address for CPU-local data.