Linux-进程调度器

1. 前言

在计算机中,进程的数量远多于cpu的数量,所以就存在,多个进程抢占一个cpu的情况,所以就需要一套规则,决定这些进程被处理的顺序,这就叫做进程调度。

在我的简单理解下,其实就是把进程放在一个队列中,cpu挨个去执行,但是后面知道了进程具有并发性 ,其实就是,一个cpu在某一时刻,只能处理一个进程,但是cpu并不会处理完这个进程,而是处理很短的时间(毫秒级别),进程在cpu上跑的时间段,我们称之为时间片。不论处理的怎么样,结束没结束不重要,接着处理下一个,这个进程中间的上下文数据被保存到进程PCB中,然后去排队吧。

这就是并发,虽然在某一时刻,我只在处理一个任务,但是在一个时间段,我就相当于同时处理多个任务。这些任务是被同步推进的。

后面还有进程优先级的概念,就相当于在排队,但是你VIP你就可以按照规则排在前面。

但是对于cpu而言,他只是负责计算,至于这些进程的优先级处理我并不关心,我只想要知道下一个进程是谁。那进程排序的规则是什么,又是谁来维护呢?

2. 进程调度器

进程调度器,它负责计算并决定一个进程何时获取CPU时间以及占用CPU的时长。

不要害怕,这些是我总结的大部分内容的结构图 ,接下来我会一一介绍这些

你应该看到了,进程调度器在最上面,和cpu交互的那个sched_class,Linux设计的一个结构体类型,里面定义了很多抽象的接口(函数指针)。

cpp 复制代码
struct sched_class{
    // 链表
    const struct sched_class* next; 

    // 向运行队列添加一个进程
    void(*enqueue_task)(struct rq* rq, struct task_struct* p, int flags);

    // ...
    
    // 挑选下一个优先级更高的进程
    struct task_struct(*pick_next_task)(struct rq* rq, struct task_struct* prev, struct rq_flags* rf);
}

enqueue_task:向运行队列中插入一个进程

pick_next_task:从运行队列中挑选下一个优先级更高的进程

里面类似的函数指针还有很多,实现不同的功能。其次调度器其实是一个链表。进程通用调度器提供了一个模板,调度类其实就是这些类型的实现,以及对这些接口的实现。

3. 进程调度类

为什么要实现这么多的调度类呢,因为不同的使用场景:

  • stop调度类,是系统内核线程所使用,用户不能使用,优先级最高,任务一但被执行,它将不能被抢占,不能被切换,其将一直执行下去,直至进程执行完或主动让出cpu。
  • 截止日期(dl)调度类,这个任务有最后期限,必须在任务最后期限之前完成,例如播放视频,一秒钟60帧的视频,大概每16毫秒就要播放一帧画面,这个就是最后期限
  • 实时(rt)调度类,需要立马执行的任务,需要具有实时的特性,就像驾驶系统的刹车任务,必须要实时响应
  • 完全公平(fair)调度类,这些任务都是完全公平的接受"相同"的时间,这个时间其实是虚拟时间,后面会说
  • 空闲(ide)调度类,没有其他进程需要执行,就轮到它了

因为每个调度类都有自己的排序规则,所以Linux就使用这种设计:第一层,定义结构体类型,定义抽象的操作接口,比如向运行队列插入一个任务,从运行队列中挑选一个任务;第二层,调度类,根据自身类的特点,实现具体的操作。

通过这样两层,调度器可以从每个调度类的细节实现中抽离出来

4. 进程运行队列

运行队列,顾名思义,运行队列...

调度类,是方法的实现,你需要插入任务啊,还是删除任务,还是选择任务,这些方法都可以通过调度类的函数方法实现,但是没有数据只有方法肯定是不行的。

运行队列,其实就是对各个进程的通过数据结构管理起来,简而言之存放进程的地方

不同的调度类,需要不同的数据结构来进行管理,所以就出现了不同的运行队列,例如实时调度类就要有先进先出队列,环形队列。截止日期调度类和完全公平调度类依赖的数据结构都是红黑树。

5. 进程调度过程

进程的调度是从调用通用调度器开始的,kernel/sched/core.c中定义的schedule()函数。该函数的功能是挑选下一个最佳的可运行任务。schedule()函数中的pick_next_task()遍历调度类中包含的所有对应的函数,并最终选出要运行的下一个最佳任务。

---摘自《精通Linux内核开发》

prev是一个task_struct*类型的指针,task_struct内部包含一个sched_class*类型的指针,指向该进程属于的调度类。

6. CFS完全公平调度类(浅谈)

CFS,这里主要谈谈以下三点:虚拟运行时间(vruntime),权重计算,红黑树排序

前面两个主要是为了CFS的公平和优先级,最后一个决定运行队列的数据结构

如何能够保证在这个类中的所有进程都是完全公平的接受cpu的调度呢,但是还有优先级。你一听,这不是互相矛盾嘛,又要公平,又要有优先级。确实矛盾。但是没办法,就是在有优先级的情况下实现公平。

如果只要公平,那就每个进程都运行相同的时间,如果要优先级,那就你先我后,但是你忘记并发了嘛,必须要每一个进程都要上去跑一会。

所以虚拟运行时间(vruntime)和权重计算就是这么来的。

每一个进程都有一个真实运行时间和虚拟运行时间,真实运行时间,就是你真实在cpu上跑了多少毫秒,vruntime其实是根据真实运行时间和优先级权重的权重计算而来的,然后再红黑树中按照vruntime来进行排序。每次pick_next_task都会选择红黑树最左端的进程。

nice值标识进程的优先级,nice值每减少1,CPU的时间片会增加10%

例如:一个A进程nice值为0,另一个B进程nice值为1,假如A进程的时间片是10ms,它也真实跑了10ms,那么他的vruntime就会加10,而B进程时间片是11ms,它也真实跑了11ms,但是根据权重计算,它的vruntime只会加10.由此实现完全公平。

7. 实时调度类(浅谈)

实时调度类,它的运行队列的数据结构是带头双向链表。

它有两种调度策略:

SCHED_FIFO(先进先出实时调度策略)

进程一旦获得CPU执行权,就会一直运行下去,直到该进程自愿放弃CPU,实时进程按照优先级队列排序

SCHED_RR(轮转实时调度策略)

进程在执行完一个时间片后,即使没有完成任务,也会被迫让出CPU给同一优先级的其他进程,同一优先级的实时进程能够实现时间片的轮转,确保在紧迫性相同的情况下公平分配CPU时间

8. 总结

Linux内核的知识非常多,对于进程调度这一块内容有很多,这篇博客只能带大家揭开内核神秘面纱的一角,希望大家有所收获。

关于进程调度器的代码啊,我建议大家可以看看这篇博客:http://t.csdnimg.cn/ORcS7

相关推荐
大树883 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质4 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush44 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5204 小时前
Linux 11 动态监控指令top
linux
小宇宙Zz4 小时前
Maven依赖冲突
java·服务器·maven
Inhand陈工5 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智5 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
不会C语言的男孩5 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
shushangyun_5 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化