Linux内核调度流程详解

在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_taskdequeue_taskpick_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_FIFOSCHED_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实现公平调度,而实时调度器则优先处理实时任务。时钟中断定期触发调度检查,确保系统响应性和公平性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值