Hi,
enigma wrote:New problem, despite whatever I do to try and switch tasks, nothing seems to work. I've tried linked lists, queues, simple structures with all the registers and nothing seems to work. I know that when an interrupt fires off, it saves the current state of the machine (not the general purpose registers or segments) and then calls the interrupt handler. So I figured the easiest way to switch tasks was to implement it in the PIT code (set to execute the scheduler once per second as a temporary test to see how it's working), where right before calling the scheduler, the CPU naturally saved everything that it had been doing, add a few more pushes onto the equation, then just switched ESP according to where the next task needed it. Right now, no other task except for the main kernel is executing, however, with every implementation of a scheduling structure the machine gets lost somewhere (not a triple fault, it simply executes to a point where it doesn't).
Make sure that (in your timer IRQ handler) you send an EOI to the PIC chip *before* you do any task switch; and that EFLAGS is part of the state saved and restored (so that the "interrupts disabled" flag is restored and you don't end up waiting for an IRQ that's disabled).
enigma wrote:Is this even the "correct" concept of how I should be doing software task switching?
Different people do things differently, but IMHO it's not the correct way to implement task switching...
People that use the "PIT IRQ does the task switches" method often end up screwed later on when a task needs to block (e.g. wait for disk I/O). Sometimes they even get the CPU to do absolutely nothing until the next timer IRQ occurs; which can lead to wasting almost all of the CPUs time when all of the running tasks are I/O bound; which is relatively common when you look at things like compilers (constantly waiting for file I/O) and networking stuff (constantly waiting to send or receive network packets). It can also completely kill performance for "event driven" software (including most GUIs which become "sluggish" due to latency, and just about everything else if the OS is a message based OS).
Brendan wrote:Implement code to do one task switch (e.g. "switchToTask(newTask)"), then build on it...
After you've written a "switchToTask(newTask)" function, you'd implement a "switchToNextTask(void)" function which selects the best task to run next and calls the "switchToTask(newTask)" function.
The timer IRQ would call the "switchToNextTask(void)" function. When a task blocks it'd call the "switchToNextTask(void)" function. When something happens that unblocks a higher priority task you'd call the "switchToTask(newTask)" function directly (so that when a high priority task is unblocked it gets CPU time immediately by preempting lower priority tasks, and doesn't need to wait until the next timer IRQ occurs).
Mostly, IMHO to begin with you want that "switchToTask(newTask)" function and a pair of kernel threads, where each kernel thread uses the "switchToTask(newTask)" function to do a task switch to the other kernel thread (so you end up constantly doing task switches between the 2 kernel threads). This is a very good way to test the "switchToTask(newTask)" function - most of the scheduler and no IRQs are needed.
Then you'd implement the "switchToNextTask(void)" function and still keep using kernel threads, where each kernel thread uses the "switchToNextTask(void)" function to do a task switch to some other kernel thread (so you're still constantly doing task switches between the kernel threads). Now you could try lots more kernel threads, and maybe give each kernel thread a different priority.This is a very good way to test the "switchToNextTask(void)" function - no IRQs needed.
The last thing you'd do is remove the calls to the "switchToNextTask(void)" function from the kernel threads and put it in the PIT timer IRQ handler, but by the time you do that you'd know that most of it works.
Cheers,
Brendan