- 论坛徽章:
- 0
|
setbackdq(kthread_t *tp)这个函数的作用就是根据线程的当前优先级来把线程放到分配器队列的尾部。
先来说说solaris的线程优先级。solaris有两类优先级:全局优先级和用户优先级。SYS/RT类型的调度不使用用户优先级。在solaris中,数字越大,优先级越高。全局优先级是这么分配的:
169~160: 中断线程
159~100: 实时线程
099~060: 系统线程
059~000: 供TS, FX, FSS, IA使用
kthread_t中的有两个优先级成员变量在setbackdq()中被用到:
- t_pri是全局优先级,最初在调用thread_create的时候从线程的调度类型继承下来的。
- t_epri是线程的继承优先级,用于实现优先级继承,可以解决优先级反转问题
voidsetbackdq(kthread_t *tp){ dispq_t *dq; disp_t *dp; cpu_t *cp; pri_t tpri; int bound; ASSERT(THREAD_LOCK_HELD(tp)); ASSERT((tp->t_schedflag & TS_ALLSTART) == 0); ASSERT(!thread_on_queue(tp)); /* make sure tp isn't on a runq */
这三行ASSERT的作用是: 1)确保拿到线程锁; 2)确保线程的调度标志正确,也是确保线程处于正确的状态; 3)确保线程不在run queue上面。
/* * If thread is "swapped" or on the swap queue don't * queue it, but wake sched. */ if ((tp->t_schedflag & (TS_LOAD | TS_ON_SWAPQ)) != TS_LOAD) { disp_swapped_setrun(tp); return; }
TS_ON_SWAPQ是指线程在swaq queue中,那么如果调度标志包含该位,我们就不需要将它加入分配队列了,调用disp_swapped_setrun然后返回。
if (tp->t_bound_cpu || tp->t_weakbound_cpu) bound = 1; else bound = 0;
这一段判断线程是否被绑定,以此来设置bound变量。
tpri = DISP_PRIO(tp);
获得该线程的优先级,这里对t_pri和t_epri作比较,哪个大选哪个作为调度的优先级。
if (ncpus == 1) cp = tp->t_cpu;
这里t_cpu表示该线程上次运行的cpu,那么如果系统中只有一个cpu,当然只能选上次那个了。
else if (!bound) { if (tpri >= kpqpri) { setkpdq(tp, SETKP_BACK); return; }
否则并且线程没有被绑定,判断调度的优先级是否大于kpqpri(内核抢占队列的优先级),如果是,那么就调用setkpdq()然后返回。kpqpri是个全局变量,可以通过/etc/system设置,这里我用mdb看了一下,我系统中的kpqpri是64。
/* * Let cpu_choose suggest a CPU. */ cp = cpu_choose(tp, tpri);
cpu_choose()选择一个cpu给这个线程run。
if (tp->t_cpupart == cp->cpu_part) { int qlen; /* * Perform any CMT load balancing */ cp = cmt_balance(tp, cp); /* * Balance across the run queues */ qlen = RUNQ_LEN(cp, tpri);
如果线程所在的cpu分区和被选择的cpu所在的分区是相同的,我们可以做负载均衡的工作,由此再选出一个合适的cpu出来,并得到该cpu在tpri优先级上的分配队列的长度。
if (tpri >= RUNQ_MATCH_PRI && !(tp->t_schedflag & TS_RUNQMATCH)) qlen -= RUNQ_MAX_DIFF; if (qlen > 0) { cpu_t *newcp; if (tp->t_lpl->lpl_lgrpid == LGRP_ROOTID) { newcp = cp->cpu_next_part; } else if ((newcp = cp->cpu_next_lpl) == cp) { newcp = cp->cpu_next_part; } if (RUNQ_LEN(newcp, tpri) DTRACE_PROBE3(runq__balance, kthread_t *, tp, cpu_t *, cp, cpu_t *, newcp); cp = newcp; } }
如果没有TS_RUNQMATCH调度标志或者线程的优先级大于16,那么确保被选的cpu的run queue的长度和分区中下个cpu的长度的相差不大于2。否则的话,即线程的优先级小于16,或者有TS_RUNQMATCH标志,那么就得保证被选的cpu的run queue长度和分区中下个cpu的长度完全match。
} else { /* * Migrate to a cpu in the new partition. */ cp = disp_lowpri_cpu(tp->t_cpupart->cp_cpulist, tp->t_lpl, tp->t_pri, NULL); } ASSERT((cp->cpu_flags & CPU_QUIESCED) == 0);
那么如果线程的分区和被选的cpu的分区不一样,就得将cpu移到一个新的分区上。
} else { /* * It is possible that t_weakbound_cpu != t_bound_cpu (for * a short time until weak binding that existed when the * strong binding was established has dropped) so we must * favour weak binding over strong. */ cp = tp->t_weakbound_cpu ? tp->t_weakbound_cpu : tp->t_bound_cpu; }
否则如果线程是被绑定的,那么就简单了,直接选取绑定的那个cpu。
/* * A thread that is ONPROC may be temporarily placed on the run queue * but then chosen to run again by disp. If the thread we're placing on * the queue is in TS_ONPROC state, don't set its t_waitrq until a * replacement process is actually scheduled in swtch(). In this * situation, curthread is the only thread that could be in the ONPROC * state. */ if ((tp != curthread) && (tp->t_waitrq == 0)) { hrtime_t curtime; curtime = gethrtime_unscaled(); (void) cpu_update_pct(tp, curtime); tp->t_waitrq = curtime; } else { (void) cpu_update_pct(tp, gethrtime_unscaled()); }
这一段主要用来更新线程的cpu使用时间和百分比。
dp = cp->cpu_disp; disp_lock_enter_high(&dp->disp_lock); DTRACE_SCHED3(enqueue, kthread_t *, tp, disp_t *, dp, int, 0); TRACE_3(TR_FAC_DISP, TR_BACKQ, "setbackdq:pri %d cpu %p tid %p", tpri, cp, tp);#ifndef NPROBE /* Kernel probe */ if (tnf_tracing_active) tnf_thread_queue(tp, cp, tpri);#endif /* NPROBE */ ASSERT(tpri >= 0 && tpri disp_npri); THREAD_RUN(tp, &dp->disp_lock); /* set t_state to TS_RUN */
拿到分配器锁,并将线程设置为TS_RUN状态。
tp->t_disp_queue = dp; tp->t_link = NULL; dq = &dp->disp_q[tpri]; dp->disp_nrunnable++; if (!bound) dp->disp_steal = 0; membar_enter();
获得当前优先级下的分配器指针。
if (dq->dq_sruncnt++ != 0) { ASSERT(dq->dq_first != NULL); dq->dq_last->t_link = tp; dq->dq_last = tp; } else { ASSERT(dq->dq_first == NULL); ASSERT(dq->dq_last == NULL); dq->dq_first = dq->dq_last = tp; BT_SET(dp->disp_qactmap, tpri); if (tpri > dp->disp_maxrunpri) { dp->disp_maxrunpri = tpri; membar_enter(); cpu_resched(cp, tpri); } }
如果当前优先级下的分配器队列长度不等于0,这个就表示有另外的线程等待运行,那么就将当前的线程插入到该分配器队列的尾部。
否则也将该线程插入到分配器队列尾部,然后判断线程的优先级是否比当前CPU上运行的最高优先级的线程还要高,如果是的话,就调用cpu_resched()来激活一个抢占过程。
if (!bound && tpri > dp->disp_max_unbound_pri) { if (tp == curthread && dp->disp_max_unbound_pri == -1 && cp == CPU) { /* * If there are no other unbound threads on the * run queue, don't allow other CPUs to steal * this thread while we are in the middle of a * context switch. We may just switch to it * again right away. CPU_DISP_DONTSTEAL is cleared * in swtch and swtch_to. */ cp->cpu_disp_flags |= CPU_DISP_DONTSTEAL; } dp->disp_max_unbound_pri = tpri; }
如果run queue里没有其他未绑定的线程,通过设置CPU_DISP_DONTSTEAL来表示我们正在context switch中,并且马上就要再一次切换到该线程上运行。这么做是为了防止该线程被移到其他的CPU上。
(*disp_enq_thread)(cp, bound);}
最后调用disp_enq_thread指针,如果被选中的cpu在睡觉的话,我们得唤醒它,让它起来干活,:)
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/41699/showart_1286912.html |
|