Linux的进程调度是内核分配CPU资源给不同进程的核心机制,调度器会根据预设的调度策略,在合适的时机切换正在运行的进程,保证系统整体运行效率和响应速度。调度并不是随机发生的,而是在特定的系统事件触发下才会执行。

进程状态变更触发的调度
当进程的状态发生改变时,内核往往会触发调度。最常见的场景是进程进入阻塞状态,比如进程需要等待I/O操作完成、等待锁资源或者等待某个事件通知时,会主动调用调度相关函数让出CPU。
比如进程调用sleep()函数进入睡眠状态时,内核会将该进程的状态设置为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE,然后触发调度选择其他可运行的进程执行。对应的内核代码逻辑片段如下:
// 进程进入可中断睡眠状态并触发调度
static void __sleep_on(struct task_struct *tsk)
{
tsk->state = TASK_INTERRUPTIBLE;
// 将进程从运行队列移除并触发调度
schedule();
}
时间片耗尽触发的调度
对于采用分时调度策略的进程,内核会给每个进程分配一定的CPU时间片。当进程的时间片耗尽时,调度器会重新计算进程的运行时间,若当前进程的时间片已经用完,就会触发调度切换到其他同优先级或者更高优先级的进程。
在CFS(完全公平调度器)中,虽然没有传统意义上的固定时间片,但是会通过虚拟运行时间来判断是否需要调度。当当前进程的虚拟运行时间超过同队列中其他进程的虚拟运行时间时,就会触发调度。相关的判断逻辑如下:
// CFS调度器中判断是否需要进行调度
static int should_schedule(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr = cfs_rq->curr;
struct sched_entity *next = __pick_next_entity(cfs_rq);
// 如果下一个调度实体的虚拟运行时间更小,触发调度
if (next && entity_before(next, curr))
return 1;
return 0;
}
中断返回用户态时触发的调度
当硬件中断处理完成,返回到用户态之前,内核会检查是否有需要调度的进程。如果当前进程设置了TIF_NEED_RESCHED标志位,说明有更高优先级的进程需要运行,或者当前进程需要被抢占,此时就会触发调度。
中断返回的处理逻辑中,调度检查的代码示例如下:
// 中断返回用户态前的调度检查
asmlinkage void ret_from_intr(void)
{
// 检查是否需要调度
if (need_resched()) {
// 触发调度
schedule();
}
// 返回用户态继续执行
return_to_user_mode();
}
进程主动让出CPU触发的调度
进程可以通过调用sched_yield()系统调用主动让出CPU,告知调度器自己愿意暂时放弃CPU使用权,调度器会立即选择其他可运行的进程执行。这种场景常见于进程暂时不需要CPU资源,希望让其他进程优先运行的情况。
对应的系统调用实现逻辑如下:
// sched_yield系统调用实现
SYSCALL_DEFINE0(sched_yield)
{
struct task_struct *tsk = current;
// 将当前进程放到运行队列的末尾
sched_move_to_tail(tsk);
// 触发调度
schedule();
return 0;
}
进程优先级变更触发的调度
当进程的优先级发生改变时,比如通过nice命令调整进程的nice值,或者进程调用setpriority系统调用修改优先级,内核会重新计算进程在运行队列中的位置。如果变更后的优先级高于当前正在运行的进程,就会触发调度切换到优先级更高的进程。
创建新进程触发的调度
当调用fork()、clone()等系统调用创建新进程时,新创建的进程会被加入到运行队列中。如果新进程的优先级高于当前正在运行的进程,内核会设置TIF_NEED_RESCHED标志位,在合适的时机触发调度,让新进程有机会运行。
新进程创建后加入运行队列的代码片段:
// 新进程创建后加入运行队列
void wake_up_new_task(struct task_struct *tsk)
{
// 将新进程加入对应CPU的运行队列
enqueue_task(tsk, tsk->wake_cpu);
// 检查是否需要设置调度标志
check_preempt_curr(rq, tsk);
}
总结
Linux的进程调度触发场景主要包括进程状态变更、时间片耗尽、中断返回用户态、进程主动让出CPU、优先级变更、创建新进程等几类。这些触发场景保证了CPU资源能够被合理分配给不同的进程,既保证了系统的响应速度,也保证了整体运行效率。理解这些调度时机,有助于我们更深入掌握Linux内核的运行逻辑,也能为系统性能优化提供理论支撑。