第四章 进程调度
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了第四章 进程调度,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含22113字,纯文字阅读大概需要32分钟。
内容图文
![第四章 进程调度](/upload/InfoBanner/zyjiaocheng/935/c7dc7d620e2c422bad7d15651f62bc82.jpg)
2.1.策略: 策略决定调度程序在何时让什么进程运行。调度器的策略往往决定系统的整体印象,并且, 还要负责优化使用处理器时间。 2.1. I/O消耗型和处理器消耗型的进程: I/O消耗型指进程的大部分时间用来提交I/O请求或是等待I/O请求。这样的进程经常处于可运行状态,但通常都是运行短短的一会儿,因为它在等待更多的I/O请求时最后总会阻塞。 处理器耗费型进程把时间大多用在执行代码上。除非被抢占,否则它们通常都一直不 停地运行,因为它们没有太多的I/O需求。从系统响应速度考虑,调度器不应该经常让它们运行。对于这类处理器消耗型的进程,调度策略是尽量降低它们的运行频率。 进程可以同时展示这两种行为:比如,X Windows服务器既是I/O消耗型,也是处理器消耗型。还有些进程可能是I/O消耗型,但属于处理器消耗型活动的范围。 调度策略通常要在两个矛盾的目标中间寻找平衡:进程响应迅速(响应时间短)和最大系统利用率(高吞吐量)。因为交互式程序都是I/O消耗型的,所以调度程序向这种类型的进程倾斜会缩短系统响应时间。Linux为了保证交互式应用, 所以对进程的响应作了优化(缩短响应时间),更倾向于优先调度I/O消耗型进程。 2.2.进程优先级: 调度算法中最基本的一类就是基于优先级的调度。这是一种根据进程的价值和其对处理器时间的需求来对进程分级的想法。优先级高的进程先运行,低的后运行,相同优先级的进程按轮转方式进行调度(一个接一个,重复迸行)。在包栝Linux在内的某些系统中,优先级髙的进程使用的时间片也较长。调度程序总是选择时间片未用尽而且优先级最高的进程运行。 Linux根据以上思想实现了一种基于动态优先级的调度方法。一开始,该方法先设置基本的优先级,然而它允许调度程序根据需要来加、减优先级。举个例子,如果一个进程在I/O等待上耗费的时间多于其运行时间,那么该进程明显属于I/O消耗型进程。它的优先级会被动态提高。反之,如果是处理器消耗型就会降低优先级。 Linux内核提供了两组独立的优先级范围。第一种是nice值,范围从-20到+ 19,默认值是0。 nice的值越大优先级越低——你正在为系统中其他进程做好事(being nice)。 nice值小的进程(优先级高)在nice值大的进程(优先级低)之前执行。另外nice值也用来决定分配给进程的时间片的长短。nice值为-20的进程可能获得的时间片最长,nice值为19的进程获得的时间片可能最短。 第二个范围是实时优先级,其值是可配置的,默认情况下它的变化范围是从0到99。任何实时进程的优先级都高于普通的进程。Linux提供对POSIX实时优先级的支持。
2.3.时间片: 时间片是一个数值,它表明进程在被抢占前所能持续运行的时间。时间片过长会导致系统对交互的响应表现欠佳,时间片太短会明显增大进程切换带来的处理器耗时。 Linux调度程序提高交互式程序的优先级,让它们运行得更频繁。于是,调度程序提供较长的默认时间片(参看图4-1)给交互式程序。此外,Linux调度程序还能根据进程的优先级动态调整分配给它的时间片。从而保证了优先级高的进程,假定也是重要性高的进程,执行的频率高,执行时间长。通过实现这样一种动态调整优先级和时间片长度的机制,linux调度性能不但非常稳定而且也很强健。 进程并不是一定非要一次就用完它所有的时间片,进程可以通过重复调度,分五次每次20毫秒用完这些时间片。这样,即使是交互式程序也能从中获益——当它们没必要一次用这么多时间的时候,它们可以分几次使用,这样能保证它们尽可能长时间的处于可运行状态。 当一个进程的时间片耗尽时,就认为进程到期了。没有时间片的进程不会再投入运行,除非等到其他所有的进程都耗尽了它们的时间片(也就是说它们的剩余时间片为0)。在那个时候,所有进程的时间片会被重新计算。
2.4.进程抢占: 当一个进程进入TASK_RUNNING状态,内核会检查它的优先级是否高于当前正在执行的进程。如果是这样,调度程序会被唤醒,抢占当前正在运行的进程并运行新的可运行进程。此外,当一个进程的时间片变为0时,它会被抢占,调度程序被唤醒以选择一个新的进程。 2.5. 调度策略的活动: 理论上认为调度程序给文字编辑程序更高的优先级和更长的时间片,因为它是交互式的。这将保证文字编辑程序有充足的时间片可用。此外,由于拥有较高的优先级,所以文字编辑程序还能在需要的时候抢占视频编码程序——例如,用户敲键盘的瞬间。这样才能保证文字编辑程序对用户键盘输入的即时响应。这样做当然会影响视频编码程序,但由于文字编辑程序仅仅只在当用户按键时间断地运行,所以视频编码程序可以在所有剩余时间中独享处理器。这样的优化同时提高了这两种应用的性能。
3.linux调度算法: Linux的调度程序定义于kernel/sched.c中。2.5版内核的开发版后设计新的调度程序是为了实现下列目标: ?充分实现O(l)调度。不管有多少进程,新调度程序采用的每个算法都能在恒定时间内完成。 ?全面实现SMP的可扩展性。每个处理器拥有自己的锁和自己的可执行队列。 ?强化SMP的亲和力。尽量将相关一组任务分配给一个CPU进行连续的执行。只有在需要平衡任务队列大小的时才zaicpu之间移动进程。 ?加强交互性能。即使在系统处于相当负载的情况下,也能保证系统的响应,并立即调度交互式进程。 ?保证公平。在合理设定的时间范围内,没有进程会处于饥饿状态。同样的,也没有进程能够显失公平地得到大量时间片。 ?虽然最常见的优化情况是系统中只有1?2个可运行进程,但是优化也完全有能力扩展到具有多处理器且每个处理器上运行多个进程的系统中。 新的调度程序实现了上述目标。
3.1. 可执行队列: 调度程序中最基本的数据结构是运行队列(run_workqueue)。可执行队列是给定处理器上的可执行进程的链表,每个处理器一个。每个可投入运行的进程都惟一的归属于一个可执行队列。此外,可执行队列中还包含每个处理器的调度信息。 struct run_workqueue:
![第四章 进程调度 - 文章图片](/upload/getfiles/0001/2021/5/10/20210510085015859.jpg)
3.2.优先级数组: 每个运行队列都有两个优先级数组,一个活跃的和一个过期的。优先级数组在kernel/schedx.c中被定义,它是prio_array类型的结构体。优先级数组是一种能够提供0(1)级算法复杂度的数据结构。优先级数组使可运行处理器的每一种优先级都包含一个相应的队列,而这些队列包含对应优先级上的可执行进程链表。优先级数组还拥有一个优先级位图,当需要査找当前系统内拥有最高优先级的可执行进程时,它可以帮助提卨效率。
![第四章 进程调度 - 文章图片](/upload/getfiles/0001/2021/5/10/20210510085016419.jpg)
MAX_PRIO定义了系统拥有的优先级个数。默认值是140。这样,每个优先级都有一个struct listjiead结构体。B1TMAP_SIZE是优先级位图数组的大小,类型为unsigned long长整型,长32位, 如果每一位代表一个优先级的话,140个优先级需要5个长整型数才能表示。所以,bitmap就正好有5个数组项,总共160位.
每个优先级数组都要包含一个这样的位图成员,至少为每个优先级准备一位。一幵始,所有的位都被置为0。当某个拥有一定优先级的进程开始准备执行时(也就是状态变为TASK—RUNNING), 位图中相应的位就会被置为1。
每个优先级数组还包含一个叫做struct list_head的队列,其中每个元素都是一个struct list_head
类型的队列。每个链表与一个给定的优先级相对应,事实上,每个链表都包含该处理器队列上相应优先级的全部可运行进程。所以要找到下一个要运行的任务非常简单,就像在链表中选择下一元素一样。对于给定的优先级,按轮转方式调度任务。3.3.重新计算时间片:
Linux调度程序减少了对循环的依赖。取而代之的是它为每个处理器维护两个优先级数组: 既有活动数组也有过期数组。活动数组内的可执行队列上的进程都还有时间片剩余,而过期数组内的可执行队列上的进程都耗尽了时间片。当一个进程的时间片耗尽时,它会被移至过期数组, 但在此之前,时间片已经给它重新计算好了。重新计算时间片现在变得非常简单,只要在活动和过期数组之间来回切换就行了。因为数组是通过指针进行访问的,所以交换它们用的时间就是交换指针需要的时间。这个动作由schedule()完成。
3.4.schedule():
选定下一个进程并切换到它去执行是通过scheduleO函数实现的。当内核代码想要休眠时,会直接调用该函数,另外,如果有哪个进程将被抢占,那么该函数也会被唤起执行。schedule()函数独立于每个处理器运行。因此,每个CPU都要对下一次该运行哪个进程做出自己的判断。 下面的代码用来判断谁是优先级最高的进程:![第四章 进程调度 - 文章图片](/upload/getfiles/0001/2021/5/10/20210510085017025.jpg)
![第四章 进程调度 - 文章图片](/upload/getfiles/0001/2021/5/10/20210510085017617.jpg)
? 3.5.计算优先级和时间片: 进程拥有一个初始的优先级,叫做nice值。该数值变化范围为-20到+ 19,默认值为0。 19优 先级最低,-20最髙。进程task_struct的static_prio域就存放着这个值。 effective_prio()函数可以返回一个进程的动态优先级。这个函数以nice值为基数,再加上-5到+ 5之间的进程交互性的奖励或罚分。举例来说,一个交互性很强的进程,即使它的nice值为10,它的动态优先级最终也有可能达到5。相反,一个温和的处理器吞噬者,虽然本来nice值一样是10,它最后的动态优先级却可能是12。 通过一些推断来获取准确反映进程到底是I/O消耗型的还是处理器消耗型的。最明显的标准莫过于进程休眠的时间长短了。如果一个进程的大部分时间都在休眠,那么它就是I/O消耗型的。如果一个进程执行的时间比休眠的时间长,那它就是处理器消耗型的。 为了支持这种推断机制,Linux记录了一个进程用于休眠和用干执行的时间。该值存放在task_struct的sleep_avg域中。它的范围从0到MAX_SLEEP_AVG。它的默认值为10毫秒。当一个进程从休眠状态恢复到执行状态时,sleep_aVg会根据它休眠时间的长短而增长,直到达到MAX_SLEEP_AVG为止。相反,进程每运行一个时钟节拍,sleep_avg就做相应的递减,到0为止。 所以,尽管一个进程休眠了不少时间,但它如果总是把自己的时间片用得一干二净,那么它就不会得到大额的优先级奖励——这种推断机制不仅会奖励交互性强的进程,它还会惩罚处理器耗费量大的进程,并且不会滥用这些奖惩手段,这种衡量标准还有很快的反应速度。由于奖励和罚分都加在作为基数的nice值上,所以用户还是可以通过改变进程的nice值来对调度程序施加影响. 另一方面,重新计算时间片相对简单了。它只要以静态优先级为基础就可以了。在一个进程创建的时候,新建的子进程和父进程均分父进程剰余的进程时间片。然而,当一个任务的时间片用完之后,就要根据任务的静态优先级重新计算时间片。task_timeslice()函数为给定任务返回一个新的时间片。时间片的计算只需要把优先级按比例缩放,使其符合时间片的数值范围要求就可以了。
![第四章 进程调度 - 文章图片](/upload/getfiles/0001/2021/5/10/20210510085018182.jpg)
3.6.睡眠和唤醒: 休眠(被阻塞)的进程处于一个特殊的不可执行状态。进程休眠有各种原因,但肯定都是为了等待一些事件。事件可能是一段时间、从文件I/O读更多数据,或者是某个硬件事件。进程把它自己标记成休眠状态,把自己从可执行队列移出, 放入等待队列,然后调用scheduleO选择和执行一个其他进程。唤醒的过程刚好相反:进程被设置为可执行状态,然后再从等待队列中移到可执行队列。 休眠通过等待队列进行处理。等待队列是由等待某些事件发生的进程组成的简单链表。内核用wake_queue_head_t来代表等待队列。等待队列可以通过DECLARE_WAITQUEUE()静态创建, 也可以由init_waitqueue_head()动态创建。 内核中进行休眠的推荐操作就相对复杂了一些:
![第四章 进程调度 - 文章图片](/upload/getfiles/0001/2021/5/10/20210510085018729.jpg)
等待队列。
![第四章 进程调度 - 文章图片](/upload/getfiles/0001/2021/5/10/20210510085019239.jpg)
![第四章 进程调度 - 文章图片](/upload/getfiles/0001/2021/5/10/20210510085019831.jpg)
2) 其次,load_balance()从最繁忙的运行队列中选择一个优先级数组以便抽取进程。最好是过期数组,因为那里面的进程已经有相对较长的一段时间没有运行了,很可能不在处理器的髙速缓存中(换句话说,它们不是高速缓存命中(CaCheh0t))。如果过期数组为空,那就只能选活动数组。
3) 接着,load_balance()寻找到含有进程并且优先级最高(值最小)的链表,因为把优先级高的进程平均分散开来才是最重要的。
4) 分析找到的所有这些优先级相同的进程,选择一个不是正在执行,也不会因为处理器相关性而不可移动,并且不在高速缓存中的进程。如果有进程满足这些条件,调用pull_task()将其从最繁忙的队列中抽取到当前队列。 5) 只要可执行队列之间仍然不均衡,就重复上面两个步骤,继续从繁忙的队列中抽取进程到 当前队列。这最终会消除不平衡,此时,解除对当前运行队列进行的锁定,从load_balance()返回。4.抢占和上下文切换: 上下文切换,也就是从一个可执行进程切换到另一个可执行进程,由定义在kernel/sched.c中的context_switch()函数负责处理。每当一个新的进程被选出来准备投入运行的时候,schedule()就会调用泫函数。它完成了两项基本的工作: ?调用定义在<asm/mmu_context.h>中的switch_mm(),该函数负责把虚拟内存从上一个进程映射切换到新进程中。 ?调用定义在<asm/system.h>中的switch_to(),该函数负责从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复桟信息和寄存器信息。 内核提供了一个need_resched标志来表明是否需要重新执行一次调度(见表4-2)。当某个进程耗尽它的时间片时,scheduler_tick()就会设置这个标志,当一个优先级高的进程进入可执行状态的时候,try_to_wake_up()也会设置这个标志。
![第四章 进程调度 - 文章图片](/upload/getfiles/0001/2021/5/10/20210510085020243.jpg)
4.1.用户抢占: 内核即将返回用户空间的时候,如果need_resched标志被设置,会导致scheduleO被调用,此时就会发生用户抢占。内核无论是在从中断处理程序还是在系统调用后返回,都会查neecLresched标志。如果它被设置了,那么,内核会选择一个其他(更合适的)进程投入运行。从中断处理程序或系统调用返回的代码都是跟体系结构相关的,在entry.S (此文件不仅包含内核入口部分的程序,内核退出部分的相关代码也在其中)文件中通过汇编语言来实现。 简而言之,用户抢占在以下情况时产生: ?从系统调返回用户空间。 ?从中断处理程序返回用户空间。
4.2.内核抢占: 只要没有持有锁,内核就可以进行抢占。锁是非抢占区域的标志。由于内核是支持SMP的,所以,如果没有持有锁,那么正在执行的代码就是可重新导入的,也就是可以抢占的。 为了支持内核抢占所作的第一处变动就是为每个进程的thread_info引入了 preempt_count计数器。该计数器初始值为0,每当使用锁的时候数值加1,释放锁的时候数值减1。当数值为0的时候, 内核就可执行抢占。从中断返回内核空间的时候,内核会检査need—resched和preempt_count的值。如果need—resched被设置,并且preempt_count为0的话,这说明有一个更为重要的任务需要执行并且可以安全地抢占. 如果进程被阻塞了,或者它显示地调用schedule(),内核抢占也会显示发生。这种形式的内核抢占从来都是受支持的,因为根本无需额外的逻辑来保证内核可以安垒地被抢占。 内核抢占会发生在: ?当从中断处理程序正在执行,且返回内核空间之前。 ?当内核代码再一次具有可抢占性的时候。 ?如果内核中的任务显示的调用schedule(). ?如果内核中的任务阻塞(这同样也会导致调用schedule())
5.实时: Linux提供了两种实时调度策略:SCHED_FIFO和SCHED_RR。而普通的、非实时的调度策略是SCHED_NORMAL。SCHED_FIFO实现了一种简单的、先入先出的调度算法,它不使用时间片。SCHED_FIFO级的进程会比任何SCHED_NORMAL级的进程都先得到调度。一旦一个SCHED_FIFO级进程处于可执行状态,就会一直执行,直到它自己受阻塞或显式地释放处理器为止;它不基于时间片,可以一直执行下去。只有较高优先级的SCHED_FIFO或者SCHED_RR任务才能抢占SCHED_FIFO任务。如果有两个或者更多的SCHED_FIFO级进程,它们会轮流执行,但是在它们愿意让出处理机时会再次让出。只要有SCHED_FIFO级进程在执行,其他级别较低的进程就只能等待它结束后才有机会执行。 SCHED_RR与SCHED—FIFO大体相同,只是SCHED_RR级的进程在耗尽事先分配给它的时间后就不能再接着执行了.时间片只用来重新调度同一优先级的进程。对于SCHED_FIFO进程,髙优先级总是立即抢占低优先级,但低优先级进程决不能抢占SCHED_RR任务,即使它的时间片耗尽。 这两种实时算法实现的都是静态优先级。内核不为实时进程计算动态优先级。 Linux的实时调度算法提供了一种软实时工作方式。软实时的含义是,内核调度进程,尽力使进程在它的限定时间到来前运行,但内核不保证总能满足这些进程的要求。 默认的实时优先级范围是从0到99。SCHED_NORMAL级进程的nice值共享了这个取值空间,它的取值范围是从MAX_RT_PRIO到(MAX_RT_PRIO + 40)。也就是说,在默认情况下,nice值从-20到+19 直接对应的是从100到139的实时优先级范围。(实时进程:0--99; 一般进程:100--139).
6.与调度相关的系统调用: Linux提供了一族系统调用,用干管理与调度程序相关的参数。这些系统调用可以用来操作和处理进程优先级、调度策略及处理器,同时还提供了显式的将处理器交给其他进程的机制。
![第四章 进程调度 - 文章图片](/upload/getfiles/0001/2021/5/10/20210510085020800.jpg)
![第四章 进程调度 - 文章图片](/upload/getfiles/0001/2021/5/10/20210510085021266.jpg)
? 5.1.与调度策略和优先级相关的系统调用: sched_setscheduler()和schecLgetscheduler()分别用于设置和获取进程的调度策略和实时优先级。与其他的系统调用相似,它们的实现也是由许多参数检查、初始化和清理构成的。其实最重要的工作在于读取或改写进程task_struct的policy和rt_priority的值。
sched_setparam()和sched_getparam()分别用于设置和获取进程的实时优先级。这两个系统调用获取封装在sched_param特殊结构体中的rt_priority。 sched_get_priority_max ()和sched_get_priority_min ()分别用于返回给定调度策略的最大和最小优先级。实时调度策略的最大优先级是MAX_USER_RT_PRIO减1,最小优先级等于1。
对于一个普通的进程,nice()函数可以将给定进程的静态优先级增加一个给定的量。只有超级 用户才能在凋用它时使用负值,从而提髙进程的优先级。nice()函数会调用内核的set_user_nice()5.3.放弃处理器时间: Linux通过sched_yield()系统调用,提供了一种让进程显式地将处理器时间让给其他等待执行进程的机制。它是通过将进程从活动队列中移到过期队列中实现的。由此产生的效果不仅抢占了该进程并将其放入优先级队列的最后面,还将其放入过期队列中——这样能确保在一段时间内它都不会再被执行了。由于实时进程不会过期, 所以属于例外。 内核代码为了方便,可以直接调用yieid(),它先要确定给定进程确实处于可执行状态,然后再调用sched_yield()。用户空间的应用程序直接使用sched_yield()系统调用就可以了。
内容总结
以上是互联网集市为您收集整理的第四章 进程调度全部内容,希望文章能够帮你解决第四章 进程调度所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。