schedule调度流程
schedule就是主调度器的函数, 在内核中如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule,
该函数完成如下工作:
- 确定当前就绪队列, 并在保存一个指向当前(仍然)活动进程的task_struct指针。
-
检查死锁, 关闭内核抢占后调用__schedule完成内核调度。
-
恢复内核抢占, 然后检查当前进程是否设置了重调度标志TLF_NEDD_RESCHED, 如果该进程被其他进程设置了TIF_NEED_RESCHED标志, 则函数重新执行进行调度。
do { preempt_disable(); /* 关闭内核抢占 */ __schedule(false); /* 完成调度 */ sched_preempt_enable_no_resched(); /* 开启内核抢占 */ } while (need_resched()); /* 如果该进程被其他进程设置了TIF_NEED_RESCHED标志,则函数重新执行进行调度 */
__schedule如何完成内核抢占
查看__schedule 函数实现,主要做如下工作:
-
完成一些必要的检查, 并设置进程状态, 处理进程所在的就绪队列。
-
调度全局的pick_next_task选择抢占的进程。
-
如果当前cpu上所有的进程都是cfs调度的普通非实时进程, 则直接用cfs调度, 如果无程序可调度则调度idle进程。
-
否则从优先级最高的调度器类sched_class_highest(目前是stop_sched_class)开始依次遍历所有调度器类的pick_next_task函数, 选择最优的那个进程执行。
-
-
context_switch完成进程上下文切换。
-
调用switch_mm(), 把虚拟内存从一个进程映射切换到新进程中。
-
调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息。
-
调度的内核抢占和用户抢占
内核在完成调度的过程中总是先关闭内核抢占, 等待内核完成调度的工作后, 再把内核抢占开启, 如果在内核完成调度器过程中, 这时候如果发生了内核抢占,我们的调度会被中断, 而调度却还没有完成, 这样会丢失我们调度的信息。
在调度完成后, 内核会去判断need_resched条件, 如果这个时候为真, 内核会重新进程一次调度,此次调度由于发生在内核态因此仍然是一次内核抢占need_resched条件其实是判断need_resched标识TIF_NEED_RESCHED的值,内核在thread_info的flag中设置了一个标识来标志进程是否需要重新调度, 即重新调度need_resched标识TIF_NEED_RESCHED,内核在即将返回用户空间时会检查标识TIF_NEED_RESCHED标志进程是否需要重新调度,如果设置了,就会发生调度, 这被称为用户抢占,
而内核抢占是通过自旋锁preempt_count实现的, 同样当内核可以进行内核抢占的时候(比如从中断处理程序返回内核空间或内核中的进程被堵塞的时候),内核会检查preempt_count和TIF_NEED_RESCHED标志,如果进程设置了 TIF_NEED_RESCHED标志,并且preempt_count为0,则发生内核抢占。