linux 进程的管理和调度 --- __schedule() 函数分析
运行队列
Linux采用的是每个CPU都有自己的运行队列,这样做的好处:
(1)每个CPU在自己的运行队列上选择任务降低了竞争;
(2)某个任务位于一个CPU的运行队列上,经过多次调度后,内核趋于选择相同的CPU执行该任务,那么上次任务运行的变量很可能仍然在这个CPU缓存上,提高运行效率。
__schedule()
入参
#define SM_NONE 0x0 // 主动调度 #define SM_PREEMPT 0x1 #define SM_RTLOCK_WAIT 0x2
函数分析
static void __sched notrace __schedule(unsigned int sched_mode) { struct task_struct *prev, *next; unsigned long *switch_count; unsigned long prev_state; struct rq_flags rf; struct rq *rq; int cpu; cpu = smp_processor_id(); rq = cpu_rq(cpu); // 找到当前CPU的运行队列 prev = rq->curr; // 记录要切换出去的任务 schedule_debug(prev, !!sched_mode); if (sched_feat(HRTICK) || sched_feat(HRTICK_DL)) hrtick_clear(rq); local_irq_disable(); rcu_note_context_switch(!!sched_mode); rq_lock(rq, &rf); smp_mb__after_spinlock(); /* Promote REQ to ACT */ rq->clock_update_flags <<= 1; update_rq_clock(rq); switch_count = &prev->nivcsw; // Context involuntary switch counter,此任务上下文非主动切换计数 prev_state = READ_ONCE(prev->__state); if (!(sched_mode & SM_MASK_PREEMPT) && prev_state) { // 主动调度,并且要切换出去的任务不是运行态(非运行态prev_state是非零,通常主调度之前会提前设置当前进程的
// 运行状态为 TASK_INTERRUPTIBLE 或者 TASK_UNINTERRUPTIBLE) if (signal_pending_state(prev_state, prev)) { WRITE_ONCE(prev->__state, TASK_RUNNING); } else { prev->sched_contributes_to_load = (prev_state & TASK_UNINTERRUPTIBLE) && !(prev_state & TASK_NOLOAD) && !(prev->flags & PF_FROZEN); if (prev->sched_contributes_to_load) rq->nr_uninterruptible++; deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK); // 设置 prev->on_rq 为0,表示此任务不在运行队列里;并把此任务从运行队列移除 if (prev->in_iowait) { atomic_inc(&rq->nr_iowait); delayacct_blkio_start(); } } switch_count = &prev->nvcsw; // Context voluntary switch counter,此任务上下文主动切换计数 } // 抢占调度,要切换的任务还是在运行队列 next = pick_next_task(rq, prev, &rf); // 根据prev任务的调度类选一个最高优先级的任务返回 clear_tsk_need_resched(prev); // 清楚prev任务的 TIF_NEED_RESCHED 的标记 clear_preempt_need_resched(); #ifdef CONFIG_SCHED_DEBUG rq->last_seen_need_resched_ns = 0; #endif if (likely(prev != next)) { rq->nr_switches++; // 队列内的任务切换次数统计 RCU_INIT_POINTER(rq->curr, next); ++*switch_count; // 任务上下文切换统计 migrate_disable_switch(rq, prev); psi_sched_switch(prev, next, !task_on_rq_queued(prev)); trace_sched_switch(sched_mode & SM_MASK_PREEMPT, prev, next); /* Also unlocks the rq: */ rq = context_switch(rq, prev, next, &rf); } else { rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP); rq_unpin_lock(rq, &rf); __balance_callbacks(rq); raw_spin_rq_unlock_irq(rq); } }
上下文切换函数 context_switch,切换内存描述符和MMU的页表首地址、CPU寄存器。
用户线程有用户态和内存态,内核线程只有内核态。
task_struct 有两个成员,分别是 mm 和 active_mm,对于 用户线程 来说 , active_mm
字段 与 mm
字段 指向同一个 " 内存描述符 " ;但对于 " 内核线程 " 来说 , mm
字段 指向 空指针 , active_mm
字段 指向同一个CPU最近运行的用户线程的 " 内存描述符 " ;
/* * context_switch - switch to the new MM and the new thread's register state. */ static __always_inline struct rq * context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next, struct rq_flags *rf) { prepare_task_switch(rq, prev, next); /* * For paravirt, this is coupled with an exit in switch_to to * combine the page table reload and the switch backend into * one hypercall. */ arch_start_context_switch(prev); /* * kernel -> kernel lazy + transfer active * user -> kernel lazy + mmgrab() active * * kernel -> user switch + mmdrop() active * user -> user switch */ if (!next->mm) { // 切换到内核线程 enter_lazy_tlb(prev->active_mm, next); next->active_mm = prev->active_mm; // 指向要被切换出去线程的内存描述符 if (prev->mm) // 被切换出去线程属于用户线程 mmgrab(prev->active_mm); // 由于被切换出去线程的内存描述符被要运行的内核线程使用,内存描述符的引用计数加一 else prev->active_mm = NULL; // 被切换出去线程属于内核线程,其内存描述符指向NULL } else { // 切换到用户线程 membarrier_switch_mm(rq, prev->active_mm, next->mm); /* * sys_membarrier() requires an smp_mb() between setting * rq->curr / membarrier_switch_mm() and returning to userspace. * * The below provides this either through switch_mm(), or in * case 'prev->active_mm == next->mm' through * finish_task_switch()'s mmdrop(). */ switch_mm_irqs_off(prev->active_mm, next->mm, next); // 设置 MMU 的页表首地址为准备运行的用户线程的页表首地址 if (!prev->mm) { // from kernel /* will mmdrop() in finish_task_switch(). */ rq->prev_mm = prev->active_mm; prev->active_mm = NULL; } } rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP); prepare_lock_switch(rq, next, rf); /* Here we just switch the register state and the stack. */ switch_to(prev, next, prev); // 切换CPU寄存器 barrier(); return finish_task_switch(prev); }