linux进程的管理与调度 --- wake_up_process
如下为唤醒进程的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); }