linux进程的管理与调度 --- wake_up_process

发布时间 2023-10-21 21:42:14作者: 流水灯

如下为唤醒进程的API,执行内容如下:

  • 给待唤醒进程选择一个合适的CPU
  • 将待唤醒进程放入选定CPU的运行队列,每个CPU都有一个运行队列
  • 判断当前进程是否应该被待唤醒进程抢占,如果应该,置位当前进程的 TIF_NEED_RESCHED 标志
int wake_up_process(struct task_struct *p) // 入参为待唤醒的进程
{
    return try_to_wake_up(p, TASK_NORMAL, 0);
}
#define TASK_NORMAL         (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)

 

struct task_struct 的成员变量 on_rq 一共有三个取值,0(进程不再允许)、TASK_ON_RQ_QUEUED(进程已经在某个CPU的运行队列)、TASK_ON_RQ_MIGRATING(为了CPU之间的负载均衡,进程正在从一个CPU的运行队列迁移到另个CPU的运行队列)
struct thread_info 的成员变量 cpu 指示进程是在哪个CPU上运行
static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
    unsigned long flags;
    int cpu, success = 0;

    preempt_disable();
    if (p == current) { // 待唤醒的进程为当前进程

        if (!ttwu_state_match(p, state, &success))
            goto out; // 待唤醒的进程为非阻塞态(比如运行态)

        trace_sched_waking(p);
        WRITE_ONCE(p->__state, TASK_RUNNING); // 待唤醒的进程为阻塞态,改为运行态
        trace_sched_wakeup(p);
        goto out;
    }

    raw_spin_lock_irqsave(&p->pi_lock, flags);
    smp_mb__after_spinlock();
    if (!ttwu_state_match(p, state, &success))
        goto unlock; // 待唤醒的进程为非阻塞态(比如运行态)

    trace_sched_waking(p);

    smp_rmb();
    if (READ_ONCE(p->on_rq) && ttwu_runnable(p, wake_flags))
        goto unlock; // 待唤醒的进程在某一个CPU的运行队列

#ifdef CONFIG_SMP

    smp_acquire__after_ctrl_dep();

    WRITE_ONCE(p->__state, TASK_WAKING);

    if (smp_load_acquire(&p->on_cpu) &&
        ttwu_queue_wakelist(p, task_cpu(p), wake_flags | WF_ON_CPU))
        goto unlock;

    smp_cond_load_acquire(&p->on_cpu, !VAL);

// 给唤醒的进程选择一个合适的处理器 cpu
= select_task_rq(p, p->wake_cpu, wake_flags | WF_TTWU); if (task_cpu(p) != cpu) { if (p->in_iowait) { delayacct_blkio_end(p); atomic_dec(&task_rq(p)->nr_iowait); } wake_flags |= WF_MIGRATED; psi_ttwu_dequeue(p); set_task_cpu(p, cpu); } #else cpu = task_cpu(p); #endif /* CONFIG_SMP */ ttwu_queue(p, cpu, wake_flags); // 将待唤醒的进程加入到CFS就绪队列中 unlock: raw_spin_unlock_irqrestore(&p->pi_lock, flags); out: if (success) ttwu_stat(p, task_cpu(p), wake_flags); preempt_enable(); return success; }
static void ttwu_queue(struct task_struct *p, int cpu, int wake_flags)
{
    struct rq *rq = cpu_rq(cpu);
    struct rq_flags rf;

    if (ttwu_queue_wakelist(p, cpu, wake_flags))
        return;

    rq_lock(rq, &rf);
    update_rq_clock(rq);
    ttwu_do_activate(rq, p, wake_flags, &rf);
    rq_unlock(rq, &rf);
}
static void
ttwu_do_activate(struct rq *rq, struct task_struct *p, int wake_flags,
         struct rq_flags *rf)
{
    int en_flags = ENQUEUE_WAKEUP | ENQUEUE_NOCLOCK;

    lockdep_assert_rq_held(rq);

    if (p->sched_contributes_to_load)
        rq->nr_uninterruptible--;

#ifdef CONFIG_SMP
    if (wake_flags & WF_MIGRATED)
        en_flags |= ENQUEUE_MIGRATED;
    else
#endif
    if (p->in_iowait) {
        delayacct_blkio_end(p);
        atomic_dec(&task_rq(p)->nr_iowait);
    }

    activate_task(rq, p, en_flags); // 将待唤醒的进程加入CFS就绪队列中,也就是将进程调度实体加入红黑树中
    ttwu_do_wakeup(rq, p, wake_flags, rf); // 检查待唤醒的进程是否应该发生抢占
}
void activate_task(struct rq *rq, struct task_struct *p, int flags)
{
    enqueue_task(rq, p, flags);

    p->on_rq = TASK_ON_RQ_QUEUED;
}

