Implementing deferred calls executed when in preemptible context

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
anndd2
Posts: 1
Joined: Thu Jan 08, 2026 2:23 pm

Implementing deferred calls executed when in preemptible context

Post by anndd2 »

Hello.

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--;
}
However this code has a race condition. Consider what happens when normal code which was not preemptible decides to re-enable preemption:

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
As system continues running DPC will get executed at some point but it missed the initial execution window. I would like to avoid that.
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
Post Reply