主调度器schedule

中断处理完毕后,系统有三种执行流向:                                                                               
1)直接返回中断前的状态;
2)系统重新进行调度;
3)进行信号处理;

我们此处重点关注:在用户态下发生scheduler_tick,且已判定当前进程可被抢占的情形(此处以ARM为例)。

__irq_usr:
#......
b ret_to_user_from_irq

ENTRY(ret_to_user_from_irq)
    ldr r1, [tsk, #TI_FLAGS]
#define _TIF_WORK_MASK (_TIF_SIGPENDING | _TIF_NEED_RESCHED |  _TIF_NOTIFY_RESUME)
    tst r1, #_TIF_WORK_MASK  ;判定是否有信号需要处理,或者被抢占
    bne work_pending
#......
ENDPROC(ret_to_user_from_irq)

work_pending:
    mov r0, sp              @ 'regs'
    mov r2, why             @ 'syscall'
    bl  do_work_pending 
#......

当do_work_pending中检查thread_info的flags知道当前进程可被抢占时,则启动主调度器schedule                            

schedule()----prev = rq->curr  但前运行进程
         |----next = pick_next_task(rq)  选择下一运行进程
         |---->context_switch(rq, prev, next)

 

context_switch()---->prepare_task_switch(rq, prev, next) 
               |     更新prev,next的进程信息统计量
|----mm = next->mm; 取得即将被调度进入的进程的内存描述符 |----oldmm = prev->active_mm;
               |      取得即将被调度出去的进程的运行时内存描述符。
|   此时需要区分即将被调入的进程是普通进程还是内核线程|     被调出的进程是普通进程还是内核线程|    引入active_mm的目的在于实现 lazy TLB |   (当我们把运行的进程个数限于2个,一个是普通进程 |   另一个是内核线程时就容易明白了)。
               |
|    我们讨论普通情形:即将被调入调出的进程都为普通进程, |    且oldmm与mm不同,需要切换内存页表。 |----switch_mm(oldmm, mm, next);
               |      切换页表,刷TLB (注意假设oldmm与mm不同)
               |       对于vivt型cache需要注意对cache的操作
|----switch_to(prev, next, prev);
               |     cpu_context_save切换(运行到next进程)
               |        对于unicore保存r4-r15,r16-r26,
               |        r27(fp),r29(sp),r30(lr)    
               |        不需保存r0-r3,r31(pc),r28(ip)
|----finish_task_switch(this_rq(), prev);
               |     对被调度出的进程进行相关处理
|---->mmdrop(mm);

 

更新prev,next的进程信息统计量(注意exec_start,prev_sum_exec_runtime)
prepare_task_switch(rq, prev, next)
|---->sched_info_switch(prev, next)
#ifdef CONFIG_SCHEDSTATS
     |----__sched_info_switch(prev, next)
         |---->sched_info_depart(prev)
               |----prev->sched_info.last_queued = task_rq(t)->clock;
         |---->sched_info_arrive(next)
               |----t->sched_info.run_delay += 
                      now - t->sched_info.last_queued; |----t->sched_info.last_queued = 0; |----t->sched_info.last_arrival = now; #endif
pick_next_task()---->p = fair_sched_class.pick_next_task(rq);
               |     即pick_next_task_fair()
                     |---->se = pick_next_entity(cfs_rq);
                     |---- set_next_entity(cfs_rq, se);
                           |---->update_stats_curr_start(cfs_rq, se);
                           |     即se->exec_start = 
                                    rq_of(cfs_rq)->clock_task; |---- cfs_rq->curr = se; |---- se->prev_sum_exec_runtime =
                                    se->sum_exec_runtime;

 

关于switch_to(prev, next, prev)
《深入Linux架构》p_83-84,83页上的图看懂了,但是84页第三段“因此……”没看明白,和同学讨论了一下:
A->B->C->A,第二次调度进A运行时,确实需要知道上次运行的是C,以便调用finish_task_switch(),其实自己理解错误的原因很低级(只顾着看汇编,自己想多了):
  函数UP中有变量a、b,函数UP调用LOW(a,b),很明显UP中的a值没有被改变,如果要改变,则需要a = LOW(a,b);
  当然我们也可以用传地址的方式来完成啊,当我尝试用传地址的方式来修改UP中的变量a时,发现我们需要保存即将恢复的进程中a的地址才能在下次运行时通过地址来修改a的值,这并不明智,因此对于unicore或ARM来说通过r0来返回值进而赋值是很好的选择。          

posted on 2013-08-21 12:45  阿加  阅读(1866)  评论(0编辑  收藏  举报

导航