Hi again,
OK, time for the example. I'm going to use the word "task" everywhere. For your OS design this might mean "thread" or "process" or something else.
I should point out that I'm going to assume a whole pile of things. Hopefully readers will be able to adjust for the differences between my assumptions and their OS design, and my assumptions themselves won't effect any decisions made.
The first of these assumptions is that the task switching code itself consists of more than just swapping stacks. This isn't a bad idea because in reality, extra code for swapping address spaces, sorting out FPU/MMX/SSE/SSE2 state and updating the TSS is very likely, and doing other things (like working out how much CPU time each task has used) are possible.
Related to the first assumption, I'll also assume that there's a single function responsible for doing the actual task switch, rather than a seperate version of it for each "kernel exit point", as this would be much easier to implement and maintain.
I will also assume that, for the kernel API, parameters are passed in registers through a software interrupt, which includes a "function number" that is used with a table to find the address of the kernel function to be executed. I'm making this assumption for a few reasons - it's almost exactly what Linux and my own OS does, and it's one of the cleanest, most efficient methods. I will also assume that all kernel functions return a 32 bit status code, such as "OK", "function not defined", "out of memory" or "time-out error". This is just my personal preference.
Please note: The kernel API calling interface used in this example is very similar to my own OS, and does not create problems for programs written in C (the library "hides" the actual kernel API calling interface, the same as it does on Linux and most other OSs).
I guess I should also make it clear that the point of this post is to compare the "task switches at kernel exit points only with as much as possible written in C" approach suggested by others to the "handle task switches at any time with as much as sensible written in assembly" approach that I use. Specifically, I'll be looking at the added complexity the former method causes for a simple kernel API function that does not cause a task switch, in an attempt to either highlight the code I called "ugly hacks" in my earlier post, or alternatively to highlight any mis-understanding of mine.
For this purpose I've decided to examine a kernel function that returns the current system timer tick. I will assume that this function returns a 32 bit value called "system_timer_tick" for the sake of simplicity. Even though I've selected a simple function the difference in complexity for both methods should be the same for more complex kernel functions.
For the "handle task switches at any time with as much as sensible written in assembly" method, the kernel API code becomes:
Code: Select all
kernel_API_entry_point:
cmp eax,MAX_FUNCTION_NUMBER
ja .function_not_defined_error
call [kernel_API_function_table + eax * 4]
iretd
.function_not_defined_error:
mov eax,ERROR_FUNCTION_NOT_DEFINED
iretd
And the actual "get system timer tick" function would be:
Code: Select all
get_system_timer_tick:
mov ebx,[SYSTEM_TIMER_TICK]
mov eax,STATUS_OK
ret
The application would need this code to use the function:
Code: Select all
mov eax,GET_SYSTEM_TIMER_TICK
int KERNEL_API_INTERRUPT
test eax,eax
jne .error
[continued]