• 主调度器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来返回值进而赋值是很好的选择。          

  • 相关阅读:
    如何在android项目中引用project作为类库引用
    Unity3d之MonoBehaviour的可重写函数整理
    Phonegap hello world 不容易啊~!
    数据结构,到底如何用中学,学中用?
    自动化测试(1610)
    软件测试人员的发展路线
    软件测试分类
    我的第一篇博客随笔
    安装虚拟机和Linux系统的学习
    英雄联盟的游戏经验
  • 原文地址:https://www.cnblogs.com/openix/p/3272406.html
Copyright © 2020-2023  润新知