I am making a kernel and I would like to implement a Windows DPC-like mechanism for deferred calls. The idea would be to queue it from some atomic context (eg from IRQ handlers) and they will be executed as soon as code enters preemptible context. The callbacks themselves won't be preemptible. Most processing of the IRQ would then happen in DPC. As far as I know Linux has something similar. The idea would be to do roughly this:
Code: Select all
execute_pending_dpcs() {
if (current_cpu->dpc_pending) {
// DPC queue protected internally by spinlock that disables IRQs.
while (dpc = current_cpu->dpc_queue->pop()) {
dpc();
}
}
}
on_entering_preemptible_context() {
execute_pending_dpcs();
reschedule_if_needed();
}
preempt_enable() {
if (counter == 1) {
on_entering_preemptible_context();
}
counter--;
}
on_trap() {
// IRQs or faults or traps are always non-preemptible.
preempt_disable();
// handle trap, ....
// on trap exit
if (counter == 1) {
// Exiting to preemptible code.
on_entering_preemptible_context();
}
counter--;
}
Code: Select all
preempt_disable_counter = 1
execute_pending_dpcs executes all dpcs
< IRQ fires
< IRQ inserts DPC
< IRQ exit sees that it returns to code with counter = 1 so it doesn't execute the DPC
preempt_disable_counter = 0
DPC is missed
The solution would be to make sure that both check if queue is empty and decrement of preemption counter happen with interrupts disabled. However preempt_enable can be called quite frequently and I would like to avoid using cli/sti in the common path where there are no DPCs pending.
One idea to solve that is to do something similar to what Windows does. Instead of having preemption disable counter I could use CR8 in stack-based manner.
preempt disable would become write_cr8(DPC_SCHEDULER_PRIORITY) and preempt enable would become write_cr8(previous_cr8).
I would then use APIC to request interrupt when DPC is pending or reschedule is required. The hardware would take care of invoking the interrupt as soon as code enters preemptible context. I quite like that idea but it would require some changes in the kernel.
Any opinions on CR8/APIC method? How do you solve that problem in the kernel? Do you use DPC-like deferred calls or something else to defer execution to a safe point?
Regards