The timer ISR doesn't call schedule()
directly. It ends up calling update_process_times()
so the scheduler process accounting information is up to date.
The scheduler is eventually called when returning to userspace. If the kernel is preemptive, it is also called when returning from the timer interrupt to kernelspace.
As an example, imagine a process A which issues a syscall which is interrupted by a device-generated interrupt, that is then interrupted by a timer interrupt:
process A userspace → process A kernelspace → device ISR → timer ISR
syscall device IRQ timer IRQ
When the timer ISR ends, it returns to another ISR, that then returns to kernelspace, which then returns to userspace. A preemptive kernel checks if it needs to reschedule processes at every return. A non-preemptive kernel only does that check when returning to userspace.
In ARM land, the codepath goes broadly like:
- An IRQ received while in userspace ends up calling
__irq_usr
, while an IRQ received while in SVC mode ends up calling __irq_svc
. IRQs should not be received while in other processor modes.
- In
__irq_svc
, after handling the IRQ, if the kernel is preemptive, preemption is not disabled, and a reschedule is needed, the kernel jumps to svc_preempt
, which calls preempt_schedule_irq
, which calls schedule
. Otherwise, no reschedule is done.
- Eventually, the CPU will return to userspace, either from an IRQ handler (
__irq_usr
→ ret_to_user_from_irq
), or from a syscall (vector_swi
→ ret_fast_syscall
). There, the kernel checks whether there is work to be done, and if a reschedule is needed, schedule
is called.
schedule()
is called when returning to userspace (and wherever a system call blocks, also on the idle task). With preempt enabled, it's apparently also called when returning from any interrupt, and also in a few other places, e.g. onmutex_unlock()
slow path, on certain conditions while receiving network packets, ...