调度时机一般可以分成两类:主动调度和强制调度。
在形式上一般是这样的:
内核在等待资源的时候,将当前进程移到等待队列,并主动调用schedule()放弃CPU;
主动调度的例子:
read()系统调用,会调用到wait_on_sync_kiocb(),其中有这么一段
while (iocb->ki_users) { set_current_state(TASK_UNINTERRUPTIBLE); if (!iocb->ki_users) break; schedule(); }大致就是,资源还未准备的话,就主动调用schedule()放弃CPU;
在形式上一般是这样的:
在系统调用 / 中断处理中设置TIF_NEED_SCHED;
系统调用返回用户态 / 中断返回(可能返回内核态)前,
检查TIF_NEED_SCHED(中断返回内核态还要检查preempt_count),
如果进行了设置,则在返回前调用schedule();
强制调度的例子:
IO中断的例子,
阻塞read()的IO资源读取完成,进入中断处理。
在中断处理中,调用try_to_wake_up()的封装函数,设置TIF_NEED_SCHED;
中断返回后(假设是返回到用户态),检查到TIF_NEED_SCHED被设置,调用schedule();
时钟中断的例子,
在时钟中断的中断处理中,调用scheduler_tick(),
最简单的情况是当普通进程的时间片耗尽后,设置TIF_NEED_SCHED;
中断返回后(假设是返回到用户态),检查到TIF_NEED_SCHED被设置,调用schedule();
系统调用的例子,
信号机制中,想发送信号到某个进程时,会调用specific_send_sig_info()。
在specific_send_sig_info()中,最后会调用signal_wake_up(),设置TIF_NEED_SCHED;
(底层还是try_to_wake_up())
系统调用返回后,检查到TIF_NEED_SCHED被设置,调用schedule();
基本摘自此博文,文章是讲抢占的,但是抢占就是调度的一种。
Linux用户抢占和内核抢占详解(概念, 实现和触发时机)–Linux进程的管理与调度(二十)
内核主动放弃CPU,具体情况就类似于上面所介绍的read()系统调用;时钟中断处理例程检查当前任务的时间片,当任务的时间片消耗完时,scheduler_tick()函数就会设置need_resched标志;信号量、等到队列、completion等机制唤醒时都是基于waitqueue的,而waitqueue的唤醒函数为default_wake_function,其调用try_to_wake_up将被唤醒的任务更改为就绪状态并设置need_resched标志。设置用户进程的nice值时,可能会使高优先级的任务进入就绪状态;改变任务的优先级时,可能会使高优先级的任务进入就绪状态;新建一个任务时,可能会使高优先级的任务进入就绪状态;对CPU(SMP)进行负载均衡时,当前任务可能需要放到另外一个CPU上运行;抢占就是强制调度,
也就是一般是系统调用 / 中断返回前,如在处理中设置了TIF_NEED_SCHED,那么就会触发的一种调度。
上面已经进行了大致的介绍。
其实说法不少,也看过把主动放弃CPU也视作抢占的一种。
我是觉得主动放弃这种拦不住的情况,不应该叫被抢占吧,
抢占定义为可被TIF_NEED_RESCHED和preempt_count拦下来的强制调度,感觉比较合适?
抢占可分为:
用户抢占,一般在系统调用返回 / 中断返回到用户态时被触发;
内核抢占,一般在中断返回到内核态时被触发。
在最原始的单核处理器下只要关闭中断就不会发生内核抢占,其他架构的细节尚未研究清楚;
用户抢占感觉没什么好说,基本就是强制调度。
内核抢占是比较特殊的,限制比用户抢占要多,两者的控制变量为:
用户抢占,TIF_NEED_RESCHED;
内核抢占,preempt_count和TIF_NEED_RESCHED,
也就是内核多一层是否抢占的检测,
preemot_count为0的时候表示开启抢占,总体情况比较多,细节尚未研究清楚;
而且也是个可选项,在编译内核时可以选择是否启用对内核抢占的支持。
内核在这些情况下是不允许被抢占的:
引自:Linux用户抢占和内核抢占详解(概念, 实现和触发时机)–Linux进程的管理与调度(二十)
内核正进行中断处理。在Linux内核中进程不能抢占中断(中断只能被其他中断中止、抢占,进程不能中止、抢占中断),在中断例程中不允许进行进程调度。进程调度函数schedule()会对此作出判断,如果是在中断中调用,会打印出错信息;
内核正在进行中断上下文的Bottom Half(中断下半部,即软中断)处理。硬件中断返回前会执行软中断,此时仍然处于中断上下文中。如果此时正在执行其它软中断,则不再执行该软中断;
内核的代码段正持有spinlock自旋锁、writelock/readlock读写锁等锁,处干这些锁的保护状态中。内核中的这些锁是为了在SMP系统中短时间内保证不同CPU上运行的进程并发执行的正确性。当持有这些锁时,内核不应该被抢占;
内核正在执行调度程序schedule()。抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序;
内核正在对每个CPU“私有”的数据结构操作(Per-CPU date structures)。在SMP中,对于per-CPU数据结构未用spinlocks保护,因为这些数据结构隐含地被保护了(不同的CPU有不一样的per-CPU数据,其他CPU上运行的进程不会用到另一个CPU的per-CPU数据)。但是如果允许抢占,但一个进程被抢占后重新调度,有可能调度到其他的CPU上去,这时定义的Per-CPU变量就会有问题,这时应禁抢占;
笔者暂时水平还是太有限,上文中的抢占对不可重入函数的影响,还有内核同步方面的介绍,
还不怎么看得懂,暂时只理解了大意。
笔者水平有限,
本节主要引自:内核随记(二)——内核抢占与中断返回
我们会听说:
从中断返回后,发生抢占或者信号处理等;从系统调用返回后,发生抢占或者信号处理等;本节就是借由大佬对返回处理的源码分析,大致了解一下这些处理流程。
当内核从中断返回时,应当考虑以下几种情况:
内核控制路径并发执行的数量:如果为1,则CPU返回用户态;挂起进程的切换请求:如果有挂起请求,则进行进程调度;否则,返回被中断的进程;待处理信号:如果有信号发送给当前进程,则必须进行信号处理;单步调试模式:如果调试器正在跟踪当前进程,在返回用户态时必须恢复单步模式;Virtual-8086模式:如果中断时CPU处于虚拟8086模式,则进行特殊的处理; #从中断返回 ret_from_intr: GET_THREAD_INFO(