在Linux内核中,进程调度由调度器(scheduler)负责,确保CPU资源合理分配给各个进程。调度器的核心是CFS(Completely Fair Scheduler),它从2.6.23版本开始成为默认调度器。以下是调度流程的详细说明:
1. 调度器核心数据结构
1.1 task_struct
每个进程或线程在内核中由一个task_struct结构体表示,包含进程状态、调度信息等。
struct task_struct {
// 进程状态
volatile long state;
// 调度类
const struct sched_class *sched_class;
// 调度实体
struct sched_entity se;
// 进程优先级
int prio, static_prio, normal_prio;
// 其他字段...
};
1.2 sched_class
调度类定义了调度器的操作,如enqueue_task、dequeue_task、pick_next_task等。
struct sched_class {
const struct sched_class *next;
void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
struct task_struct * (*pick_next_task) (struct rq *rq);
void (*put_prev_task) (struct rq *rq, struct task_struct *p);
void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
// 其他操作...
};
2. 调度流程
2.1 调度触发
调度可能在以下情况下触发:
- 进程主动放弃CPU(如调用
sched_yield())。 - 进程进入睡眠状态(如等待I/O)。
- 时钟中断(
tick)发生时,检查是否需要调度。
2.2 调度入口
调度的主要入口是schedule()函数,定义在kernel/sched/core.c中。
asmlinkage __visible void __sched schedule(void) {
struct task_struct *tsk = current;
sched_submit_work(tsk);
__schedule(false);
}
__schedule()是核心调度函数:
static void __sched notrace __schedule(bool preempt) {
struct task_struct *prev, *next;
struct rq *rq;
int cpu;
cpu = smp_processor_id();
rq = cpu_rq(cpu);
prev = rq->curr;
// 选择下一个要运行的进程
next = pick_next_task(rq, prev);
if (likely(prev != next)) {
// 切换上下文
context_switch(rq, prev, next);
}
}
2.3 选择下一个进程
pick_next_task()通过调度类选择下一个要运行的进程。
static inline struct task_struct *pick_next_task(struct rq *rq, struct task_struct *prev) {
const struct sched_class *class;
struct task_struct *p;
// 遍历调度类,选择下一个进程
for_each_class(class) {
p = class->pick_next_task(rq, prev);
if (p)
return p;
}
return NULL;
}
对于CFS调度器,pick_next_task()会调用pick_next_task_fair(),选择vruntime最小的进程。
static struct task_struct *pick_next_task_fair(struct rq *rq, struct task_struct *prev) {
struct cfs_rq *cfs_rq = &rq->cfs;
struct sched_entity *se;
struct task_struct *p;
se = pick_next_entity(cfs_rq);
p = task_of(se);
return p;
}
2.4 上下文切换
context_switch()负责切换进程上下文,包括切换地址空间和寄存器状态。
static __always_inline struct rq *context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next) {
struct mm_struct *mm, *oldmm;
mm = next->mm;
oldmm = prev->active_mm;
// 切换地址空间
switch_mm_irqs_off(oldmm, mm, next);
// 切换寄存器状态
switch_to(prev, next, prev);
return finish_task_switch(prev);
}
3. 调度策略
3.1 CFS调度器
CFS调度器通过虚拟运行时间(vruntime)实现公平调度,vruntime较小的进程优先运行。
struct sched_entity {
struct load_weight load;
struct rb_node run_node;
u64 vruntime;
// 其他字段...
};
3.2 实时调度器
实时调度器(如SCHED_FIFO和SCHED_RR)优先于普通进程运行,适用于实时任务。
4. 时钟中断处理
时钟中断(tick)触发时,调度器会更新当前进程的vruntime并检查是否需要重新调度。
void scheduler_tick(void) {
int cpu = smp_processor_id();
struct rq *rq = cpu_rq(cpu);
struct task_struct *curr = rq->curr;
curr->sched_class->task_tick(rq, curr, 0);
}
对于CFS调度器,task_tick()会调用task_tick_fair(),更新vruntime并检查是否需要调度。
static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued) {
struct sched_entity *se = &curr->se;
update_curr(cfs_rq_of(se));
// 检查是否需要调度
if (cfs_rq_of(se)->nr_running > 1)
resched_curr(rq);
}
总结
Linux内核的调度流程包括调度触发、选择下一个进程、上下文切换等步骤。CFS调度器通过vruntime实现公平调度,而实时调度器则优先处理实时任务。时钟中断定期触发调度检查,确保系统响应性和公平性。
1247

被折叠的 条评论
为什么被折叠?



