-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow thread switching within kernel functions #1
Comments
A possible solution (essentially a thread-based coroutine mechanism): When a thread is going to be suspended because it is about to wait for an object, enter a locked critical section, yield, or a similar reason:
Every emulation thread (including the initial thread) will execute as follows:
A conditional variable could be used instead of the single-shot synchronization object. Of course, the thread scheduler needs to be aware that these threads are suspended, so there needs to be a flag on the Thread object indicating that they're not available for scheduling, or they could be removed entirely from the vector of threads until they are ready to execute again, at which point they're added back in. Examples of situations where this mechanism would be engaged and the corresponding conditions:
If KeSuspendThread is invoked on a thread that is already suspended for another reason, its condition should be expanded to include the SuspensionCount. Note that KeStallExecutionProcessor is used to perform time-sensitive hardware I/O operations. These operations always run on high IRQL, which means thread switches never happen. Since OpenXBOX does not strive for cycle-accurate emulation, the function is simply a no-op. This approach runs the risk of creating an excessive number of short-lived threads if the game code abuses locks, waits, timers and such. |
Approaches I've considered and discarded:
|
One more thing: proper IRQL management is now required. |
The above approach has been partially implemented, but is currently untested. Emulation state is still just a boolean indicating whether to continue running or not. |
I figured that since I'm going full on with implementing the entire kernel, I might as well leave thread scheduling up to the kernel itself instead of our own custom class. The host thread suspension technique will still be used, but in a different and simpler way. Basically the kernel will update the KPRCB's current and next thread fields taking into account priorities, thread queues, quantums and more (just like the real thing), and the scheduler will switch to whatever current thread is in there, creating a host thread or waking one up if a context switch happens in the middle of a kernel function call. The Thread class might disappear as we'll be using those KTHREADs instead. I expect to be able to fully implement the majority (if not all) of the Ke* and Kf* functions. Also, big changes might happen. One big thing that has to come next is interrupts. The system clock ticks 1000 times per second on the Xbox, updating KeSystemTime, KeInterruptTime and KeTickCount, and also handling thread switches when their quantum expire. Without it this approach will get stuck on a single thread. It seems Unicorn doesn't do interrupts on its own. |
Implemented several internal kernel functions that deal with initialization, thread scheduling, thread suspension, context switching and much more. This is as real as it gets! Even thread priorities and quantums are handled correctly. With this, the existing thread scheduler has been greatly simplified, as it no longer needs to do the scheduling but simply manage host threads to suspend/resume execution at the correct times. In addition to that, two new function invocation mechanisms were implemented: host-to-guest (InvokeGuest) and guest-to-host (through handlers similar to what was done to the Kernel Thunk table). With it, the emulator is now capable of providing the guest with pointers to functions implemented on the host, as well as allowing the host to invoke guest functions directly.
This should work in most cases. Still needs more testing, but so far it goes all the way to the point where we got stuck before due to an unimplemented kernel function (NtReadFile in the case of Microsoft XDK software). The cool thing is, it automatically causes a BugCheck because of it! The way the scheduler works is similar to what was described above, but much more simplified. A new host thread is created everytime a new guest thread is switched in, suspending the current host thread. When the scheduler switches back to the old thread, the corresponding host thread is resumed. No conditions are needed because the kernel handles them internally; all we need to do is check what is the current thread in the KPRCB and manage the host threads. In order to get this to work, I also had to implement two function invocation mechanisms:
By the way, over a third of the exported kernel functions are now fully implemented, with the majority being Ke* and Rtl* functions. Almost half of the functions have at least a partial or fake implementation. In order to progress further, interrupts need to be implemented. As explained above, the system clock plays a role in thread switching; without it, threads may get stuck executing forever, starving other threads. |
While implementing the custom Xbox kernel outside of the emulated environment, I found that some of the kernel functions need to suspend execution due to various reasons (yielding, waiting for a signal, trying to enter a locked critical section).
Since we cannot suspend execution of the host thread, we need to figure out a way to allow the emulator to "suspend" execution of the host thread (but not really) and continue with the emulation, so that at a later point in time, when the suspension condition is no longer met, the original thread can resume execution from the point where it stopped.
The text was updated successfully, but these errors were encountered: