进程调度2「Core」

Linux内核是一个支持多任务「multitasking」的操作系统,可以同时执行多个任务,但是CPU在某个时刻只能运行一个任务,因此为了使Linux内核真的看起来像多个任务在同时运行,调度器将连续不断地替换当前正在运行的任务,这个替换的过程被称为任务切换「task switch」或者调度「scheduling」。
内核通过处理定时器「timer」中断能够周期循环地检查当前进程的need_resched标志是否设置以及时间片「time slice」是否用尽,若当前进程设置了need_resched标志或者用完了时间片,那么将发生任务切换,这个过程就是利用定时器「timer」中断处理函数实现的周期调度「periodic scheduling 」。除此之外,内核还有很多其他代码会随时检查当前进程的need_resched标志是否设置,若need_resched标志被设置了,则将进行任务切换,这个过程属于非周期调度。在内核中这两种调度能够保证调度器尽可能经常地进行任务切换,以致于所有现存的进程能够尽可能公平地被执行。与周期调度不同,内核常见的非周期调度大致可分两种:被动式非周期调度和主动式非周期调度,比如如下所述的两个场景:

  • 进程相关事件发生时会调用设置need_resched标志的函数,比如一个进程的状态发生了变化(进程唤醒事件出现、进程优先级改变)或者当前进程主动让出「yield」CPU执行时间,这只是触发了调度请求「scheduling request」,而真正的任务切换却发生于调用schedule()的函数,或检测need_resched标志是否设置的函数。
  • 与前者不同的是有些代码(sleep、blocking API、lock API)会使当前进程进入睡眠状而不能继续执行,则会直接显式调用核心调度函数__schedule()进行任务切换。

下面给出了触发任务切换的调度点「scheduling point」,它们将检查need_resched标志是否被设置,如果设置的话,则会调用核心调度函数进行任务切换。

  • 在中断处理完成后
  • 在系统调用处理完成后
  • 在使能内核抢占后

总之,本文主要分析使用timer中断实现的周期调度、非周期调度涉及的调度请求「scheduling request」以及任务切换。


  • Target Platform: Rock960c
  • **ARCH:**arm64
  • **Linux Kernel:**linux-4.19.27

基于timer interrupt的周期调度

scheduler_tick()

非周期调度

调度的检查场合

任务唤醒

wakeup_task_routine

图1 任务唤醒的调用流程
其他场景

调度器也会周期检查如下的情况是否需要进行调度。

  • 任务显示地让出「yield」CPU执行时间
  • 高优先级的任务插入运行队列
  • 任务的优先级改变
  • 任务的调度类改变
  • 新创建的任务首次被执行

调度点「scheduling point」

arm64: factor work_pending state machine to C

el0_irq_process

图2 在用户态下发生中断后可能进行的调度过程

el1_irq_process

图3 在内核态下发生中断后可能进行的调度过程

arm64: convert raw syscall invocation to C
arm64: convert syscall trace logic to C
arm64: convert native/compat syscall entry to C

el0_svc_process

图4 在用户态下触发系统调用后可能进行的调度过程
内核抢占「kernel preemption」开启时

preempt_enable

图4 在内核抢占被使能时可能进行的调度过程
主动式非周期调度:在当前任务进入睡眠时
由User-space sleep API和blocking API触发的调度

sleep_schedule

图5 当任务进入睡眠时引起的调度

关键调度函数:schedule()、preempt_schedule_irq()和preempt_schedule_common()

调度请求「scheduling request」

调度核心:__schedule()

任务唤醒: try_to_wake_up()

try_to_wake_up

图10 try_to_wake_up