While rewriting my Scheduler for Multi-CPU, I'm finding a race condition that I'm struggling to choose an elegant solution to. The Kernel uses per-thread Kernel stacks and currently (single-CPU), when I switch directly to a new thread, I put the current thread on some kind of list - possibly a ready-to-run list - then I search the ready-to-run list for a thread to run. Because it's single-CPU and interrupts and disabled, there is no chance of anything trying to run the thread I just placed on the list. Later I do something like the following…
Code: Select all
PUSHAD
... (other stuff)
MOV [currentThreadData], ESP
MOV ESP, [newThreadData]
... (other stuff, CR3 etc)
POPAD
RET
I can think of three solutions, and was hoping for some input on each and whether there is something I have overlooked...
- 1. Either a new thread state (scheduling) or a lock in the thread data. This would mean a new CPU has to wait after removing the thread from the list until the first CPU has chosen the next task to run, which I don't like as it seems like an unnecessary stall.
2. Guarantee that all code that can be used following a decision to perform a task switch avoids using the Stack. This would mean leaving Interrupts disabled from the moment I decide to yield the current task, through all code to put it on a ready-to-run, sleep or I/O queue, choosing a new Task and saving possibly large amounts of context (FPU/SSE etc).
3. Switch to a per-CPU Kernel Stack when yielding the current thread until switching to the next. This would allow me to enable IRQ's at points throughout scheduling - with task switching postponed. However, it also means I should probably just switch to per-CPU Kernel stacks instead of per-Thread Kernel stacks, which I don't like as they limit the ability to have Kernel-mode threads that I really like.