进程调度

调度系统设计精要

百般思念 提交于 2020-02-26 22:26:34
作者 | Draveness 导读 :本文作者写这篇文章前前后后大概 2 个月的时间,全文大概 2w 字,建议收藏后阅读或者通过电脑阅读。 调度是一个非常广泛的概念,很多领域都会使用调度这个术语,在计算机科学中, 调度 就是一种将任务(Work)分配给资源的方法。任务可能是虚拟的计算任务,例如线程、进程或者数据流,这些任务会被调度到硬件资源上执行,例如:处理器 CPU 等设备。 图 1 - 调度系统设计精要 本文会介绍调度系统的常见场景以及设计过程中的一些关键问题,调度器的设计最终都会归结到一个问题上 — 如何对资源高效的分配和调度以达到我们的目的,可能包括对资源的合理利用、最小化成本、快速匹配供给和需求。 图 2 - 文章脉络和内容 除了介绍调度系统设计时会遇到的常见问题之外,本文还会深入分析几种常见的调度器的设计、演进与实现原理,包括操作系统的进程调度器,Go 语言的运行时调度器以及 Kubernetes 的工作负载调度器,帮助我们理解调度器设计的核心原理。 设计原理 调度系统其实就是调度器(Scheduler),我们在很多系统中都能见到调度器的身影,就像我们在上面说的,不止操作系统中存在调度器,编程语言、容器编排以及很多业务系统中都会存在调度系统或者调度模块。 这些调度模块的核心作用就是对有限的资源进行分配,以实现最大化资源的利用率或者降低系统的尾延迟

多线程编程总结

这一生的挚爱 提交于 2020-02-24 05:48:57
一、线程模型: 线程是程序中完成一个独立任务的完整执行序列,即一个可调度的实体。根据运行环境和调度者的身份,线程可分为 内核线程和 用户线程。 内核线程:运行在内核空间,由内核来调度; 用户线程:运行在用户空间,由线程库来调用。 当进程的一个内核线程获得CPU的使用权时,它就加载并运行一个用户线程。可见,内核程序相当于用户线程运行的容器。一个进程可以拥有M个内核线程和N个用户线程,其中M≤N。并且在一个系统的所有进程中,M和N的比值都是固定的。按照M:N的取值,线程的实现方式可分为三种模式: 完全在用户空间实现、 完全由内核调度和 双层调度。 1、完全在用户空间实现的线程无须内核的支持,内核甚至根本不知道这些现成的存在。线程库负责管理所有执行线程,比如线程的优先级、时间片等。线程库利用longjmp来切换线程的执行,使它们看起来像是“并发”执行的。但实际上内核仍然是把整个进程作为最小单位来调度的。换句话说,一个进程的所有执行线程共享该进程的时间片,它们对外表现出相同的优先级。因此,对于这种实现方式而言,M=1,即N个用户空间线程对应1个内核线程,而该内核线程实际上就是进程本身。 完全在用户空间实现的线程的优点是:创建和调度线程都无需内核的干预,因此速度相当快。并且由于它不占用额外的内核资源,所以即使一个进程创建了很多线程,也不会对系统性能造成明显的影响。其缺点是:对于多处理器系统

Linux O(1)调度器

血红的双手。 提交于 2020-02-24 04:59:43
前面我们学习了O(n)调度器的设计,以及它的核心算法。在这里复习下。 O(n)调度器核心: O(n)调度器采用一个runqueue运行队列来管理所有可运行的进程,在主调度schedule函数中会选择一个优先级最高,也就是时间片最大的进程来运行,同时也会对喜欢睡眠的进程做一些补偿,去增加此类进程的时间片。当runqueue运行队列中无进程可选择时,则会对系统中所有的进程进行一次重新计算时间片的操作,同时也会对剩余时间片的进程做一次补偿。 O(n)调度器的缺陷: 时间复杂度是O(n) SMP系统扩展不好,访问runqueue需要加锁 实时进程不能及时调度 CPU空转的现象存在 进程在各个CPU之间跳跃,性能影响 O(1)调度器的引入 基于O(n)调度器的种种问题,linux内核社区则在2.6内核版本引入了O(1)调度器,当然了引入的目的也正是要解决O(n)调度器面临的问题。我们这片文章以Linux2.6.2版本来学习,在Linux内核文档中有一篇关于O(1)调度器的目的,如何设计的,以及实现有一个详细的介绍:sched-design.txt文档,有兴趣的可以去阅读。 O(1)调度器的工作原理 系统中的runqueue是一个PER_CPU变量,也就是说每一个CPU维护这一个runqueue,这样在SMP系统就可以有效的避免多个CPU去访问同一个runqueue。

linux 进程调度1

