asmlinkage void __sched schedule(void)
{
struct task_struct *prev, *next;
unsigned long *switch_count;
struct rq *rq;
int cpu;
need_resched:
preempt_disable(); //为什么要关抢占,不关有什么影响
cpu = smp_processor_id();
rq = cpu_rq(cpu);
rcu_sched_qs(cpu);
prev = rq->curr; //为什么要根据当前cpu id来取rq,然后是rq->curr ? 为什么不直接current?
switch_count = &prev->nivcsw; //默认为非主动调度计数(抢占)
release_kernel_lock(prev);
need_resched_nonpreemptible:
schedule_debug(prev);
if (sched_feat(HRTICK))
hrtick_clear(rq);
spin_lock_irq(&rq->lock);
update_rq_clock(rq);
clear_tsk_need_resched(prev);
if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
//当前进程不是出于运行态,并且不是被抢占。不能只检查抢占计数,因为可能某个进程(如网卡轮询)直接
//调用了schedule,如果不判断prev->stat就可能误deactivate_task
//进程为R状态,到达这里,有两种可能,一种是主动schedule,另外一种是被抢占。被抢占
//有两种情况,一种是时间片到点,一种是时间片没到点。
//时间片到点后,主要是置当前进程的need_resched标志,接下来在时钟中断结束后,会preempt_schedule_irq抢占调度
if (unlikely(signal_pending_state(prev->state, prev)))
prev->state = TASK_RUNNING;
else
deactivate_task(rq, prev, 1); //将当前进程从队列里摘除,task->on_rq设置为0, 例如:
set_current_state(TASK_INTERRUPTIBLE);schedule()
switch_count = &prev->nvcsw; //如果不是被抢占的,就累加主动切换次数
}
pre_schedule(rq, prev);
if (unlikely(!rq->nr_running))
idle_balance(cpu, rq);
put_prev_task(rq, prev); //CFS,if (prev->on_rq) { __enqueue_entity();}
//CFS,如果进程之前是被抢占的(或者是主动调度schedule),则将此进程入运行队列红黑树,这里隐含当前
进程并不在红黑树运行队列里
//RT,if (p->se.on_rq) enqueue_pushable_task(rq, p); 如果是被抢占的(或者是主动schedule),则入
pushable队列。
next = pick_next_task(rq);
// CFS,pick_next_task_fair,从红黑树里,选出vtime最小的那个进程,调用set_next_entity将其出队。
这里隐含的意思是,当前正在运行的进程,不在红黑树cpu运行队列中,但是他的on_rq参数为1;
RT,pick_next_task_rt,取出合适的进程后,dequeue_pushable_task,
从pushable队列里取出来,实际上,对于RT进程,put和pick并不操作运行队列
//对于FIFO和RR的区别,在scheduler_tick中通过curr->sched_class->task_tick进入到task_tick_rt的处理
,
如果是非RR的进程则直接返回,否则递减时间片,如果时间片耗完,则需要将当前进程放到运行队列的末尾,
这个时候才操作运行队列(FIFO和RR进程,是否位于同一个plist队列?),时间片到点,会重新移动当前进
程requeue_task_rt,进程会被加到队列尾,接下来set_tsk_need_resched触发调度,进程被抢占进入
schedule
if (likely(prev != next)) {
sched_info_switch(prev, next);
perf_event_task_sched_out(prev, next, cpu);
rq->nr_switches++;
rq->curr = next;
++*switch_count;
context_switch(rq, prev, next); /* unlocks the rq */
/*
* the context switch might have flipped the stack from under
* us, hence refresh the local variables.
*/
cpu = smp_processor_id();
rq = cpu_rq(cpu);
} else
spin_unlock_irq(&rq->lock);
post_schedule(rq);
if (unlikely(reacquire_kernel_lock(current) < 0))
goto need_resched_nonpreemptible;
preempt_enable_no_resched();
if (need_resched())
goto need_resched;
}
if (!(prev->state & TASK_RUNNING_MUTEX) &&prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
if (unlikely(signal_pending_state(prev->state, prev)))
prev->state = TASK_RUNNING;
else
deactivate_task(rq, prev, 1);
switch_count = &prev->nvcsw;
}
if条件里的prev->state,代表当前进程没有处于running态,
!(preempt_count() & PREEMPT_ACTIVE表示当前进程不是被抢占,
就把当前进程从队列里取出来 deactivate_task
这段代码的意思是,如果有类似这么一些语句:
set_current_state(TASK_INTERRUPTIBLE);
schedule();
那么将会把当前进程从运行队列里删除
接下来是
put_prev_task(rq, prev);
如果是cfs,则为put_prev_task_fair, 这个函数主要就是更新当前进程的虚拟运行时间等,如果此进程之前
就在运行队列上,就重新把它插入队列,这种情况什么时候发生?时间片到期的进程,也需要更新
if (prev->on_rq) {
__enqueue_entity(cfs_rq, prev);
}
proc的调度相关字段解释 2.6.32内核
CONFIG_SCHED_DEBUG (需要配置CONFIG_SCHEDSTAT才能显示更多信息)
/proc/pid/sched 显示信息解释如下:
ata/2 (111, #threads: 1)
---------------------------------------------------------
se.exec_start : 1159.482823 //此进程最近被调度到的开始执行时刻 ns
se.vruntime : -1.044234 //虚拟运行时间,cfs调度用
se.sum_exec_runtime : 0.000000 //累计运行时间
se.avg_overlap : 0.000000
se.avg_wakeup : 3.617187 //当前进程最近一次调用try_to_wake_up为止的单次执行时间
se.avg_running : 0.000000 //平均单次执行时间
//以下开始需要同时配置CONFIG_SCHEDSTATS才能显示
se.wait_start : 0.000000 //最近一次当前进程被入队的时刻 cfs使用
se.sleep_start : 1159.482823 //此进程最近一次被从队列里取出,并被置S状态的时刻
se.block_start : 0.000000 //此进程最近一次被从队列里取出,并被置D状态的时刻
se.sleep_max : 0.000000 //最长处于S状态时间
se.block_max : 0.482651 //最长处于D状态时间
se.exec_max : 0.000000 //最长单次执行时间
se.slice_max : 0.000000 //曾经获得时间片的最长时间 (当cpu load过高时开始计算)
se.wait_max : 0.000000 //最长在就绪队列里的等待时间
se.wait_sum : 0.000000 //累计在就绪队列里的等待时间
se.wait_count : 2 //累计等待次数 (被出队的次数) cfs使用,当被选中做下一个待运行进程时(set_next_entity),等待结束
se.iowait_sum : 0.000000 //io等待时间
se.iowait_count : 0 //io等待次数 io_schedule调用次数
sched_info.bkl_count : 0 //此进程大内核锁调用次数
se.nr_migrations : 2 //需要迁移当前进程到其他cpu时累加此字段
se.nr_migrations_cold : 0 //2.6.32 代码里赋值都是0
se.nr_failed_migrations_affine : 0 //进程设置了cpu亲和,进程迁移时检查失败的次数
se.nr_failed_migrations_running : 0 //当前进程出入R,不运行迁移的次数
se.nr_failed_migrations_hot : 0 //当前进程因为是cache hot导致迁移失败的次数
se.nr_forced_migrations : 0 //在当前进程cache hot下,由于负载均衡尝试多次失败,强行进行迁移的次数
se.nr_forced2_migrations : 1 //在当前进程是cache hot下,设置此进程进行迁移的次数
se.nr_wakeups : 1 // 被唤醒的累计次数
se.nr_wakeups_sync : 0 // 同步唤醒次数,即a唤醒b,a立刻睡眠,b被唤醒的次数 /* waker goes to sleep after wakup */
se.nr_wakeups_migrate : 0 //被唤醒得到调度的当前cpu,不是之前睡眠的cpu的次数
se.nr_wakeups_local : 0 //被本地唤醒的次数
se.nr_wakeups_remote : 1 //其他唤醒累计次数
se.nr_wakeups_affine : 0 //考虑了任务的cache亲和性的唤醒次数
se.nr_wakeups_affine_attempts : 0 //尝试进行考虑了任务的cache亲和性的唤醒次数
se.nr_wakeups_passive : 0 //2.6.32 代码里赋值都是0
se.nr_wakeups_idle : 0 //2.6.32 代码里赋值都是0
avg_atom : 0.000000 //本进程平均切换耗时
avg_per_cpu : 0.000000 //如果本进程曾经被推或者拉到其他cpu上,则计算每个cpu上的平均耗时
nr_switches : 2 //主动切换和被动切换的累计次数
nr_voluntary_switches : 2 //主动切换次数
nr_involuntary_switches : 0 //被动切换次数
se.load.weight : 1024 //权重,和负载均衡有关
policy : 0 //进程属性,0为非实时
#define SCHED_NORMAL 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#define SCHED_BATCH 3
#define SCHED_IDLE 5
prio : 120 //优先级
clock-delta : 165 //测试用
/proc/sched_debug
此为cpu15运行队列的调度信息:
cpu#15, 1595.968 MHz
.nr_running : 0 //运行队列里处于就绪态进程的数目
.load : 0 //当前cpu负载
.nr_switches : 120820818 //切换累计次数
.nr_load_updates : 674238115 //cpu权重更新的次数,和负载均衡有关
.nr_uninterruptible : -30
.next_balance : 8012.054504 //下次执行定时负载均衡的时间
.curr->pid : 0 //当前运行进程
.clock : 3717386659.081997 //当前cpu运行队列时刻
.cpu_load[0] : 0 //以下为cpu历史负载数组
.cpu_load[1] : 0
.cpu_load[2] : 0
.cpu_load[3] : 0
.cpu_load[4] : 0
//以下开始需要同时配置CONFIG_SCHEDSTATS才记录
.yld_count : 8236 //调yield的次数
.sched_switch : 0
.sched_count : 122061248 //调用schedule的次数,>=nr_switches
.sched_goidle : 30689623 //切换到idle进程的次数
.avg_idle : 1000000 //cpu处于idle的平均时间
.ttwu_count : 46238267 //此cpu try_to_wake_up唤醒进程的次数
.ttwu_local : 30988951 //本地唤醒的次数,即进程睡眠前所在cpu为当前cpu
.bkl_count : 2090 //此cpu上大内核锁调用次数
cfs_rq[15]:/
.exec_clock : 545099081.498339 //非实时线程得到调度的累计时间
.MIN_vruntime : 0.000001 //当前cfs 运行队列里虚拟时间最小的那个进程的vtime
.min_vruntime : 770437019.732761 //当前cfs 运行队列虚拟时间下限,当处于rb树最左边进程运行虚拟时间,减去此值大于一个阈值后抢占当前进程
.max_vruntime : 0.000001 // 2.6.32里没有找到这个字段
.spread : 0.000000
.spread0 : 407973445.279093 //cpu0上的min_vruntime与当前cpu的min_vruntime差值
.nr_running : 0 //当前cpu非实时进程在就绪队列中的个数
.load : 0 //非实时进程负载
.nr_spread_over : 6811 //当前cpu中,长时间非实时进程由一个进程占有的次数
s64 d = se->vruntime - cfs_rq->min_vruntime;
if (d > 3*sysctl_sched_latency)
schedstat_inc(cfs_rq, nr_spread_over);
.shares : 0
rt_rq[15]:/
.rt_nr_running : 0 //处于就绪队列的实时进程个数
.rt_throttled : 0 //是否限制实时进程的带宽运行时间,实时进程是否需要让出cpu
.rt_time : 0.000000 //做实时进程带宽分析用
.rt_runtime : 950.000000 //做实时进程带宽分析用
另外,还有一个:
/proc/schedstat
字段详细解释文档在内核目录的:
documentation/scheduler/sched-stats.txt
和负载均衡关系大些