4. 周期性调度器(Periodic Scheduler):基于Tick的调度决策
周期性调度器是Linux内核实现时间共享与抢占决策的核心引擎。它由时钟中断(Timer Tick)驱动,在每个调度周期(通常为1ms或4ms)触发一次,负责检查当前进程的时间片是否耗尽,并决定是否需要重新调度。本章深入剖析scheduler_tick()函数的入口逻辑与进程时间片递减机制。
4.1 基于Tick的调度决策模型
在CFS(完全公平调度器)中,Tick不再是固定时间片,而是触发vruntime更新与调度点检查的脉冲信号。其核心决策流程如下:
核心原则:每个Tick到来时,内核更新当前进程的vruntime,并判断是否超过当前红黑树中最左节点的vruntime。若超过,则设置
TIF_NEED_RESCHED标志,等待返回用户态时触发抢占。
4.2 scheduler_tick() 函数入口
scheduler_tick()是周期性调度器的核心入口函数,定义在kernel/sched/core.c中。它由时钟中断处理程序timer_interrupt() → update_process_times() → scheduler_tick() 路径调用。
// kernel/sched/core.c (简化示意)
void scheduler_tick(void)
{
int cpu = smp_processor_id();
struct rq *rq = cpu_rq(cpu);
struct task_struct *curr = rq->curr;
// 1. 更新当前CPU的运行队列统计
update_rq_clock(rq);
// 2. 调用调度类的task_tick方法
curr->sched_class->task_tick(rq, curr, 0);
// 3. 检查是否需要触发负载均衡
trigger_load_balance(rq);
// 4. 更新CPU的全局时间统计
update_cpu_load_active(rq);
}
关键点:
scheduler_tick()不直接执行进程切换,而是通过task_tick回调更新进程时间信息,并设置TIF_NEED_RESCHED标志。真正的上下文切换发生在中断返回用户态时。
4.3 进程时间片递减逻辑
对于CFS调度类,task_tick回调指向task_tick_fair()。该函数实现时间片递减的核心逻辑:
// kernel/sched/fair.c (简化示意)
static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
{
struct cfs_rq *cfs_rq = task_cfs_rq(curr);
struct sched_entity *se = &curr->se;
// 1. 更新当前调度实体的vruntime
update_curr(cfs_rq);
// 2. 检查是否应该抢占
if (cfs_rq->nr_running > 1)
check_preempt_tick(cfs_rq, curr);
}
update_curr()函数完成vruntime的增量计算:
static void update_curr(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr = cfs_rq->curr;
u64 now = rq_clock_task(rq_of(cfs_rq));
u64 delta_exec;
// 计算自上次更新以来的实际执行时间
delta_exec = now - curr->exec_start;
curr->exec_start = now;
// 更新sum_exec_runtime
curr->sum_exec_runtime += delta_exec;
// 更新vruntime:delta_exec * (NICE_0_LOAD / se->load.weight)
curr->vruntime += calc_delta_fair(delta_exec, &curr->load);
// 更新cfs_rq的min_vruntime
update_min_vruntime(cfs_rq);
}
4.4 时间片递减与抢占检查
check_preempt_tick()函数负责判断当前进程是否应该被抢占:
| 检查条件 | 触发动作 | 说明 |
|---|---|---|
| vruntime差值 > 调度粒度 | 设置TIF_NEED_RESCHED | 当前进程运行时间过长,应让出CPU |
| 当前进程时间片耗尽 | 设置TIF_NEED_RESCHED | 基于sysctl_sched_min_granularity检查 |
| 红黑树最左节点vruntime更小 | 设置TIF_NEED_RESCHED | 有更值得运行的进程等待 |
| 以上条件均不满足 | 继续执行 | 当前进程仍是最优选择 |
4.5 时间片递减的关键参数
周期性调度器的时间片行为受以下内核参数控制:
| 参数名 | 默认值 | 作用 |
|---|---|---|
sysctl_sched_min_granularity |
0.75 ms | 进程最小执行时间片,防止频繁切换 |
sysctl_sched_latency |
6 ms | 目标调度延迟,决定时间片分配总量 |
sysctl_sched_wakeup_granularity |
1.0 ms | 唤醒抢占阈值,防止小任务被频繁抢占 |
HZ |
250 / 1000 | 时钟中断频率,决定Tick间隔 |
注意: 在HZ=1000的系统中,每1ms触发一次
scheduler_tick()。如果sysctl_sched_min_granularity设置为0.75ms,则进程至少运行1个Tick(1ms)后才可能被抢占,实际最小时间片受Tick粒度约束。
4.6 周期性调度器的完整调用链
从时钟中断到调度决策的完整路径如下:
- 硬件时钟中断 → CPU进入中断处理程序
- timer_interrupt() → 调用
do_timer()更新全局jiffies - update_process_times() → 调用
scheduler_tick() - scheduler_tick() → 调用
task_tick_fair() - task_tick_fair() → 调用
update_curr()更新vruntime - check_preempt_tick() → 判断是否设置
TIF_NEED_RESCHED - 中断返回 → 检查
TIF_NEED_RESCHED,触发schedule()
4.7 总结与关键要点
- 周期性调度器由时钟中断驱动,每个Tick触发一次
scheduler_tick() - 时间片递减通过
update_curr()更新vruntime实现,而非传统固定时间片计数器 - 抢占决策基于vruntime差值比较,由
check_preempt_tick()完成 - 延迟设置:
TIF_NEED_RESCHED标志仅在中断返回时触发实际切换 - 参数调优:
sysctl_sched_min_granularity和sysctl_sched_latency控制调度粒度