2016-09-25

2016-09-25 Kernel Threads and VT100 Console

The Ring 3 test from yesterday is useful and proves the TSS, but the rest of my OS is not quite set up yet to handle everything from process space with system calls and protected memory and the like.  What I really need is a way to run multiple tasks that are within kernel space.  What I need is KERNEL THREADS!

Because I already have much of a window manager in place, I made a quick change to the kernel main function so that instead of just opening a window for the console, it opens two more windows.  My test case is then a couple of functions which write characters into those other windows with a delay of of a few thousand nops, which will then get set to run as separate threads.

The method signature for the function to create a kernel thread looks like this
int32 process_kernelThreadStart( void* pThreadStart, size_t pStackSize, int32_t pParamCount, ... );
It allocates a stack for the thread and copies the parameters from its own stack into the thread stack it just allocated.  It also writes the address of a termination function to the stack so that if the thread function ever exits, it "returns" to termination function is called which collects the return value and terminates that thread.

The interrupt handlers already push the state of the processor to the stack, but I added an extra parameter to pass a pointer to this suspended state to each of the registered interrupt handlers.  This is so that the timer interrupt handler for switching tasks can get a handle to the suspended state to copy it to the process table.  I made the state structure in the process table the same as the structure pushed by the generic interrupt handler so that I can just memcpy() from one to the other as required.  It means that the Process table includes an Interrupt Number and Error Code, but that is ok as it represents the last interrupt which switched the process out, such as a normal Timer interrupt, or a Divide by Zero or Overflow exception.

This extra parameter isn't so helpful for IRQ interrupt handlers, as most of them don't care what was interrupted by their device (the jerks!), the will ignore it and return to the interrupted task without any changes.  Any processor exceptions or system calls will benefit from this because they will be able to change the processor state that is returned, such as setting the EAX register to a return code, or performing an IN operation and setting the EAX register to the result.

The actual switch task code itself is then fairly straightforward, it copies the suspended state into the process table, then copies the next task state from the process table back to the stack and uses IRET to jump to it.  Note that this means that most of the time switchTask() never actually returns as it will IRET out to what it should be doing next.  There is one case though where the task being suspended task is the only runnable task, where the function doesn't do an explicit IRET and just returns (I can see why people find this confusing).

With all this in place, it now correctly switches between my test kernel threads and they output their characters into the correct windows "at the same time".


A few last elements to put the cherry on top of this.

I added a task state enumeration so that a task can be sleeping and be skipped by the getNextRunnableTask() function.  I also added a WaitTime field so that a task can get a tick count when it should be woken up.

There are times when a task doesn't need to use all of its time slice because it's going to sleep for some reason, such as waiting for some disk activity, or a semaphore, or just because it is sleepy.  For this I need to have a yield() function available which, when called, causes the current task to be suspended and the next task to run for the rest of the time slice.  This function creates a stack frame just as if an interrupt had just been called, then calls the switchtask passing a pointer to the state.  The new task will get what's left of the original time slice.

Combining the yield(), task state and the WakeTime elements means that now a task can call sleep() and go to sleep for a specified number of milliseconds.  With this I've also changed the test threads so that instead of looping for 500'000 nops, they instead sleep for half a second.



I also found time after a few glasses of wine to add some VT100 emulation to the intuition window character handler, so that one of my threads now uses console escape sequences to clear the window, home the cursor to the top left corner, and colour the text.  Suddenly it's become a status display ...


Next steps, re-implement the network stack from a very old test kernel to be a network subsystem.

No comments:

Post a Comment