中断处理完毕后,系统有三种执行流向:
1)直接返回中断前的状态;2)系统重新进行调度;3)进行信号处理;我们此处重点关注:在用户态下发生scheduler_tick,且已判定当前进程可被抢占的情形(此处以ARM为例)。__irq_usr:#......b ret_to_user_from_irqENTRY(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来返回值进而赋值是很好的选择。