余生颓废 提交于 2020-02-21 19:13:02
调度策略与调度类 进程分为实时进程和普通进程,分别对应实时调度策略和普通调度策略 在 task_struct 中,有一个成员变量,我们叫调度策略 unsigned int policy; 它有以下几个定义: #define SCHED_NORMAL 0 #define SCHED_FIFO 1 #define SCHED_RR 2 #define SCHED_BATCH 3 #define SCHED_IDLE 5 #define SCHED_DEADLINE 6 配合调度策略的,还有我们刚才说的优先级,也在 task_struct 中 int prio, static_prio, normal_prio; unsigned int rt_priority; 实时进程,优先级的范围是 0~99;对于普通进程,优先级的范围是 100~139。数值越小,优先级越高 实时进程的调度策略: SCHED_FIFO、SCHED_RR、SCHED_DEADLINE 普通进程的调度策略:SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE 代码实现: 在 task_struct 里面,还有这样的成员变量: const struct sched_class *sched_class; 调度策略的执行逻辑,就封装在这里面 stop_sched_class

linux 进程调度2

↘锁芯ラ 提交于 2020-02-21 18:56:57
抢占式调度 两种情况: 执行太久, 需切换到另一进程; 高优先级进程被唤醒 切换到另一进程实现:   时钟中断处理函数会调用 scheduler_tick()查看是否是需要抢占的时间点 void scheduler_tick(void) { int cpu = smp_processor_id(); struct rq *rq = cpu_rq(cpu); struct task_struct *curr = rq->curr; ...... curr->sched_class->task_tick(rq, curr, 0); cpu_load_update_active(rq); calc_global_load_tick(rq); ...... } 由时钟中断触发检测, 中断处理调用 scheduler_tick 取当前进程 task_struct->task_tick_fair()->取 sched_entity cfs_rq 调用 entity_tick() entity_tick() 调用 update_curr 更新当前进程 vruntime, 调用 check_preempt_tick 检测是否需要被抢占 check_preempt_tick 中计算 ideal_runtime(一个调度周期中应该运行的实际时间), 若进程本次调度运行时间 > ideal

Starting from fork(...)

纵然是瞬间 提交于 2020-02-21 04:48:55
作为计算机程序的基本单位,一切五花八门,新奇古怪的程序都源于一个fork。亚当夏娃之后,人类繁衍生息便出现了社会,fork繁衍生息之后便出现了windows,或者Linux,又或者你手中的iPhone5,双卡双待,大屏加超长待机,还有标配的炫酷铃声——《爱情买卖》。 fork不是一个C函数,而是一个系统调用。c通常是用户层的语言,比如简单的加减法,若要解决复杂的问题,比如申请一段内存,开多进程,这显然不是c 能办到的,或者你也不知如何实现这样一个函数。不同的操作系统有自己的标准,亦有自己定义的API,fork一个进程更不会是一套相同的代码。这种C自己办不到的事情,只能量力而行,通知系统(内核)帮自己处理下咯,内核处理好,将结果返回给c,这便是合作的道理。 创建一个进程 #include <unistd.h>pid_t fork(void); 系统调用的过程 --> 应用程序函数,也就是上面的pid fork(void) --> libc里的封装例程 , 向内核发送系统调用号 --> 系统调用处理函数,接收到系统调用号,通过sys_call_table找到相应服务例程地址 /* 0 */ CALL(sys_restart_syscall) CALL(sys_exit) CALL(sys_fork_wrapper) //--> CALL(sys_read) CALL(sys_write

linux调度

一个人想着一个人 提交于 2020-02-20 18:06:16
调度的基本数据结构 1.每个cpu都有自己的一个struct rq, 里面有一个实时进程的struct rt_rq 和一个普通进程的struct cfs_rq。 在调度时,调度器首先会先去实时进程队列找是否有实时进程需要运行,如果没有才去CFS运行队列找是否有进程需要运行。 2.普遍进程的cfs_rq 定义如下 这里面的rb_root指向的就是红黑树的根节点,这个红黑树在CPU看起来就是一个队列,不断的取下一个应该运行的进程。 rb_leftmost指向的是最左面的节点 3.调度队列——红黑树,调度实体 完全公平调度算法CFS,CFS会为每一个进程安排一个虚拟运行时间vruntime。如果一个进程在运行,随着时间的增长,进程的vruntime将不断增大,但没有得到执行的进行vruntime将不变。 显然,那些vruntime少的,原来受到了不公平的对待,需要给它补上,所以会优先运行这样的进程。 高低优先级怎么处理?优先级其实就是一个数值,对于实时进程,优先级的范围是0-99;对于普通进程,优先级的范围是100-139.数值越小,优先级越高。 在计算vruntime的时候有一个公式 权重和nice数值有关系,nice越小权重就越大, NICE_0_LOAD是nice为0时的权重。所以从这个公式我们可以看出,高优先级(即nice值小的)的进程vruntime加的会少

