Hi,
naegelejd wrote:I'm interested in whether you'd use an interrupt handler for preemption.
I'd never directly use an interrupt handler for pre-emption.
For "indirectly"...
Let's start by assuming the kernel API could be anything (e.g. SYSCALL and/or SYSENTER and/or call gate and/or software interrupt) where the kernel supports all of them and applications use whichever they like - e.g. the slowest and smallest option (software interrupt) where code size is more important, and the fastest option where speed matters. For all of them the kernel (internally) would just have a table of function pointers. For the SYSCALL handler it'd have code to "call [table+eax*4]" and do SYSRET; for the call gate handler it'd have code to do "call [table+eax*4]" and do RETF; etc.
Let's also assume that the kernel supports "batch functions"; where if your application needs to use 10 different kernel API functions it can build a list of input/output parameters and call a special "do batch" kernel API function; which would loop through the provided list (doing each kernel API function in the list, one at a time).
Note: This is a very good idea, to reduce the overhead the CPL=3 -> CPL=0 -> CPL=3 switching.
Now...
If the current task is being pre-empted because it called a "yield()" kernel API function, then there's no guarantee that any interrupt was involved at all. The kernel API (whatever it is) would do its "call [table+eax*4]" which would probably call that "decide_which_task_to_switch_to(void)" function I mentioned earlier, which would call the "goto_thread()" function. The "goto_thread()" function only sees a normal function call.
If the current task is being pre-empted because it used all of the CPU time it was given; then this isn't really any different to the task voluntarily calling "yield()". The result is the same - e.g. the timer IRQ calls the "decide_which_task_to_switch_to(void)" function, which would call the "goto_thread()" function. The "goto_thread()" function only sees a normal function call.
If the current task is blocked (e.g. it called "sleep()" or "load_huge_file_from_slow_floppy()" or something); then it still ends up the same. Something ends up calling the "decide_which_task_to_switch_to(void)" function, which would call the "goto_thread()" function. The "goto_thread()" function only sees a normal function call.
If the current task is being pre-empted because a higher priority task unblocked; then this is different. Something (e.g. a device driver) calls something (e.g. a kernel "unblock_thread(thread)" function), which calls the "goto_thread()" function directly.
The "goto_thread()" function only sees a normal function call.
Under no circumstances does the "goto_thread()" function ever see anything other than a normal function call (where creating a new thread emulates a normal function return, so that "goto_thread()" doesn't see anything different).
naegelejd wrote:If so, how would "goto_task" be called since a switch inside an interrupt handler requires the target thread's stack to contain at least EIP, CS, and EFLAGS, in addition to registers because the handler ends with an IRET?
For a simple example, imagine an interrupt handler like this:
Code: Select all
interruptHandler(void) {
foo();
bar();
if(something) {
do_something();
}
}
When the interrupt handler starts, the return EIP, return CS and return EFLAGS are on the stack. If it doesn't call "do_something()" then this return information is still on the stack when the function exists. If it does call "do_something()" then the return information is still on the stack when the function exists. It simply makes no difference whether "do_something()" is called or not.
Now imagine if "do_something()" looks like this:
Code: Select all
int k = 0;
void do_something(void) {
k = (k + 54321) * 1234567;
if( (k & 3 == 0) {
goto_thread(4);
}
}
In this case, the return EIP, return CS and return EFLAGS are on the stack is still where it always was when the interrupt handler exists. It still makes no difference whether "do_something()" is called or not.
Note: Technically, if this was an IRQ handler then it'd probably need to send its EOI before calling "do_something()", so it would need to care a tiny little bit if a task switch can happen or not.
Cheers,
Brendan