35.Linux/Unix 系统编程手册(下) -- 进程的优先级和调度

1.进程优先级
	Linux 和 其他 Unix 实现一样,调度进度使用 cpu 的默认模型是循环时间共享。
  在这种模型中,每个进程轮流使用 cpu 一段时间,这段时间被称为时间片和量子。循环时间
  共享满足了交互式多任务系统的两个重要需求:
  	1.公平性:每个进程都有机会用到cpu
  	2.响应度:一个进程在使用 cpu 之前无需等待太多时间

  	在循环时间共享算法中,进程无法直接控制何时使用cpu以及使用cpu的时间。默认情况下,每个进程
  轮流使用cpu直至时间片用光或者自己放弃cpu(如进行睡眠或执行一个磁盘读取操作).如果所有的进程都试图
  尽可能多的使用cpu(即没有进程会被睡眠或被IO操作阻塞),那么它们使用cpu的时间是差不多的。
    
    进程特性nice值允许进程间接影响内核的调度算法。每个进程都有一个nice值,其取值范围在 -20(高优先级) 到 19(低优先级)
  默认值为0。在传统Unix实现中,只有特权进程才能够赋给自己一个负值(高优先级).非特权进程只能降低自己的优先级,即赋予一个大于0
  的nice值。这样做之后它们就对其他进程"nice"了。

  	使用 fork() 创建的子进程会继承 nice 值,并且该值在 exec() 调用中得以保持。

2.nice值的影响
	进程的调度不是严格按照nice值的层次进行的,相反,nice值是一个权重因素,它导致内核调度器倾向于拥有高优先级的进程。给一个
  进程赋予一个低优先级的nice值,并不会导致它完全无法获得cpu,但会导致它使用cpu的时间变少.

3.实时进程调度概述
	在一个系统上一般会同时运行交互式进程和后台进程。标准的内核调度算法一般能够为这些进程提供足够的性能和响应度。但实时应用对调度器
  有更加严格的要求。
    1.实时应用必须要为外部输入提供担保最大响应时间。在很多情况下,这些担保最大响应时间必须非常短。内核必须提供工具让优先级高的进程
  能够快速的获得cpu的控制权,抢占当前运行的所有进程。
    2.高优先级进程应该能够保持互斥的访问cpu直至它完成或者自动释放cpu
    3.实时应用应该能够精准的控制其他组件进程的调度顺序

    SUSv3 提供了实时进程调度API,这个API提供了2个实时调度策略:SCHED_RR 和 SCHED_FIFO。使用这2种策略中的任意一种进行调度的进程
  的优先级都高于标准循环时间分享策略。实时调度API使用常量 SCHED_OTHER 来标识 循环时间分享策略。

  	对于多处理器Linux系统来说,高优先级的可运行进程总是优于低优先级的进程并不适用。在多处理系统中,各个CPU拥有独立的运行队列。
 在多个进程的实时应用可以使用 CPU 亲和力 API 来避免这种调度行为。
    Linux 提供了99个实时优先级,其数值从 1 到 99(最高).

4.SCHED_RR 策略
	在 SCHED_RR(循环)策略中,优先级相同的进程以循环时间分享的方式执行。进程每次使用cpu的时间为一个固定长度的时间片。一旦被调度执行后,
  使用SCHED_RR 策略的进程会保持对 cpu 的控制直到下列条件的一个得到满足:
  	1.达到时间片的终点
  	2.自愿放弃cpu,这可能是由于执行了一个阻塞式的系统调用或者调用了 sched_yied()系统调用。
  	3.终止了
  	4.被一个优先级更高的进程抢占了
  	
  	对于上述列出的前2点情况,当运行在 SCHED_RR 策略下的进程丢掉 CPU 之后将会被放置在其优先级级别对应的队列的队尾。在最后一种情况,当优先级
   更高的进程执行结束之后,被抢占的进程会继续执行直到时间片剩余部分被消耗完(即抢占的进程仍然位于与其优先级级别对应的队列的队头).
    在 SCHED_RR 和 SCHED_FIFO 两种策略中,当运行的进程可能会因为下面某个原因而被抢占:
    	1.之前被阻塞的高优先级进程接触阻塞了(如它等待的IO完成了)
    	2.另外一个进程的优先级被提到了一个级别高于当前运行的进程的优先级
    	3.当前运行的进程的优先级被降低于其他可运行的进程的优先级了

    SCHED_RR 策略与标准的循环时间分享调度算法 SCHED_OTHER 类似,即它也允许优先级相同的一组进程分享 cpu 时间。它们之间最重要的区别在于 SCHED_RR 
  策略存在严格的优先级级别,高优先级的进程总是优于优先级较低的进程。而在 SCHED_OTHER 策略中,低 nice 值(即优先级高)的进程不会独占 cpu, 它仅仅在调度
  策略时为进程提供一个较大的权重。它们之间另外一个区别是,SCHED_RR 策略允许精确控制进程被调用的顺序。

