p->state = TASK_RUNNING;
内核也使用 set_task_state
和 set_current_state
宏:他们分别设置执行进程的状态和当前执行进程的状态。
二:进程调度
在一个多进程的操作系统中,进程调度是一个关键性的问题,它对系统性能有着绝对的影响。
一般来说,操作系统是应用程序和可用资源之间的“中间件”。 计算机的可用资源有内存,CPU,其他硬件等。CPU也是一种资源,调度器可以临时分配一个执行的任务在CPU上运行。调度器使得同时执行多个程序成为可能,各种各样的进程就可以同时共享使用CPU了。
内核必须提供一种方法,使各种进程任务能公平的共享cpu时间,又能考虑不同任务的优先级,能有效的分配CPU的时间。也要防止CPU尚有能力且有进程执行,但是由于某种原因而长时间得不到执行的情况。一旦这些情况发生,调度机制要能识别与化解。
调度器满足的目标
为了满足上面的目标,所以设计一个调度器要考虑的为题有:
1、 调度的时机
也就是说在什么情况下,什么时候进行调度
2、调度的策略
根据什么准则挑选下一个进程进入cpu运行
3、调度的方式
是可剥夺,还是不可剥夺。当运行的进程不愿意资源放弃CPU的使用权,是否可以强制性的剥夺使用权。等等问题。
首先,自愿的调度随时都可以进行。在内核里,一个进程可以通过 schedule()
启动一次调度,也可以在 schedule()
之前将本进程的状态设置为TASK_INTERRUPTIBLE 或者 TASK_UNINTERRUPTIBLE, 暂时放弃运行而进入睡眠。
在用户空间中,则可以通过系统调用 pause()
来达到同样的目的。也可以为这种自愿放弃时间运行加上时间限制。在内核中有个 schedule_timeout()
来达到这个目的。 在用户空间则是 nanosleep()
。
从应用角度看,只有在用户空间自愿放弃运行着一主动是可见的,在内核主动放弃运行时不可见的,它隐藏在其他可能受阻的系统调用中。几乎所有涉及到外设的系统调用,如open(), read(), write(), select() 等,都是可能受阻。
除此之外,调度还可以是非自愿的,即强制发生在每次从系统调用返回前夕,以及每次从中断或者异常处理返回到用户空间的前夕。
注意:这里“返回到用户空间”是几个关键字,因为这意味着只有在用户空间(当CPU在用户空间运行)发生的中断或者异常才会引起调度。
进程的分类
1、一种分类
IO密集型
计算密集型
2、 另外一种分类
交互式进程
批处理进程
实时进程与普通进程
在linux中, 调度算法可以明确的确认所有实时进程的身份, 但是没办法区分交互式程序和批处理程序(统称为普通进程), linux2.6的调度程序实现了基于进程过去行为的启发式算法, 以确定进程应该被当做交互式进程还是批处理进程. 当然与批处理进程相比, 调度程序有偏爱交互式进程的倾向。
根据进程的不同分类Linux采用不同的调度策略.
实时进程:采用FIFO 或者 Round Robin的调度策略
普通进程:则要区分交互式和批处理的不同。传统Linux调度器提高交互式应用的优先级,使得它们能更快地被调度。而CFS和RSDL等新的调度器的核心思想是”完全公平”。这个设计理念不仅大大简化了调度器的代码复杂度,还对各种调度需求的提供了更完美的支持.
linux进程的调度算法其实经过了很多次的演变, 但是其演变主要是针对与普通进程的, 因为前面我们提到过根据进程的不同分类Linux采用不同的调度策略.实时进程和普通进程采用了不同的调度策略, 更一般的普通进程还需要启发式的识别批处理进程和交互式进程.
Linux调度器的演进
一开始的调度器是复杂度为O(n)的始调度算法(实际上每次会遍历所有任务,所以复杂度为O(n)), 这个算法的缺点是当内核中有很多任务时,调度器本身就会耗费不少时间,所以,从linux2.5开始引入赫赫有名的O(1)调度器。
然而,linux是集全球很多程序员的聪明才智而发展起来的超级内核,没有最好,只有更好,在O(1)调度器风光了没几天就又被另一个更优秀的调度器取代了,它就是CFS调度器Completely Fair Scheduler.
这个也是在2.6内核中引入的,具体为2.6.23,即从此版本开始,内核使用CFS作为它的默认调度器,O(1)调度器被抛弃了, 其实CFS的发展也是经历了很多阶段,最早期的楼梯算法(SD), 后来逐步对SD算法进行改进出RSDL(Rotating Staircase Deadline Scheduler), 这个算法已经是”完全公平”的雏形了, 直至CFS是最终被内核采纳的调度器, 它从RSDL/SD中吸取了完全公平的思想,不再跟踪进程的睡眠时间,也不再企图区分交互式进程。它将所有的进程都统一对待,这就是公平的含义。CFS的算法和实现都相当简单,众多的测试表明其性能也非常优越。
另外一种是通过周期性的机制,以固定频率运行,不时的检查是否有必要
因此当前linux的调度程序由两个调度器组成:主调度器,周期性调度器(两者又统称为通用调度器(generic scheduler)或核心调度器(core scheduler))
并且每个调度器包括两个内容:调度框架(其实质就是两个函数框架)及调度器类
**
6种调度策略
linux内核目前实现了6中调度策略(即调度算法), 用于对不同类型的进程进行调度, 或者支持某些特殊的功能。
比如 SCHED_NORMAL
和 SCHED_BATCH
调度普通的非实时进程, SCHED_FIFO
和 SCHED_RR
和 SCHED_DEADLINE
则采用不同的调度策略调度实时进程, SCHED_IDLE
则在系统空闲时调用idle进程.
字 段
所在调度器类
SCHED_NORMAL
(也叫SCHED_OTHER)用于普通进程,通过CFS调度器实现。SCHED_BATCH用于非交互的处理器消耗型进程。SCHED_IDLE是在系统负载很低时使用
SCHED_BATCH
SCHED_NORMAL普通进程策略的分化版本。采用分时策略,根据动态优先级(可用nice()API设置),分配CPU运算资源。注意:这类进程比上述两类实时进程优先级低,换言之,在有实时进程存在时,实时进程优先调度。但针对吞吐量优化, 除了不能抢占外与常规任务一样,允许任务运行更长时间,更好地使用高速缓存,适合于成批处理的工作
SCHED_IDLE
优先级最低,在系统空闲时才跑这类进程(如利用闲散计算机资源跑地外文明搜索,蛋白质结构分析等任务,是此调度策略的适用者)
CFS-IDLE
SCHED_FIFO
先入先出调度算法(实时调度策略),相同优先级的任务先到先服务,高优先级的任务可以抢占低优先级的任务
SCHED_RR
轮流调度算法(实时调度策略),后者提供 Roound-Robin 语义,采用时间片,相同优先级的任务当用完时间片会被放到队列尾部,以保证公平性,同样,高优先级的任务可以抢占低优先级的任务。不同要求的实时任务可以根据需要用sched_setscheduler() API设置策略
SCHED_DEADLINE
新支持的实时进程调度策略,针对突发型计算,且对延迟和完成时间高度敏感的任务适用。基于Earliest Deadline First (EDF) 调度算法
linux内核实现的6种调度策略, 前面三种策略使用的是cfs调度器类,后面两种使用rt调度器类, 最后一个使用DL调度器类
5个调度器类
而依据其调度策略的不同实现了5个调度器类, 一个调度器类可以用一种种或者多种调度策略调度某一类进程, 也可以用于特殊情况或者调度特殊功能的进程.
调 度 器 类
对应调度策略
stop_sched_class
优先级最高的线程,会中断所有其他线程,且不会被其他任务打断
作用
1.发生在cpu_stop_cpu_callback 进行cpu之间任务migration
2.HOTPLUG_CPU的情况下关闭任务
无, 不需要调度普通进程
dl_sched_class
采用EDF最早截至时间优先算法调度实时进程
SCHED_DEADLINE
rt_sched_class
采用提供 Roound-Robin算法或者FIFO算法调度实时进程
具体调度策略由进程的task_struct->policy指定
SCHED_FIFO, SCHED_RR
fair_sched_clas
采用CFS算法调度普通的非实时进程
SCHED_NORMAL, SCHED_BATCH
idle_sched_class
采用CFS算法调度idle进程, 每个cup的第一个pid=0线程:swapper,是一个静态线程。调度类属于:idel_sched_class,所以在ps里面是看不到的。一般运行在开机过程和cpu异常的时候做dump
SCHED_IDLE
其所属进程的优先级顺序为
stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
3个调度实体
调度器不限于调度进程, 还可以调度更大的实体, 比如实现组调度: 可用的CPUI时间首先在一半的进程组(比如, 所有进程按照所有者分组)之间分配, 接下来分配的时间再在组内进行二次分配.
这种一般性要求调度器不直接操作进程, 而是处理可调度实体, 因此需要一个通用的数据结构描述这个调度实体,即seched_entity结构, 其实际上就代表了一个调度对象,可以为一个进程,也可以为一个进程组.
linux中针对当前可调度的实时和非实时进程, 定义了类型为seched_entity的3个调度实体
对应调度器类
调度器类的就绪队列
另外,对于调度框架及调度器类,它们都有自己管理的运行队列,调度框架只识别rq(其实它也不能算是运行队列),而对于cfs调度器类它的运行队列则是cfs_rq(内部使用红黑树组织调度实体),实时rt的运行队列则为rt_rq(内部使用优先级bitmap+双向链表组织调度实体), 此外内核对新增的dl实时调度策略也提供了运行队列dl_rq
调度器整体框架
本质上, 通用调度器(核心调度器)是一个分配器,与其他两个组件交互.
调度器用于判断接下来运行哪个进程.
内核支持不同的调度策略(完全公平调度, 实时调度, 在无事可做的时候调度空闲进程,即0号进程也叫swapper进 程,idle进程), 调度类使得能够以模块化的方法实现这些侧露额, 即一个类的代码不需要与其他类的代码交互
当调度器被调用时, 他会查询调度器类, 得知接下来运行哪个进程
在选中将要运行的进程之后, 必须执行底层的任务切换.
这需要与CPU的紧密交互. 每个进程刚好属于某一调度类, 各个调度类负责管理所属的进程. 通用调度器自身不涉 及进程管理, 其工作都委托给调度器类.
每个进程都属于某个调度器类(由字段task_struct->sched_class标识), 由调度器类采用进程对应的调度策略调度(由task_struct->policy )进行调度, task_struct也存储了其对应的调度实体标识
上面的调度部分,我有的是直接拿下面参考博文,它的这个系列写的非常好。大家可以去看看。侵删
https://blog.csdn.net/gatieme/article/details/51699889
== just do it ==