Technical detail on task switching from DPC code
Posted: Mon Feb 12, 2018 9:10 am
Hi,
I already have pre-emptive task switching but would like to add support for DPC's in my OS. I.e. I want the timer ISR to "schedule" a DPC for actually carrying out the task switch once this is required. But I have some trouble wrapping my head around how to actually control the task switch with respect to switching of stacks etc.
How things work currently: In my current OS, all ISR's start by an assembly stub that saves all "caller saves" (according to the fastcall convention my o/s uses), then calls into the registered handler which is in C (it is not necessary for the stub to save the "callee saves" because those are implicitly saved by the C code). The assembly also keeps track of our IRQL using a global variables which it pushes on the stack and sets to 1 (and pops and writes back on the way out). In this way, the IRQL always contains a "1" if we are executing some interrupt procedure, and "0" if we are not. When the ISR C-code handler is done, before returning back we check if we are now changing IRQL from "1" to "0" - if so, another C function is called to execute everything in the DPC queue.Note also that my task switcher currently, when switching away only saves the "callee-saves" stack because whoever called it (either the C code in the timer ISR or say the yield() syscall code etc.) couldn't expect the other to be saved anyway. A consequence of this design is that when there is an time-interrupt-with-task-switch scenario, every user registers get saved exactly once. Initially the "caller saves" get saved by the interrupt ISR. Then later, once the interrupt ISR invokes the taskSwitch() function the callee saves are saved prior to the switch. A consequence is that the state of the "interuptee" is not saved in one uniform place but is rather scattered over the kernel stack.
Anyway, so how would a task switch work in the DPC model? The ISR puts in a taskSwitch DPC in the queue and exits. Once all other ISR's are done, the stub will when about to change to IRQL=0 execute the DPCs. At some point it will get to the taskSwitch DPC. So what should this do? It could switch in one of the other tasks, but then it will be "switching away" from the current location in the code which will be in the middle of the taskSwitch DPC (which will thus be resumed later). This seems like a weird stack to break away from! Ideally, I would like it to break away more cleanly, maybe at the point-of-view the ISR entry or exit (the part that triggered the whole thing). What do o/s'es typically do? I.e. what do their taskSwitch DPC do and what does the stack look like once they "leave"?
I already have pre-emptive task switching but would like to add support for DPC's in my OS. I.e. I want the timer ISR to "schedule" a DPC for actually carrying out the task switch once this is required. But I have some trouble wrapping my head around how to actually control the task switch with respect to switching of stacks etc.
How things work currently: In my current OS, all ISR's start by an assembly stub that saves all "caller saves" (according to the fastcall convention my o/s uses), then calls into the registered handler which is in C (it is not necessary for the stub to save the "callee saves" because those are implicitly saved by the C code). The assembly also keeps track of our IRQL using a global variables which it pushes on the stack and sets to 1 (and pops and writes back on the way out). In this way, the IRQL always contains a "1" if we are executing some interrupt procedure, and "0" if we are not. When the ISR C-code handler is done, before returning back we check if we are now changing IRQL from "1" to "0" - if so, another C function is called to execute everything in the DPC queue.Note also that my task switcher currently, when switching away only saves the "callee-saves" stack because whoever called it (either the C code in the timer ISR or say the yield() syscall code etc.) couldn't expect the other to be saved anyway. A consequence of this design is that when there is an time-interrupt-with-task-switch scenario, every user registers get saved exactly once. Initially the "caller saves" get saved by the interrupt ISR. Then later, once the interrupt ISR invokes the taskSwitch() function the callee saves are saved prior to the switch. A consequence is that the state of the "interuptee" is not saved in one uniform place but is rather scattered over the kernel stack.
Anyway, so how would a task switch work in the DPC model? The ISR puts in a taskSwitch DPC in the queue and exits. Once all other ISR's are done, the stub will when about to change to IRQL=0 execute the DPCs. At some point it will get to the taskSwitch DPC. So what should this do? It could switch in one of the other tasks, but then it will be "switching away" from the current location in the code which will be in the middle of the taskSwitch DPC (which will thus be resumed later). This seems like a weird stack to break away from! Ideally, I would like it to break away more cleanly, maybe at the point-of-view the ISR entry or exit (the part that triggered the whole thing). What do o/s'es typically do? I.e. what do their taskSwitch DPC do and what does the stack look like once they "leave"?