进程的优先级与调度策略—Linux

寵の児 提交于 2020-02-18 03:37:26
文章目录 1.概述 1.1 进程优先级 1.2 普通进程的调度 1.2.1 静态优先级和基本时间片 1.2.2 动态优先级和平均睡眠 1.3 实时进程的调度 1.4 内核空间优先级 2.调度策略 2.1 进程的抢占 2.2 调度算法 2.3 O(1)调度 2.4 调度模型——机制与策略分离 2.5 完全公平调度——CFS 2.6 调度器总结 1.概述 进程调度中的所谓调度就是从就绪队列中选择一个进程投入CPU运行,则调度的主战场就是就绪队列,核心是调度算法,实质性的动作是进程的切换。 对于以时间片为主的调度,时钟中断就是驱动力,确保每个进程在CPU上运行一定的时间。在调度的过程中,用户还可以通过系统调用nice来调整优先级,比如降低自己的优先级等等。 当然也涉及进程状态的转换,新创建的进程就加入到了就绪队列中,推出的进程就从队列中删除。 从图中可以看出,所有CPU的所有进程都存放在了一个就绪队列中,那么我们从中选中一个进程进行调度的过程,实际上是从这个队列上的一种线性查找的过程,因此其算法复杂度为O(n)。 把就绪状态的进程组成一个双向循环链表,也叫就绪队列(runqueue)。 在task_struct结构里头定义的队列的结构就是一个list_head。 循环链表的队头是init_task结构,即0号进程的PCB。 1.1 进程优先级 在进程的调度算法中

操作系统面试题总结

孤街浪徒 提交于 2020-02-17 11:38:30
1.进程的常见状态?以及各种状态之间的转换条件? 就绪 执行 阻塞:正在执行的进程由于某些发生的事件/中断(如I/O请求,申请缓冲区失败暂时无法继续执行的状态) 2.进程同步 同步和互斥的区别? 同步指的是不同进程之间的执行有先后顺序/依赖关系。需要对多个相关进程在执行次序上进行协调,使并发执行的诸进程之间互相合作,使程序的执行具有可再现性。 同步机制遵循的原则 空闲让进 忙则等待(保证对临界区的互斥访问) 有限等待(等待有限的时间,避免死等) 让权等待(进程不能自己进入临界区时,应该是v昂处理机,一面陷入忙等待状态) 3.进程通信的方式有哪些? 低级通信 :使用信号进行进程间的互斥和同步,交换的信息量少 高级通信 :利用才做系统提供的一组通信命令传送大量数据,实现过程对进程/用户隐藏 高级通信机制可以分为一下三大类 共享存储器系统(如剪贴板) 消息传递系统 管通信系统 管道时单向的,先进先出,无结构的,固定大小的字节流,把一个进程的标准输出和另一个进程的标准输入连接在一起。一段写入,另一端读取,有阻塞机制 什么是信号量 :简单来说就是一个计数器 消息队列 共享内存 :不同进程的虚拟内存可以被映射到同一块物理内存 套接字 4.上下文切换 将CPU资源从一个进程分配给另一个进程的机制。切换的过程中,操作系统需要保存当前进程的状态(包括寄存器,程序计数器,内存空间的指针/页表头的地址等

线程的前世今生

一世执手 提交于 2020-02-17 01:02:58
进程 早期的计算机只有一个单核CPU,操作系统把进程作为CPU调度单元。进程拥有独立的内存地址空间,那时候还没有线程的概念。 进程有3个状态,分别是阻塞、就绪、运行。当进程所需资源未到位时是 阻塞状态 ,当进程拥有资源但未被CPU调度是 就绪状态 ,当进程用有资源并且被CPU调度了就是 运行状态 。 用户态线程 随着程序越来越复杂,调度产生的上下文切换也越发昂贵,于是程序员寻思能不能在同一地址空间(Address Space)下,执行多个进程。但操作系统内核出于保护目的,禁止一个进程直接访问另一个进程的地址空间。 既然操作系统不支持,程序员决定在用户空间下维护一张线程表,实现可以自行调度的“进程”,这就是 用户态线程 ,现在一般叫做 纤程 或 协程 。 用户态线程的优势有: 在用户空间下进行线程切换的速度远快于在操作系统内核中的实现 程序员可以自行实现垃圾回收器来回收线程 当线程数量过多时,由于在用户空间维护线程表,不会占用大量的操作系统空间 用户态线程的劣势有: 由于操作系统不知道线程的存在,因此当一个进程中的某一个线程进行系统调用时,比如缺页中断而导致线程阻塞,此时操作系统会阻塞整个进程,即使这个进程中其它线程还在工作 假如进程中一个线程长时间不释放CPU,因为用户空间并没有时钟中断机制,会导致此进程中的其它线程得不到CPU而持续等待 内核态线程 伴随多核CPU的出现