static inline void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
{
    if (!(flags & ENQUEUE_NOCLOCK))
        update_rq_clock(rq);

    if (!(flags & ENQUEUE_RESTORE)) {
        sched_info_enqueue(rq, p);
        psi_enqueue(p, flags & ENQUEUE_WAKEUP);
    }

    uclamp_rq_inc(rq, p);
    p->sched_class->enqueue_task(rq, p, flags);

    if (sched_core_enabled(rq))
        sched_core_enqueue(rq, p);
}

 

DEFINE_SCHED_CLASS(fair) = {

    .enqueue_task        = enqueue_task_fair,
    .dequeue_task        = dequeue_task_fair,
    .yield_task        = yield_task_fair,

 

static void ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags,
               struct rq_flags *rf)
{
    check_preempt_curr(rq, p, wake_flags);
    WRITE_ONCE(p->__state, TASK_RUNNING);
    trace_sched_wakeup(p);
}

 

void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags)
{
    if (p->sched_class == rq->curr->sched_class)
        rq->curr->sched_class->check_preempt_curr(rq, p, flags);
    else if (p->sched_class > rq->curr->sched_class)
        resched_curr(rq);

    /*
     * A queue event has occurred, and we're going to schedule.  In
     * this case, we can save a useless back to back clock update.
     */
    if (task_on_rq_queued(rq->curr) && test_tsk_need_resched(rq->curr))
        rq_clock_skip_update(rq);
}

 

DEFINE_SCHED_CLASS(fair) = {
    .check_preempt_curr    = check_preempt_wakeup,

 

/*
 * Preempt the current task with a newly woken task if needed:
 */
static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
{
    (1)
    //获取当前处理器运行队列正在运行的进程:rq->curr 
    struct task_struct *curr = rq->curr;
    (2)
    //获取当前处理器运行队列正在运行的进程调度实体:&curr->se
    //获取唤醒进程的调度实体&p->se
    struct sched_entity *se = &curr->se, *pse = &p->se;
    struct cfs_rq *cfs_rq = task_cfs_rq(curr);
    int scale = cfs_rq->nr_running >= sched_nr_latency;

    if (test_tsk_need_resched(curr))
        return;

    (3)
    //如果当前任务是空闲进程,那么唤醒的进程就应该发起抢占,因为空闲进程的优先级最低
    /* Idle tasks are by definition preempted by non-idle tasks. */
    if (unlikely(curr->policy == SCHED_IDLE) &&
        likely(p->policy != SCHED_IDLE))
        goto preempt;

    /*
     * Batch and idle tasks do not preempt non-idle tasks (their preemption
     * is driven by the tick):
     */
    if (unlikely(p->policy != SCHED_NORMAL) || !sched_feat(WAKEUP_PREEMPTION))
        return;

    //与任务调度组有关:CONFIG_FAIR_GROUP_SCHED
    find_matching_se(&se, &pse);
    (4)
    //更行当前处理器正在运行进程的 vruntime
    update_curr(cfs_rq_of(se));
    BUG_ON(!pse);
    (5)
    // 调用wakeup_preempt_entity判断唤醒的进程是否发生抢占
   // 这里调用wakeup_preempt_entity函数计算是否将当前正在运行的进程标记为应该被抢占时,如果当前处理器正在运行的进程的 vruntime 大于唤醒进程的 vruntime,不
   // 是直接就确定将当前正在运行的进程标记为应该被抢占,而是增加了一个时间缓冲,如果唤醒的进程 vruntime 加上进程最小运行时间(sysctl_sched_wakeup_granularity = 1ms转化为虚拟时间)
   // 仍然小于当前处理器正在运行的进程的 vruntime,那么就确定当前处理器正在运行的进程应该被抢占,增加一个时间缓冲避免进程切换过于频繁,花费过多的时间在上下文切换中。
if (wakeup_preempt_entity(se, pse) == 1) { /* * Bias pick_next to pick the sched entity that is * triggering this preemption. */ if (!next_buddy_marked) set_next_buddy(pse); //唤醒的进程应该发起抢占 goto preempt; } return; (6) //将当前进程标记为应该被抢占
   // 注意这里只是当前进程标记为应该被抢占,请求重新调度,但是真正的抢占动作并没有发生。
   // resched_task将进程的struct thread_info的flags成员设置为:TIF_NEED_RESCHED
preempt: resched_task(curr); /* * Only set the backward buddy when the current task is still * on the rq. This can happen when a wakeup gets interleaved * with schedule on the ->pre_schedule() or idle_balance() * point, either of which can * drop the rq lock. * * Also, during early boot the idle thread is in the fair class, * for obvious reasons its a bad idea to schedule back to it. */ if (unlikely(!se->on_rq || curr == rq->idle)) return; if (sched_feat(LAST_BUDDY) && scale && entity_is_task(se)) set_last_buddy(se); }