一篇文章让你了解Linux进程调度器( 四 )


  • 调用cond_resched()时
  • 显式调用schedule()时
  • 从系统调用或者异常中断返回用户空间时
  • 从中断上下文返回用户空间时
当开启内核抢占(默认开启)时 , 会多出几个调度时机 , 如下
  • 在系统调用或者异常中断上下文中调用preempt_enable()时(多次调用preempt_enable()时 , 系统只会在最后一次调用时会调度)
  • 在中断上下文中 , 从中断处理函数返回到可抢占的上下文时(这里是中断下半部 , 中断上半部实际上会关中断 , 而新的中断只会被登记 , 由于上半部处理很快 , 上半部处理完成后才会执行新的中断信号 , 这样就形成了中断可重入)
而在系统启动调度器初始化时会初始化一个调度定时器 , 调度定时器每隔一定时间执行一个中断 , 在中断会对当前运行进程运行时间进行更新 , 如果进程需要被调度 , 在调度定时器中断中会设置一个调度标志位 , 之后从定时器中断返回 , 因为上面已经提到从中断上下文返回时是有调度时机的 , 在内核源码的汇编代码中所有中断返回处理都必须去判断调度标志位是否设置 , 如设置则执行schedule()进行调度 。
而我们知道实时进程和普通进程是共存的 , 调度器是怎么协调它们之间的调度的呢 , 其实很简单 , 每次调度时 , 会先在实时进程运行队列中查看是否有可运行的实时进程 , 如果没有 , 再去普通进程运行队列找下一个可运行的普通进程 , 如果也没有 , 则调度器会使用idle进程进行运行 。
系统并不是每时每刻都允许调度的发生 , 当处于硬中断期间的时候 , 调度是被系统禁止的 , 之后硬中断过后才重新允许调度 。而对于异常 , 系统并不会禁止调度 , 也就是在异常上下文中 , 系统是有可能发生调度的 。
4.3 抢占标识TIF_NEED_RESCHED
内核在检查need_resched标识TIF_NEED_RESCHED的值判断是否需要抢占当前进程, 内核在thread_info的flag中设置了一个标识来标志进程是否需要重新调度, 即重新调度need_resched标识TIF_NEED_RESCHED, 内核在即将返回用户空间时会检查标识TIF_NEED_RESCHED标志进程是否需要重新调度
系统中每个进程都有一个特定于体系结构的struct thread_info结构, 用户层程序被调度的时候会检查struct thread_info中的need_resched标识TLF_NEED_RESCHED标识来检查自己是否需要被重新调度.
如果内核检查进程的抢占标识被设置, 则会在一个关键的时刻, 调用调度器来完成调度和抢占的工作
4.4 内核抢占和用户抢占
而根据进程抢占发生的时机, 抢占可以分为内核抢占和用户抢占, 内核抢占就是指一个在内核态运行的进程, 可能在执行内核函数期间被另一个进程取
一般来说 , 用户抢占发生几下情况:
  • 从系统调用返回用户空间;
  • 从中断(异常)处理程序返回用户空间
内核抢占发生的时机 , 一般发生在:
  • 当从中断处理程序正在执行 , 且返回内核空间之前 。当一个中断处理例程退出 , 在返回到内核态时(kernel-space) 。这是隐式的调用schedule()函数 , 当前任务没有主动放弃CPU使用权 , 而是被剥夺了CPU使用权 。
  • 当内核代码再一次具有可抢占性的时候 , 如解锁(spin_unlock_bh)及使能软中断(local_bh_enable)等, 此时当kernel code从不可抢占状态变为可抢占状态时(preemptible again) 。也就是preempt_count从正整数变为0时 。这也是隐式的调用schedule()函数
  • 如果内核中的任务显式的调用schedule(), 任务主动放弃CPU使用权
  • 如果内核中的任务阻塞(这同样也会导致调用schedule()), 导致需要调用schedule()函数 。任务主动放弃CPU使用权
内核抢占采用同抢占标识的类似方法被实现, linux内核在thread_info结构中添加了一个自旋锁标识preempt_count, 称为抢占计数器(preemption counter).
struct thread_info
{
/* ...... */
int preempt_count; /* 0 => preemptable, <0 => BUG */
/* ...... */
}
 
一篇文章让你了解Linux进程调度器

文章插图
 
内核自然也提供了一些函数或者宏, 用来开启, 关闭以及检测抢占计数器preempt_coun的值, 这些通用的函数定义在include/asm-generic/preempt.h, 而某些架构也定义了自己的接口 ,  比如x86架构/arch/x86/include/asm/preempt.h


推荐阅读