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.
__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 (
ret_to_user_from_irq), or from a syscall (
ret_fast_syscall). There, the kernel checks whether there is work to be done, and if a reschedule is needed,
schedule is called.