User-mode task switching

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.
Post Reply
CygnusOlor
Posts: 1
Joined: Mon Jul 10, 2023 8:46 pm

User-mode task switching

Post by CygnusOlor »

I'm a mega noob to OSDev, so my apologies if the answer is obvious :?

So I followed the Cooperative Multitasking tutorial on the wiki because I'm struggling a lot with System V assembly function parameters and couldn't get my own to work. It worked, and now I have a basic kernel mode multitasking system. When you have the same memory page maps and can switch to another process without having to switch rings, it's way easier to switch tasks. It's not too hard to wire up the task switch function to IRQ 0 to make a round robin scheduler. However, I'm trying to set up features like control rings and per-program paging to distance userland from the kernel, which makes multitasking so much more confusing. In user mode, the kernel and the processes will have separate page maps. That means that I'll have to identity map a few critical pages for usage during the task switch. The task switch will happen either through IRQ 0, the current process syscalling to give up its control, or the current process dying. So I'm planning on the IRQ 0 switch to be something like this:
  • "Current task" pointer used to determine next task is switched to the former current task's "next task." This will be a pointer to a physical address as the kernel has identity mapped pages and it will only be used by the kernel.
  • Certain kernel pages will be identity mapped into the next task's page as read only pages. These are the page containing EIP and the subsequent page; the page containing the "from" task registers and the subsequent page; the page containing the "to" task registers and the subsequent page; and the page containing switch_task() and the subsequent page. I assume I will have to identity map all pages containing interrupt handlers too. (Maybe it's better to just identity map the whole kernel as read only?) The kernel can write to read-only pages, so I don't have to worry about that.
  • The framebuffer (0xB8000) will be identity mapped too, but as read-write.
  • The switch_task() function saves the current task's state (including EIP, ESP, and CR3) and loads the next task's.
  • Return from the interrupt, returning us to user mode.
The program's code will be loaded at maybe 0x30000000. I will have the stack and some key data for switching back to the kernel's CR3, etc. closer to 0x0. Since the kernel has everything identity mapped including the interrupt handlers, I won't need to do complicated paging configuration like this switching to ring 0 for an IRQ — only when switching back to ring 3. I wanted to ask — is this a good concept? I don't have access to my computer right now so I can't actually code it.
Octocontrabass
Member
Member
Posts: 5560
Joined: Mon Mar 25, 2013 7:01 pm

Re: User-mode task switching

Post by Octocontrabass »

CygnusOlor wrote:In user mode, the kernel and the processes will have separate page maps.
In a typical OS, the kernel doesn't have its own set of page tables. Instead, each set of user page tables is split between user mappings that are different for each user and kernel mappings that are the same everywhere. For 32-bit x86, there are no limitations on how user and kernel addresses are assigned, but it's common to split the address space at the 2GB or 3GB mark, with all addresses below that point available to the user and the rest available to the kernel. (For 64-bit x86, there are no hardware limitations, but compilers and binary formats are optimized for user mappings in the lower half and kernel mappings in the upper half.)

Some OSes do have separate page tables for the kernel, either to increase the available virtual address space for users (because fewer kernel-only pages will need to be mapped everywhere) or to work around specific CPU vulnerabilities. Most hobby OS developers aren't worried about either of those things.

Examples on the wiki and elsewhere are written on the assumption that the kernel is mapped in every set of page tables, which means it's safe for the kernel to set CR3 at any time. If you're not going to map the kernel in every set of page tables, you'll need to ensure you only set CR3 at kernel entry and exit points, while accessing only the few kernel-only pages that are mapped everywhere.
CygnusOlor wrote:That means that I'll have to identity map a few critical pages for usage during the task switch.
You don't need to identity-map anything unless you're disabling paging, and you shouldn't be disabling paging.
CygnusOlor wrote:Return from the interrupt, returning us to user mode.
You might not be returning from an interrupt anymore. The next task could have saved its state somewhere else, like a syscall.
CygnusOlor wrote:I wanted to ask — is this a good concept?
It sounds unnecessarily complicated.
Post Reply