5.SCHED_FIFO 策略
	SCHED_FIFO(先入先出)策略与 SCHED_RR 策略类似,它们之间最主要的区别在于 SCHED_FIFO 策略中不存在时间片。一旦一个 SCHED_FIFO 进程获得了 CPU的控制权
  之后,它就会一直执行下去,直到下面的条件被满足:
  	1.自动放弃cpu
  	2.终止了
  	3.被一个优先级更高的进程抢占了
  	在第一种情况中,进程会被设置为与其优先级对应的队列的队尾。在最后一种情况中,当高优先级进程执行结束之后(被阻塞或者终止了),被抢占的进程会继续执行(即被抢占的
 进程位于与其优先级对应的队列的队头)


6.SCHED_BATCH 和 SCHED_IDLE 策略
	SCHED_BATCH 它与默认的 SCHED_OTHER 类似,两个之间的区别在于 SCHED_BATCH 策略会导致被唤醒的任务被调度的次数较少,这种策略用于进程的批量式执行。
	SCHED_IDLE 它与默认的 SCHED_OTHER 类似,但提供的功能等价于一个非常低的 nice 值。在这个策略中,进程的 nice 值毫无意义。它用于运行低优先级的任务, 
  这些任务在系统中没有其他任务需要使用 cpu 时才会大量使用 cpu。

7.防止实时进程锁住系统
	由于 SCHED_RR 和 SCHED_FIFO 进程会抢占所有低优先级的进程,那么在开发使用这些策略的应用程序时需要小心可能会发生失控的实时进程因一直占住cpu而导致
  锁住系统的情况。可以使用如下方法避免:
  	1.使用 setrlimit()设置一个合理的低软 cpu 时间组员限制。如果进程消耗太多cpu时间,那么它将收到一个 SIGXCPU 信号,默认杀死进程。
  	2.使用 alarm() 设置一个警报定时器。如果进程的运行时间超过了 alarm() 调用设定的时间,那么进程会被信号 SIGALRM 信号杀死。
  	3.创建一个拥有高实时优先级的看门狗进程。这个进程可以进行无线循环,每次循环都睡眠指定时间,然后醒来监控其他进程的状态。这种状态可以包含对每个进程消耗
  的cpu时间的度量并使用 sched_getscheduler() 和 sched_getparam() 来检查进程的调度策略和优先级。如果一个进程看起来异常,那么看门狗程序可以降低该进程
  的优先级或者向其发送合适的信号来终止进程。
  	4.从2.6.25 开始,Linux 提供了一个非标准的资源限制 RLIMIT_RTIME 用于控制一个运行的实时调度策略下的进程在单次运行中能够消耗的cpu时间。

8.释放CPU
	实时进程可以通过两种方式自愿释放 cpu: 通过一个阻塞进程的系统调用(如 read()),或者调用 sched_yield();
	sched_yield() 的操作比较简单。如果存在于调用进程的优先级相同的其他排队的可运行进程,那么调用进程会被放在队列的队尾,队列中对头的进程将会被调用使用cpu.
  如果在该优先级队列中不存在可运行的进程,那么sched_yield() 不会做任何事情,调用进程会继续使用 cpu。


9.CPU 亲和力
	当一个进程在一个多处理系统上被重新调度时,无需在上一次执行的cpu上运行。之所以会在另外一个cpu运行的原因是原来的cpu在忙碌。
	
	进程切换cpu可能对性能有一定的影响 : 
		如果原来的cpu的高速缓冲器中存在进程的数据,那么为了将进程的一行数据加载到新cpu的高速缓冲器中,首先必须使这行数据失效。为了防止高速缓冲器不一致,
	多处理器架构在某个时刻只允许数据被存放在一个cpu的高速缓冲器中。这个使数据失效的过程会消耗时间。由于存在这个性能影响,Linux(2.6)尝试了给进程保证软
	CPU 亲和力 --- 在条件允许的情况下,进程重新被调度到原来的 CPU 上运行。

	sched_setaffinity();
	CPU_ZERO();
	CPU_SET();
	CPU_CLR();
	CPU_ISSET();
	sched_getaffinity();

getpriority();
setpriority();
mlock();
mlockall();
sched_get_priority_min(); //返回一个调度策略的优先级取值范围
sched_get_priority_max();
sched_setscheduler(); //修改进程的调度策略和优先级
sched_setparam(); //修改一个进程的调度策略,但不会修改其优先级
sched_getscheduler(); //获取进程的调度策略和优先级
sched_getparam();//获取优先级
sched_rr_get_interval(); //获取 SCHED_RR 进程在每次被授予使用 cpu 时,分配的时间片长度

猜你喜欢

转载自blog.csdn.net/enlyhua/article/details/82941824