精华内容
下载资源
问答
  • 依赖的是中断机制,通过中断抢回CPU执行权限然后进行调度,如Linux内核对线程调度。 协作式调度。需要主动让出CPU,调用调度代码进行调度,如协程,没有中断机制一般无法真正做到抢占。 Linux NPTL 线程库 看...

    调度本质上体现了对CPU资源的抢占。调度的方式可以分为:

    1. 抢占式调度。依赖的是中断机制,通过中断抢回CPU执行权限然后进行调度,如Linux内核对线程的调度。
    2. 协作式调度。需要主动让出CPU,调用调度代码进行调度,如协程,没有中断机制一般无法真正做到抢占。

    Linux NPTL 线程库

    看操作系统方面的文章时,要注意区分其描述的是通用操作系统还是某种特定的操作系统(如: Windows/Linux/macOS),如果是某种具体的操作系统的实现,还要看其基于哪个版本(如Linux2.6前后的线程模型就有变化),因为操作系统是在不断完善和演进的。

    NPTL(Native POSIX Thread Library)是Linux 2.6引入的新线程库,底层调用的内核优化过的clone系统调用。NPTL最初由Red hat开发,替代的是Linux 2.6版本以前的LinuxThreads,更加符合POSIX标准,可以更好的利用多核并行(parallel)运行。这里注意并发(parallel)和并行(concurrency)的区别: 并发可能是对CPU单核的分时复用,并行是真正的CPU多核同时执行。

    NPTL线程库的一个设计理念就是用户级线程和内核级线程是1:1的关系,调度完全依赖内核,这样可以充分利用多核。NTPL最初被提出时的一篇文章这样阐述了这个问题,(当然这篇文章时间较早,不能好很好的描述最新实现了,文章开头也有提到),不过设计的思路还是可以学习思考的,后续的具体实现还要看跟进最新的代码与文档。

    The most basic design decision which has to be made is what relationship there should be between the kernel threads and the user-level threads. It need not be mentioned that kernel threads are used; a pure user-level implementation could not take advantage of multi-processor machines which was one of the goals listed previously. One valid possibility is the 1-on-1 model of the old implementation where each user-level thread has an underlying kernel thread. The whole thread library could be a relatively thin layer on top of the kernel functions.

    需要注意的是,上面引文中说的用户级线程指的是完全在用户态调度管理的线程,内核级线程则指的是有task_struct与之对应并由内核进行统一管理调度的线程。而现在,用户线程这个术语一般指拥有用户空间的的用户态程序,内核线程则是指完全运行在内核态的常驻系统的线程(如1号init线程)。

    Linux 线程调度时机

    对于Linux调度,简单来说就是在内核态执行schedule函数,按照一定策略选出这个CPU核接下来要执行的线程,上下文切换到对应线程执行。

    对于用户线程调度,首先要切换到内核态,用户栈切到内核栈,在内核态调用schedule函数,选出下一个要被执行的线程,上下文切换,执行。用户线程的调度时机有:

    1. 线程运行结束或睡眠,主动进行调度,如:程序运行中调sleep、结束时调用的exit,最终都会调到schedule,进行调度;
    2. 调用阻塞的系统调用,陷入内核后会进行调度,如各种阻塞的IO调用;
    3. 从系统调用、中断处理返回用户空间的前夕,根据task_structneed_resched判断是否进行调度,如:从时钟中断返回发现时间片用完,进行调度。

    对于内核线程调度,这里的内核线程指运行在内核态的线程。Linux 2.6开始支持内核抢占,如果没有加锁,内核就可以进行抢占。内核线程的调度时机有:

    1. 内核线程被阻塞,显示调用schedule进行调度;
    2. 从中断返回内核空间前夕,发现内核线程无锁,根据对应need_resched自断判断是否进行调度;
    3. 内核代码再一次具有可抢占性的时候;

    Linux 线程调度上下文切换

    Linux线程调度的上下文切换,主要由函数context_switch完成,主要完成相关寄存器和栈的切换,如果涉及到了进程(进程是资源管理的单位)切换,还会切换页目录进而切换进程地址空间。下面是选自MIT xv6的一幅图:
    这里写图片描述

    Goroutine 调度模型

    Go语言天然支持并发靠的就是轻量级的goroutine,goroutine算是一种协程,从Go 1.4后默认goroutine栈初始大小为2k,由Go的runtime完全在用户态调度,goroutine切换也不用陷入内核,相对OS线程调度开销很小。但是,物理CPU核只能由内核来调度,内核只能调度由task_struct结构管理的OS线程,这样就涉及到了一个goroutine和OS线程的关联关系,Go 1.1后采用的就是著名的G-P-M模型。Go runtime中有关调度的代码很多都是用汇编语言编写,内核也是如此,看调度的源码细节就需要对栈帧结构、调用约定、内存模型等有一定深度的了解。

    G-P-M模型

    G是goroutine,P是抽象的逻辑processor,M是操作系统线程Machine。P作为go runtime抽象的处理器,其个数肯定要小于等于物理CPU核数的。具体的描述可以看The Go scheduler这篇文章,阐述的比较深入。总体来说,goroutine是用户态的轻量级的线程,通过Go的runtime来调度获取P,P最终会关联到一个系统线程M上,从而使得这个P下面的goroutine获得执行。也谈goroutine调度器这篇文章中的图很形象:
    这里写图片描述

    Goroutine 调度时机

    与Linux线程调度相比,Goroutine调度不支持抢占。抢占式调度依赖的是中断机制。不过在Go 1.2后,如果goroutine涉及了函数调用,那么就可以做到一定程度的“抢占”。原理也比较容易理解,如下:

    这个抢占式调度的原理则是在每个函数或方法的入口,加上一段额外的代码,让runtime有机会检查是否需要执行抢占度。这种解决方案只能说局部解决了“饿死”问题,对于没有函数调用,纯算法循环计算的G,scheduler依然无法抢占。

    实测也是如此,对于无函数调用的死循环goroutine,如果其个数等于当前CPU核数,就会导致所有其他goroutine得不到调度。因为再也没有时机能够运行到go的调度代码了。而对于线程的死循环,会有时钟中断来抢占CPU,从中断返回时会进行调度完成抢占。

    总结

    Linux2.6后完全支持了内核抢占,并引入了NPTL线程库。Go 1.2后也在一定程度上支持了goroutine的“抢占式”调度。调度的原理细节可以看文末的参考文献。

    参考

    展开全文
  • Linux线程调度机制

    千次阅读 2019-01-10 21:32:49
    Linux线程的调度机制  在Linux中,线程是由进程来实现,线程就是轻量级进程( lightweight process ),因此在Linux中,线程的调度是按照进程的调度方式来进行调度的。Linux这样实现的线程的好处的之一是:线程调度...

    Linux线程的调度机制


      在Linux中,线程是由进程来实现,线程就是轻量级进程( lightweight process ),因此在Linux中,线程的调度是按照进程的调度方式来进行调度的。Linux这样实现的线程的好处的之一是:线程调度直接使用进程调度就可以了,没必要再搞一个进程内的线程调度器。
      
      在Linux中,调度器是基于线程的调度策略(scheduling policy)和静态调度优先级(static scheduling priority)来决定那个线程来运行。
      
      Linux系统的三种调度策略:
      一、SCHED_OTHER:分时调度策略(Linux线程默认的调度策略)。
      二、SCHED_FIFO:实时调度策略,先到先服务。该策略简单的说就是一旦线程占用CPU则一直运行,一直运行直到有更高优先级任务到达或自己放弃。
      三、SCHED_RR:实时调度策略,时间片轮转。给每个线程增加了一个时间片限制,当时间片用完后,系统将把该线程置于队列末尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。
      
       调度优先级策略:
       实时调度策略大于分时调度策略;
      当实时进程/线程准备就绪后,如果当前CPU正在运行分时进程/线程,则实时进程/线程立即抢占分时进程/线程。
       同样都是实时调度策略,优先级高的先执行。
      
      所有任务都采用Linux分时调度策略时:
      1. 创建任务指定采用分时调度策略,并指定优先级nice值(-20~19)。
      2. 将根据每个任务的nice值确定在CPU上的执行时间(counter)。
      3. 如果没有等待资源,则将该任务加入到就绪队列中。
      4. 调度程序遍历就绪队列中的任务,通过对每个任务动态优先级的计算(counter+20-nice)结果,选择计算结果最大的一个去运行,当这个时间片用完后(counter减至0)或者主动放弃CPU时,该任务将被放在就绪队列末尾(时间片用完)或等待队列(因等待资源而放弃CPU)中。
      5. 此时调度程序重复上面计算过程,转到第4步。
      6. 当调度程序发现所有就绪任务计算所得的权值都为不大于0时,重复第2步。
      
      所有任务都采用FIFO时:
      1. 创建进程时指定采用FIFO,并设置实时优先级rt_priority(1-99)。
      2. 如果没有等待资源,则将该任务加入到就绪队列中。
      3. 调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务使用cpu,该FIFO任务将一直占有CPU直到有优先级更高的任务就绪(即使优先级相同也不行)或者主动放弃(等待资源)。
      4. 调度程序发现有优先级更高的任务到达(高优先级任务可能被中断或定时器任务唤醒,再或被当前运行的任务唤醒,等等),则调度程序立即在当前任务堆栈中保存当前CPU寄存器的所有数据,重新从高优先级任务的堆栈中加载寄存器数据到CPU,此时高优先级的任务开始运行。重复第3步。
      5. 如果当前任务因等待资源而主动放弃CPU使用权,则该任务将从就绪队列中删除,加入等待队列,此时重复第3步。
      
      所有任务都采用RR调度策略时:
      1. 创建任务时指定调度参数为RR,并设置任务的实时优先级和nice值(nice值将会转换为该任务的时间片的长度)。
      2. 如果没有等待资源,则将该任务加入到就绪队列中。
      3. 调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务使用CPU。
      4. 如果就绪队列中的RR任务时间片为0,则会根据nice值设置该任务的时间片,同时将该任务放入就绪队列的末尾。重复步骤3。
      5. 当前任务由于等待资源而主动退出CPU,则其加入等待队列中。重复步骤3。

    展开全文
  • Linux系统调度原理浅析

    千次阅读 2019-01-02 09:14:20
    专注于Java领域优质技术号,欢迎关注来自独家号 Kubernetes入门实践 ...本想简单地写一写 Golang 的调度原理,不过看了几篇文章后发现,有必要在写 Go 调度器前先整理一下 Linux调度原理,复习一下进程、线程、...
    专注于Java领域优质技术号,欢迎关注

    来自独家号 Kubernetes入门实践 Jing维

    写在前面

    元旦假期已经开始了,意味着 2018 年终是要溜走了。假期第一天,气温突然降下来,深圳的冬天终究证明了自己的尊严,而我则瑟瑟地躲在熟悉的角落里继续码字。

    本想简单地写一写 Golang 的调度原理,不过看了几篇文章后发现,有必要在写 Go 调度器前先整理一下 Linux 的调度原理,复习一下进程、线程、时间片、内核调度这些概念,然后再去探究 Go 调度器就不会那么难了。

    现在就让我们把大学课堂上学习的知识点捡回来,复习一下进程、线程、多线程模型、时间片以及调度等概念。

    适用人群

    入门——初级√——中级——高级;本文适应初级及以上。

    Linux系统调度原理浅析

    Linux系统调度原理

    单核CPU+内存 vs 四方小木桌

    记得在我很小的时候,家里买了一个四方小木桌,一家人吃饭的时候会把它用来做餐桌,我和老爸下象棋的时候会把它用来做象棋桌,除夕夜一家人打牌赌压岁钱的时候则会把它用来做牌桌。四方小木桌是一种通用的工具,我们可以在上面完成许许多多的事情,就像服务器上的 CPU 与内存,我们可以在上面运行许多进程。

    Linux系统调度原理浅析

    (在 CPU+内存 上可以运行很多进程,就像在小木桌上可以做很多事情)

    进程 vs 吃饭下棋

    教科书里对进程的描述比较学院风,很多网络上的文章也把进程描述得很抽象,这导致让一些非计算机专业的同学理解起来并不是那么容易。如果接上一小节的比喻,把服务器的 CPU(假设只有一个核)和内存等资源比喻为四方小木桌,其实我们完全可以把进程理解为在这个小木桌上完成的事项——比如吃饭,下象棋,打牌,等等

    能够在小木桌上做的这些事情,无论是吃饭、下象棋或者打牌,都需要花费一定的时间做准备。比如,为了在小木桌上吃饭我们需要先把碗、筷、盘子及饭菜等放到桌面上,为了在小木桌上下象棋我们需要先把棋盘、棋子摆放在桌面上,为了在小木桌上打牌我们需要先把扑克放在桌面上。当然,所有事情结束后还需要清理一下桌面,把各类东西归位(比如饭后把碗筷和盘子收拾掉,下完象棋把象棋收好,打完牌把扑克收好)。

    类似的,当跑一个进程的时候也有类似的步骤:需要1)首先把进程需要的相关资源加载到内存里,2)然后才能让进程真正地运行起来。在小木桌上吃完饭再准备下象棋,需要先收拾碗筷再摆放棋盘棋子,这个过程比较繁琐;进程之间的切换类似的道理,相对来说代价也比较大。

    线程 vs 和家人谈谈心

    如果到了饭点的时候,大家一声不吭地围着小木桌吃饭,会显得气氛非常尴尬;如果下象棋的时候双方一声不吭地落棋,也会显得气氛局促;同时作为一家人,有必要坐在一起谈谈心沟通一下感情。于是,我们可以借吃饭或者下象棋的时机和家人谈谈心,聊一聊妈妈关心的柴米油盐(比如番茄炒蛋这道菜的味道怎么样),聊一聊爸爸关心的工作事业,聊一聊小朋友的烦恼疑惑,等等。

    如果把吃饭这个进程里的 “吃” 这个动作看成一个线程,那么饭桌上 “谈心” 这个动作就可以看成是另一个线程。同样的道理,如果把下象棋这个进程里的 “落棋” 这个动作看成一个线程,那么棋桌上 “谈心” 这个动作也可以看成是另一个线程。我们当然可以在吃完饭或者下完棋并把小木桌收拾干净以后,然后大家再坐到一起谈心,不过那样的话切换时间成本太高了

    线程是进程里的一个具体的动作,一个进程里可以只包含一个线程,也可以包含很多的线程,且属于同一个进程的所有线程共享当前进程的所有资源。当操作系统的内核只有进程的抽象概念时,完成一个过程需要两步,1)把过程需要的相关资源加载到内存,2)然后执行过程。后来有了线程的抽象概念,完成一个过程就有机会利用已经加载好的资源,直接完成某个过程从而节省了资源加载到内存的时间(比如饭菜准备好已经开始吃饭了,再顺便谈谈心,不需要准备两次饭菜,也不需要收拾小木桌后再专门坐到一起聊天)。

    时间片

    如果把时间线拉的足够久,比如以年为单位来看四方小木桌,我们可以认为小木桌既能用来吃饭,也能用来下象棋。现在假设一种极端情况,比如家里有一个嗜好下象棋的爷爷,一年四季都把棋盘摆放在小木桌上面,小木桌成为下象棋专用,导致家里人没有吃饭和打牌的地方了,这肯定不妥。于是爸爸不得不和爷爷商量,以 6 个小时为时间段交替使用小木桌,比如某 6 个小时里连续使用小木桌来专门下棋,接下来的 6 个小时里就要用小木桌来专门吃饭或者打牌了,如此轮流进行。爷爷比较通情达理,觉得公平合理,于是答应了。

    上面的 6 个小时期限就是一种时间片的划分。对于 CPU 来说,可以把 50ms(毫秒)作为一个时间片划分使用权,在每个时间片里运行不同的线程。因为 50ms 是一个相对人类生理时间来说非常短的值,所以让我们误觉得一颗 CPU 核可以同时做很多事情。

    更合理的时间片划分

    把吃饭、下象棋、打牌都分配成 6 个小时的时间片,而且每件事交替轮流进行,其实是一种非常粗暴的做法。比如吃饭这件事重要非常,如果因为爸爸上班赶时间应该允许吃饭这件事提前打断下象棋这件事(让爷爷先把棋谱记下来,等吃完饭再接着玩);而一家人打牌的机会比较少,没有必要一直分配那么多时间,造成了小木桌资源的极大浪费。那么应该怎么分配时间才更合理呢——这就需要认真规划一下了。

    相同的道理,对于内核而言,也不能一刀切地把时间片统一切分成为 50ms,为了保证 CPU 的使用效率最大化,同时也能保证让使用电脑的人满意,完全可以给不同的线程分配不同的时间片时长。之所以这么讲,可以考虑这两种极端情况:内核把时间片全都给了计算密集型的进程(比如全都给了视频转码相关的线程),这种情况下 CPU 的使用效率是最大的;但是这个时候如果我正在写博客,我会发现敲键盘的响应速度异常慢,就没有办法继续码字了。为了照顾键盘和鼠标的响应速度而把计算密集型进程的时间片定义的非常小(比如 5ms),这个时候进程切换(上下文切换)的边界成本就变得非常高,假设上下文切换需要 5ms,时间片也为 5ms,CPU 的有效运行效率就只剩 50% = 5/(5+5) 了。

    因此时间片的划分是一个很讲究的事情。

    内核调度是一个非常复杂的过程

    我花了一些时间尝试理清楚内核调度的细节,不过这个过程非常的复杂,不是一时半会儿就能理清楚的;但是我们可以先意识到这个问题,然后慢慢琢磨里面的原理。

    一小节提到,为了提升 CPU 的运行效率,同时增加对用户的响应速度,时间片的划分要非常考究。

    给所有的进程分配了时间片以后,CPU 依然需要到某个地方把进程取出来依次执行,那么取出的顺序应该怎么定义才能保证 CPU 利用效率的同时保证响应速度呢?

    许我们觉得可以通过中断抢占的方式来保证响应速度,以按下键盘的某个键为例,触发相应的中断后让 CPU 优先处理监听此中断的进程, 处理完相应的中断后再返回之前被中断的进程继续执行;不过这里面需要注意中断的时机,是不是要等到计算密集型的进程把 50ms 时间片用完以后再触发中断,否则(把 50ms 时间片进行切分)无形中又增加了上下文切换的成本。

    然我敲击键盘的速度已经够快了,不过对 CPU 而言,敲击键盘的间隙也会显得非常非常久;为了保证用户体验而给我码字所在的进程分配足够长的时间片,但是时间片用不完怎么办,不能让 CPU 一直等我吧!这个时候必然需要引入某种状态来进一步细化内核调度的算法,优化上下文切换的时机。

    ⑤当然,还有很多种情况需要考虑,希望以后有时间能深挖一下 Linux 的内核源码探究一下这部分的内容,这里就不深究了。

    多线程模型

    Linux系统调度原理浅析

    (用户级和内核级线程)

    这部分的内容我认为博客已经讲的比较透彻了,因此只把相关部分摘录一下

    线程的实现可以分为两类:用户级线程(User-LevelThread, ULT)和内核级线程(Kemel-LevelThread, KLT)。内核级线程又称为内核支持的线程。

    • 在用户级线程中,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。应用程序可以通过使用线程库设计成多线程程序。通常,应用程序从单线程起始,在该线程中开始运行,在其运行的任何时刻,可以通过调用线程库中的派生例程创建一个在相同进程中运行的新线程。上图(a) 说明了用户级线程的实现方式。这种将多个用户级线程映射到一个内核级线程,线程管理在用户空间完成的方式,是线程模型中的多对一模型(n:1);因为此种模型的线程管理是在用户空间进行的,因此效率比较高(毕竟管理上更为灵活),但是当内核空间的进程被阻塞的时候,所有用户空间的线程都会被阻塞。
    • 在内核级线程中,线程管理的所有工作由内核完成,应用程序没有进行线程管理的代码,只有一个到内核级线程的编程接口。内核为进程及其内部的每个线程维护上下文信息,调度也是在内核基于线程架构的基础上完成。上图(b)说明了内核级线程的实现方式。这种将每个用户级线程映射到一个内核级线程的方式,是线程模型中的一对一模型(1:1);这种模型可以充分利用内核线程的并发优势,内核空间的某个线程被阻塞,可以启用另一个线程继续执行,不好的地方是创建内核级线程的开销比较大,会影响到应用程序的性能。
    • 在一些系统中,使用组合方式的多线程实现。线程创建完全在用户空间中完成,线程的调度和同步也在应用程序中进行。一个应用程序中的多个用户级线程被映射到一些(小于或等于用户级线程的数目)内核级线程上。上图(c)说明了用户级与内核级的组合实现方式。这种将 n 个用户级线程映射到 m 个内核级线程上(m <= n)的方式,是线程模型中的多对多模型(n:m)。这种模型集合了上面两种模型的优点于一身,不过这种模型所对应的调度系统会变得非常复杂(目前 Go 语言采用的就是这种模型,所以说 Go 的调度器实现很复杂)。

    小结

    本文简单地复习了计算机基本原理中的几个概念,涉及到进程与线程、多线程模型、时间片、以及调度等内容。这些内容基础而重要,不过如果非计算机专业科班出身(或者大学里没有好好听课)很容易成为架构、编码或运维的盲点,建议读者能够静下心来自己再去了解一下这部分的内容,或者读一读相关的书籍。

    展开全文
  • Linux 进程调度原理

    千次阅读 2014-01-24 14:59:23
    Linux 进程调度原理 进程调度依据  调度程序运行时,要在所有可运行状态的进程中选择最值得运行的进程投入运行。选择进程的依据是什么呢?在每个进程的task_struct结构中有以下四项:policy、priority、counter、...

    Linux 进程调度原理

    进程调度依据

      调度程序运行时,要在所有可运行状态的进程中选择最值得运行的进程投入运行。选择进程的依据是什么呢?在每个进程的task_struct结构中有以下四项:policy、priority、counter、rt_priority。这四项是选择进程的依据。其中,policy是进程的调度策略,用来区分实时进程和普通进程,实时进程优先于普通进程运行;priority是进程(包括实时和普通)的静态优先级;counter是进程剩余的时间片,它的起始值就是priority的值;由于counter在后面计算一个处于可运行状态的进程值得运行的程度goodness时起重要作用,因此,counter也可以看作是进程的动态优先级。rt_priority是实时进程特有的,用于实时进程间的选择。  Linux用函数goodness()来衡量一个处于可运行状态的进程值得运行的程度。该函数综合了以上提到的四项,还结合了一些其他的因素,给每个处于可运行状态的进程赋予一个权值(weight),调度程序以这个权值作为选择进程的唯一依据。关于goodness()的情况在后面将会详细分析。

    linux内核的三种调度方法

      1,SCHED_OTHER 分时调度策略,  2,SCHED_FIFO实时调度策略,先到先服务  3,SCHED_RR实时调度策略,时间片轮转  实时进程将得到优先调用,实时进程根据实时优先级决定调度权值,分时进程则通过nice和counter值决定权值,nice越小,counter越大,被调度的概率越大,也就是曾经使用了cpu最少的进程将会得到优先调度。  SHCED_RR和SCHED_FIFO的不同:  当采用SHCED_RR策略的进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。  SCHED_FIFO一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃。  如果有相同优先级的实时进程(根据优先级计算的调度权值是一样的)已经准备好,FIFO时必须等待该进程主动放弃后才可以运行这个优先级相同的任务。而RR可以让每个任务都执行一段时间。  

    相同点:  RR和FIFO都只用于实时任务。  创建时优先级大于0(1-99)。  按照可抢占优先级调度算法进行。  就绪态的实时任务立即抢占非实时任务。  

    所有任务都采用linux分时调度策略时。  1,创建任务指定采用分时调度策略,并指定优先级nice值(-20~19)。  2,将根据每个任务的nice值确定在cpu上的执行时间(counter)。  3,如果没有等待资源,则将该任务加入到就绪队列中。  4,调度程序遍历就绪队列中的任务,通过对每个任务动态优先级的计算(counter+20-nice)结果,选择计算结果最大的一个去运行,当这个时间片用完后(counter减至0)或者主动放弃cpu时,该任务将被放在就绪队列末尾(时间片用完)或等待队列(因等待资源而放弃cpu)中。  5,此时调度程序重复上面计算过程,转到第4步。  6,当调度程序发现所有就绪任务计算所得的权值都为不大于0时,重复第2步。  

    所有任务都采用FIFO时,  1,创建进程时指定采用FIFO,并设置实时优先级rt_priority(1-99)。  2,如果没有等待资源,则将该任务加入到就绪队列中。  3,调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务使用cpu,该FIFO任务将一直占有cpu直到有优先级更高的任务就绪(即使优先级相同也不行)或者主动放弃(等待资源)。  4,调度程序发现有优先级更高的任务到达(高优先级任务可能被中断或定时器任务唤醒,再或被当前运行的任务唤醒,等等),则调度程序立即在当前任务堆栈中保存当前cpu寄存器的所有数据,重新从高优先级任务的堆栈中加载寄存器数据到cpu,此时高优先级的任务开始运行。重复第3步。  5,如果当前任务因等待资源而主动放弃cpu使用权,则该任务将从就绪队列中删除,加入等待队列,此时重复第3步。  

    所有任务都采用RR调度策略时  1,创建任务时指定调度参数为RR,并设置任务的实时优先级和nice值(nice值将会转换为该任务的时间片的长度)。  2,如果没有等待资源,则将该任务加入到就绪队列中。  3,调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务使用cpu。  4,如果就绪队列中的RR任务时间片为0,则会根据nice值设置该任务的时间片,同时将该任务放入就绪队列的末尾。重复步骤3。  5,当前任务由于等待资源而主动退出cpu,则其加入等待队列中。重复步骤3。  

    系统中既有分时调度,又有时间片轮转调度和先进先出调度  1,RR调度和FIFO调度的进程属于实时进程,以分时调度的进程是非实时进程。  2,当实时进程准备就绪后,如果当前cpu正在运行非实时进程,则实时进程立即抢占非实时进程。  3,RR进程和FIFO进程都采用实时优先级做为调度的权值标准,RR是FIFO的一个延伸。FIFO时,如果两个进程的优先级一样,则这两个优先级一样的进程具体执行哪一个是由其在队列中的未知决定的,这样导致一些不公正性(优先级是一样的,为什么要让你一直运行?),如果将两个优先级一样的任务的调度策略都设为RR,则保证了这两个任务可以循环执行,保证了公平。

    进程调度策略

      调度程序运行时,要在所有处于可运行状态的进程之中选择最值得运行的进程投入运行。选择进程的依据是什么呢?在每个进程的task_struct 结构中有这么四项:  policy, priority , counter, rt_priority  这四项就是调度程序选择进程的依据.其中,policy是进程的调度策略,用来区分两种进程-实时和普通;priority是进程(实时和普通)的优先级;counter 是进程剩余的时间片,它的大小完全由priority决定;rt_priority是实时优先级,这是实时进程所特有的,用于实时进程间的选择。  首先,Linux 根据policy从整体上区分实时进程和普通进程,因为实时进程和普通进程度调度是不同的,它们两者之间,实时进程应该先于普通进程而运行,然后,对于同一类型的不同进程,采用不同的标准来选择进程:  对于普通进程,Linux采用动态优先调度,选择进程的依据就是进程counter的大小。进程创建时,优先级priority被赋一个初值,一般为0~70之间的数字,这个数字同时也是计数器counter的初值,就是说进程创建时两者是相等的。字面上看,priority是“优先级”、counter是“计数器”的意思,然而实际上,它们表达的是同一个意思-进程的“时间片”。Priority代表分配给该进程的时间片,counter表示该进程剩余的时间片。在进程运行过程中,counter不断减少,而priority保持不变,以便在counter变为0的时候(该进程用完了所分配的时间片)对counter重新赋值。当一个普通进程的时间片用完以后,并不马上用priority对counter进行赋值,只有所有处于可运行状态的普通进程的时间片(p->;;counter==0)都用完了以后,才用priority对counter重新赋值,这个普通进程才有了再次被调度的机会。这说明,普通进程运行过程中,counter的减小给了其它进程得以运行的机会,直至counter减为0时才完全放弃对CPU的使用,这就相对于优先级在动态变化,所以称之为动态优先调度。至于时间片这个概念,和其他不同操作系统一样的,Linux的时间单位也是“时钟滴答”,只是不同操作系统对一个时钟滴答的定义不同而已(Linux为10ms)。进程的时间片就是指多少个时钟滴答,比如,若priority为20,则分配给该进程的时间片就为20个时钟滴答,也就是20*10ms=200ms。Linux中某个进程的调度策略(policy)、优先级(priority)等可以作为参数由用户自己决定,具有相当的灵活性。内核创建新进程时分配给进程的时间片缺省为200ms(更准确的,应为210ms),用户可以通过系统调用改变它。  对于实时进程,Linux采用了两种调度策略,即FIFO(先来先服务调度)和RR(时间片轮转调度)。因为实时进程具有一定程度的紧迫性,所以衡量一个实时进程是否应该运行,Linux采用了一个比较固定的标准。实时进程的counter只是用来表示该进程的剩余时间片,并不作为衡量它是否值得运行的标准。实时进程的counter只是用来表示该进程的剩余时间片,并不作为衡量它是否值得运行的标准,这和普通进程是有区别的。上面已经看到,每个进程有两个优先级,实时优先级就是用来衡量实时进程是否值得运行的。  这一切看来比较麻烦,但实际上Linux中的实现相当简单。Linux用函数goodness()来衡量一个处于可运行状态的进程值得运行的程度。该函数综合了上面提到的各个方面,给每个处于可运行状态的进程赋予一个权值(weight),调度程序以这个权值作为选择进程的唯一依据。  Linux根据policy的值将进程总体上分为实时进程和普通进程,提供了三种调度算法:一种传统的Unix调度程序和两个由POSIX.1b(原名为POSIX.4)操作系统标准所规定的“实时”调度程序。但这种实时只是软实时,不满足诸如中断等待时间等硬实时要求,只是保证了当实时进程需要时一定只把CPU分配给实时进程。  非实时进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时进程又增加了第三种优先级,实时优先级。优先级是一些简单的整数,为了决定应该允许哪一个进程使用CPU的资源,用优先级代表相对权值-优先级越高,它得到CPU时间的机会也就越大。  ? 静态优先级(priority)-不随时间而改变,只能由用户进行修改。它指明了在被迫和其他进程竞争CPU之前,该进程所应该被允许的时间片的最大值(但很可能的,在该时间片耗尽之前,进程就被迫交出了CPU)。  ? 动态优先级(counter)-只要进程拥有CPU,它就随着时间不断减小;当它小于0时,标记进程重新调度。它指明了在这个时间片中所剩余的时间量。  ? 实时优先级(rt_priority)-指明这个进程自动把CPU交给哪一个其他进程;较高权值的进程总是优先于较低权值的进程。如果一个进程不是实时进程,其优先级就是0,所以实时进程总是优先于非实时进程的(但实际上,实时进程也会主动放弃CPU)。  

    当policy分别为以下值时:  1) SCHED_OTHER:这是普通的用户进程,进程的缺省类型,采用动态优先调度策略,选择进程的依据主要是根据进程goodness值的大小。这种进程在运行时,可以被高goodness值的进程抢先。  2) SCHED_FIFO:这是一种实时进程,遵守POSIX1.b标准的FIFO(先入先出)调度规则。它会一直运行,直到有一个进程因I/O阻塞,或者主动释放CPU,或者是CPU被另一个具有更高rt_priority的实时进程抢先。在Linux实现中,SCHED_FIFO进程仍然拥有时间片-只有当时间片用完时它们才被迫释放CPU。因此,如同POSIX1.b一样,这样的进程就象没有时间片(不是采用分时)一样运行。Linux中进程仍然保持对其时间片的记录(不修改counter)主要是为了实现的方便,同时避免在调度代码的关键路径上出现条件判断语句 if (!(current->;;policy&;;SCHED_FIFO)){...}-要知道,其他大量非FIFO进程都需要记录时间片,这种多余的检测只会浪费CPU资源。(一种优化措施,不该将执行时间占10%的代码的运行时间减少到50%;而是将执行时间占90%的代码的运行时间减少到95%。0.9+0.1*0.5=0.95>;;0.1+0.9*0.9=0.91)  3) SCHED_RR:这也是一种实时进程,遵守POSIX1.b标准的RR(循环round-robin)调度规则。除了时间片有些不同外,这种策略与SCHED_FIFO类似。当SCHED_RR进程的时间片用完后,就被放到SCHED_FIFO和SCHED_RR队列的末尾。  只要系统中有一个实时进程在运行,则任何SCHED_OTHER进程都不能在任何CPU运行。每个实时进程有一个rt_priority,因此,可以按照rt_priority在所有SCHED_RR进程之间分配CPU。其作用与SCHED_OTHER进程的priority作用一样。只有root用户能够用系统调用sched_setscheduler,来改变当前进程的类型(sys_nice,sys_setpriority)。  此外,内核还定义了SCHED_YIELD,这并不是一种调度策略,而是截取调度策略的一个附加位。如同前面说明的一样,如果有其他进程需要CPU,它就提示调度程序释放CPU。特别要注意的就是这甚至会引起实时进程把CPU释放给非实时进程。

    主要的进程调度的函数分析

      真正执行调度的函数是schedule(void),它选择一个最合适的进程执行,并且真正进行上下文切换,使得选中的进程得以执行。而reschedule_idle(struct task_struct *p)的作用是为进程选择一个合适的CPU来执行,如果它选中了某个CPU,则将该CPU上当前运行进程的need_resched标志置为1,然后向它发出一个重新调度的处理机间中断,使得选中的CPU能够在中断处理返回时执行schedule函数,真正调度进程p在CPU上执行。在schedule()和reschedule_idle()中调用了goodness()函数。goodness()函数用来衡量一个处于可运行状态的进程值得运行的程度。此外,在schedule()函数中还调用了schedule_tail()函数;在reschedule_idle()函数中还调用了reschedule_idle_slow()。这些函数的实现对理解SMP的调度非常重要,下面一一分析这些函数。先给出每个函数的主要流程图,然后给出源代码,并加注释。  

    goodness()函数分析  goodness()函数计算一个处于可运行状态的进程值得运行的程度。一个任务的goodness是以下因素的函数:正在运行的任务、想要运行的任务、当前的CPU。goodness返回下面两类值中的一个:1000以下或者1000以上。1000或者1000以上的值只能赋给“实时”进程,从0到999的值只能赋给普通进程。实际上,在单处理器情况下,普通进程的goodness值只使用这个范围底部的一部分,从0到41。在SMP情况下,SMP模式会优先照顾等待同一个处理器的进程。不过,不管是UP还是SMP,实时进程的goodness值的范围是从1001到1099。  goodness()函数其实是不会返回-1000的,也不会返回其他负值。由于idle进程的counter值为负,所以如果使用idle进程作为参数调用goodness,就会返回负值,但这是不会发生的。  goodness()是个简单的函数,但是它是linux调度程序不可缺少的部分。运行队列中的每个进程每次执行schedule时都要调度它,因此它的执行速度必须很快。  //在/kernel/sched.c中  static inline int goodness(struct task_struct * p, int this_cpu, struct mm_struct *this_mm)  { int weight;  if (p->;;policy != SCHED_OTHER) {/*如果是实时进程,则*/  weight = 1000 + p->;;rt_priority;  goto out;  }  /* 将counter的值赋给weight,这就给了进程一个大概的权值,counter中的值表示进程在一个时间片内,剩下要运行的时间.*/  weight = p->;;counter;  if (!weight) /* weight==0,表示该进程的时间片已经用完,则直接转到标号out*/  goto out;  #ifdef __SMP__  /*在SMP情况下,如果进程将要运行的CPU与进程上次运行的CPU是一样的,则最有利,因此,假如进程上次运行的CPU与当前CPU一致的话,权值加上PROC_CHANGE_PENALTY,这个宏定义为20。*/  if (p->;;processor == this_cpu)  weight += PROC_CHANGE_PENALTY;  #endif  if (p->;;mm == this_mm) /*进程p与当前运行进程,是同一个进程的不同线程,或者是共享地址空间的不同进程,优先选择,权值加1*/  weight += 1;  weight += p->;;priority; /* 权值加上进程的优先级*/  out:  return weight; /* 返回值作为进程调度的唯一依据,谁的权值大,就调度谁运行*/  }  

    schedule()函数分析  schedule()函数的作用是,选择一个合适的进程在CPU上执行,它仅仅根据'goodness'来工作。对于SMP情况,除了计算每个进程的加权平均运行时间外,其他与SMP相关的部分主要由goodness()函数来体现。  

    流程:  ①将prev和next设置为schedule最感兴趣的两个进程:其中一个是在调用schedule时正在运行的进程(prev),另外一个应该是接着就给予CPU的进程(next)。注意:prev和next可能是相同的-schedule可以重新调度已经获得cpu的进程.  ②中断处理程序运行“下半部分”.  ③内核实时系统部分的实现,循环调度程序(SCHED_RR)通过移动“耗尽的”RR进程-已经用完其时间片的进程-到队列末尾,这样具有相同优先级的其他RR进程就可以获得CPU了。同时,这补充了耗尽进程的时间片。  ④由于代码的其他部分已经决定了进程必须被移进或移出TASK_RUNNING状态,所以会经常使用schedule,例如,如果进程正在等待的硬件条件已经发生,所以如果必要,这个switch会改变进程的状态。如果进程已经处于TASK_RUNNING状态,它就无需处理了。如果它是可以中断的(等待信号),并且信号已经到达了进程,就返回TASK_RUNNING状态。在所以其他情况下(例如,进程已经处于TASK_UNINTERRUPTIBLE状态了),应该从运行队列中将进程移走。  ⑤将p初始化为运行队列的第一个任务;p会遍历队列中的所有任务。  ⑥c记录了运行队列中所有进程最好的“goodness”-具有最好“goodness”的进程是最易获得CPU的进程。goodness的值越高越好。  ⑦遍历执行任务链表,跟踪具有最好goodness的进程。  ⑧这个循环中只考虑了唯一一个可以调度的进程。在SMP模式下,只有任务不在cpu上运行时,即can_schedule宏返回为真时,才会考虑该任务。在UP情况下,can_schedule宏返回恒为真.  ⑨如果循环结束后,得到c的值为0。说明运行队列中的所有进程的goodness值都为0。goodness的值为0,意味着进程已经用完它的时间片,或者它已经明确说明要释放CPU。在这种情况下,schedule要重新计算进程的counter;新counter的值是原来值的一半加上进程的静态优先级(priortiy),除非进程已经释放CPU,否则原来counter的值为0。因此,schedule通常只是把counter初始化为静态优先级。(中断处理程序和由另一个处理器引起的分支在schedule搜寻goodness最大值时都将增加此循环中的计数器,因此由于这个原因计数器可能不会为0。显然,这很罕见。)在counter的值计算完成后,重新开始执行这个循环,找具有最大goodness的任务。  ⑩如果schedule已经选择了一个不同于前面正在执行的进程来调度,那么就必须挂起原来的进程并允许新的进程运行。这时调用switch_to来进行切换。

    展开全文
  • 一文搞懂Linux进程调度原理

    千次阅读 2021-02-03 14:40:01
    linux内核,进程调度器的实现,完全公平调度器 CFS Linux进程调度的目标 1.高效性:高效意味着在相同的时间下要完成更多的任务。调度程序会被频繁的执行,所以调度程序要尽可能的高效; 2.加强交互性能:在系统相当的...
  • Linux进程调度原理

    千次阅读 2015-01-19 14:00:25
    Linux进程调度的目标  1.高效性:高效意味着在相同的时间下要完成更多的任务。调度程序会被频繁的执行,所以调度程序要尽可能的高效;  2.加强交互性能:在系统相当的负载下,也要保证系统的响应时间;  3.保证...
  • linux进程调度原理

    千次阅读 2011-11-06 20:24:49
    进程调度依据  调度程序运行时,要在所有可运行状态的进程中选择最值得运行的进程投入运行。选择进程的依据是什么呢?在每个进程的task_struct结构中有以下四项:policy、priority、counter、rt_priority。这四项...
  • 这里先说一说执行流,有助于线程的理解。 程序计数器中的下一条指令地址组成的轨迹称为程序的执行流。执行流是逻辑上独立的指令区域,是人为给处理器安排的处理单元。指令指导处理器的执行方向,从处理器的角度看,...
  • Linux 进程调度原理  文章网址:http://blog.163.com/zhu_xude/blog/static/11676921620095253145900/ 优先权只是调度算法考虑的一个方面 进程调度依据 调度程序运行时,要在所有可运行状态的进程中选择...
  • Linux内核 进程调度原理

    千次阅读 2015-07-16 17:00:15
    Linux进程调度的目标  1.高效性:高效意味着在相同的时间下要完成更多的任务。调度程序会被频繁的执行,所以调度程序要尽可能的高效;  2.加强交互性能:在系统相当的负载下,也要保证系统的响应时间;  3.保证...
  • linux内核普通进程CFS调度原理

    千次阅读 2017-05-07 16:37:34
    [摘要] [正文一] linux调度系统概述 [正文二] 调度过程 [正文三] 普通进程调度与实时进程调度对比 ...请先阅读:linux内核实时进程调度原理,本文的正文一、正文二两部分内容和linux系统...
  • 文章目录一、Linux线程基本概念二、Linux内核线程实现原理三、创建线程四、线程的优缺点 一、Linux线程基本概念 linux中,线程又叫做轻量级进程(light-weight process LWP),也有PCB,创建线程使用的底层函数和...
  • 进程线程调度

    千次阅读 2016-06-15 16:16:24
    本文讲述的是linux和windows中的线程-进程调度基本原理
  • 我在前面的文章中提到了linux产生进程的三个系统调用: 1.fork() 用来产生拥有独立的虚拟地址...3.clone() linux下用来产生线程的系统调用,我们使用的pthread_create()调用的就是它 线程的基础内容在这篇文章中有提...
  • 我们知道intel的cpu拥有超线程技术,可以为一个逻辑核心开启两个处理线程。通过查看物理CPU数,每个CPU的逻辑核数,CPU线程数可以得知是否开启了超线程。 物理CPU数 [root@localhost daxiang]# cat /proc/cpuinfo ...
  • LAB1 线程机制和线程调度实现 调研Linux的进程控制块 五个互斥状态 状态 描述 TASK_RUNNING 表示进程正在执行,或者已经准备就绪,等待处理机调度 TASK_INTERRUPTIBLE 表示进程因等待某种资源而被挂起...
  • linux内核实时进程的调度原理

    千次阅读 2017-05-07 16:36:31
    本文主要介绍linux内核实时进程的调度过程。
  • 打通Linux脉络系列:进程、线程和调度 1. 第一部分大纲 1.1 Linux进程生命周期 ...2.3 Linux线程的实现本质 2.4 孤儿进程的托孤,SUBREAPER 练习题 第三部分大纲 练习题 第四部分大纲 练习题
  • Pthread 线程调度

    千次阅读 2019-01-11 11:37:31
    设置以下关于调度策略之前应该首先通过pthread_attr_setinheritsched设置PTHREAD_EXPLICIT_SCHED. 1) Scheduling Scope http://man7.org/linux/man-pages/man3/pthread_attr_setscope.3.html 通过pthread_attr_...
  • Linux 操作系统原理 — 进程与线程管理

    千次阅读 多人点赞 2018-07-19 16:54:00
    目录 目录 进程与线程 Linux 的内核态与用户态 ...Linux 进程与线程调度 任务类型与优先级 设定进程优先级 任务调度 调度类型 调度策略 任务切换与开销 上下文(Context Switch)切换 运...
  • 重大软院操作系统实验二:线程调度,计算机操作系统原理linux
  • Android线程调度机制

    千次阅读 2015-07-30 15:06:15
    [译]Android线程调度机制 2014-2-24阅读789 评论0 在Android Weekly上看到了一篇关于Android线程调度的文章,感觉挺不错的,试着将其翻译了一下,原文地址...
  • Linux内核可以看作一个服务进程(管理软硬件资源,响应...内核线程调度由内核负责,一个内核线程处于阻塞状态时不影响其他的内核线程,因为其是调度的基本单位。 这与用户线程是不一样的。因为内核线程只运行在内核态
  • Linux线程详解

    万次阅读 多人点赞 2019-06-03 12:06:33
    Linux内核线程实现原理 类Unix系统中,早期是没有“线程”概念的,80年代才引入,借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切。 轻量级进程(light-weight process),也有PCB,创建...
  • 三类调度策略:RR调度和FIFO调度的进程属于实时进程,以分时调度(OTHER)的进程是非实时进程 实时进程优先级高于普通进程,实时进程使用静态优先级调度、非实时进程使用动态优先级调度,非实时进程动态优先级通过nice...
  • linux线程

    千次阅读 2010-04-09 14:01:00
    线程属性前面一章介绍了使用缺省属性创建线程的基本原理。本章论述如何在创建线程时设置属性。注 – 只有 pthreads 使用属性和取消功能。本章中介绍的 API 仅适用于 POSIX 线程。除此之外,Solaris 线程和 pthreads ...
  • LinuxLinux线程技术

    万次阅读 2018-09-05 15:57:23
    Linux线程概念 线程的概念 线程是计算机科学中的一个术语,是指运行中的程序的调度单位。一个线程指的是进程中一个单一顺序的控制流,也称为轻量进程。它是系统独立调度和分配的基本单位。同一进程中的多个线程...
  • Linux 线程锁详解

    千次阅读 2018-11-08 00:50:54
    Linux 线程锁详解
  • linux 线程编程

    千次阅读 2013-08-07 18:15:50
    一、线程的定义  进程是一个执行实体,操作系统已进程为单位分配资源。在一个执行空间内可以使用小型进程并发完成不同的任务,这种...linux环境下使用ptread_self函数得到一个线程线程ID,其函数原型如下:  #inc

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 43,772
精华内容 17,508
关键字:

linux线程调度原理

linux 订阅