本文是个人分析《Linux内核设计与实现》而写的总结,欢迎转载,请注明出处:

                                                                                 http://blog.csdn.net/dlutbrucezhang/article/details/12185125

      第二章 --进程调度

      上一章已经详细的介绍了进程和进程管理的概念,这一章的主题在操作系统能否“完美”的工作方面占有最主要的地位。很明显,根据名字我们就能看出来,就是内核去调度进程的执行。怎样去调度进程?为什么会是那样?好吧,这篇文章就是介绍这些主题的。

1.多任务

      现在的操作系统都是多任务的,也就说多个任务(进程)能够并发的执行。注意,这里用到的词是“并发”,而不是并行。并发:是指多个任务轮流占用资源执行,但是它们之间的切换是快速的,看起来就好像是同时执行一样。由于处理器的限制,即使在内存中现在有100个进程,而且都不阻塞,同一时间,它们也只能有一个进程处于运行状态,其余的是就绪状态。
      多任务操作系统分为两种:抢占式多任务和非抢占式多任务。由于非抢占式多任务效率极其低下,特别是平衡不了实时进程和普通进程之间的关系,几乎退出了现在的市场。我们平时用的操作系统普遍支持抢占。因为,这样不仅使得计算机的反应加快,更能保证高吞吐量的实现。它的含义也就是字面上的解释,即一个进程在执行的过程中,另一个“有资格”的进程打断它的执行(称之为抢占),变成运行状态的过程。

2.I/O消耗型和处理器消耗性的进程

      在操作系统运行的过程中,存在两种表现不同的进程。I/O消耗性表现为,大部分时间是在等待I/O传输的完成,很少一部分时间是在使用CPU,这里最经典的例子就是输入法;处理器消耗性进程是指很少的时间等待I/O的完成,大部分时间是利用CPU的运算,这里经典的例子是图像处理程序和Matlab计算程序。
      这样也就会存在一些矛盾的地方。应该给谁多一些的处理器时间,怎样分配是比较合理的。也许你会说,既然处理器消耗性的进程需要更多的处理器时间,应该给他分配更多的CPU,但是,如果不给I/O消耗型的进程分配更多的处理器时间,那么它的反应会相当的慢,举一个夸张的例子,当你向键盘输入一个字符之后,一秒之后才在显示器上显示,这显然是我们不能接受的。相反,计算程序即使是多一秒或者甚至是多十秒完成,我们似乎都是可以接受的,毕竟我们在等待计算完成时已经等了很久。所以,在Linux中实现的策略是给I/O消耗性的进程分配更多的处理器时间。

3.进程优先级

      不同的进程是有不同的优先级的,这似乎很正常。因为,在不同的时间里,肯定有进程要求尽快能得到执行的机会。这里的优先级也是分为两种的。
      3.1 实时优先级
              它的优先级范围从 0 -- 99,数字越大,优先级越高,能尽快得到处理的机会就会越到,而且通常这意味着它也能获得更多的处理器时间。
      3.2 nice值
               我们可能在程序中见过这样的语句 nice(5); 这里就是给当前进程的优先级计数加上5,注意,这里并不是提高了它的优先级,反而,这样会降低当前进程的优先级。nice值的范围是 -20 -- 19。默认情况下,创建的普通进程优先级为 0 。

4.时间片

      其实,在2.6以后的内核中,就不存在时间片的概念了。不过,由于这是Unix中的经典定义,所以,需要解释下。时间片就是由进程的优先级而分配的可用处理器时间。一个进程如果时间片为0,它就必须放弃执行。
      在现在的内核中,操作系统利用CFS(完全公平调度算法)算法调度进程的执行,同时为进程分配处理器时间。这里用到的概念是处理器使用比。例如,当一个进程分配了10ms的运行时间,但是它只是执行了5ms,就被其他的进程抢占了,这时,我们说,它用到的使用比是 50%。下面,我们会看到,这里的使用比是怎样在进程调度方面发挥作用的。
 

5.进程调度策略

      这里,我们首先给出两个不同种类的普通进程。一个是I/O消耗性的进程--文字处理程序;另一个是处理器消耗性的进程--视频编码程序。假如现在的系统中,这是仅有的两个进程,为了方便起见,它们用于相同的 nice 值,所以,它们理所当然的被分配了相同的处理器使用比。
      由于文字处理程序大部分时间是在等待用户的输入,所以,分配给他的处理器时间它用到的少之又少(相比视频编码程序)。所以,它剩余的处理器使用比就会很多。相反,视频编码程序剩余的处理器使用比就会很少。
      这时,我们给出调度的条件,如果消耗的使用比比当前执行进程的小,那么新的进程立即投入运行,抢占当前的进程。当然,我们同样需要考虑优先级的原则。优先级低的进程肯定不能完成抢占,除非是当前进程阻塞或者是资源放弃处理器。

6.CFS调度器

      CFS调度器是现在Linux系统中常用的调度器类。它利用当前系统中进程的个数(可运行状态)和优先级分配给每个进程不同的处理器使用比。
      当然,如果系统中进程的个数区域无限,那么进程获得的处理器时间将是一个预先设定的极值,一般是 1ms。

7.Linux调度的实现

      7.1 时间记账
             首先,我们需要看一个结构--调度器实体
struct sched_entity {
    struct load_weight    load;        /* for load-balancing */
    struct rb_node        run_node;
    struct list_head    group_node;
    unsigned int        on_rq;

u64            exec_start;
    u64            sum_exec_runtime;
    u64            vruntime;
    u64            prev_sum_exec_runtime;

u64            nr_migrations;

#ifdef CONFIG_SCHEDSTATS
    struct sched_statistics statistics;
#endif

#ifdef CONFIG_FAIR_GROUP_SCHED
    struct sched_entity    *parent;
    /* rq on which this entity is (to be) queued: */
    struct cfs_rq        *cfs_rq;
    /* rq "owned" by this entity/group: */
    struct cfs_rq        *my_q;
#endif
};


        我们可以看到,调度器实体中存在一个字段--vruntime,这代表的是虚拟时间,也就是新的概念--处理器使用比。在进程描述符内,我们可以找到指向这个实体的指针。
      7.2 进程选择
             这是最重要的主题之一,这里它的作用是选择一个最合适的调度器类,并从它的运行队列中选择一个最合适的进程。这个最合适的进程当然是 vruntime 值最小的那个进程。为了能更深刻的理解,这里给出一段代码:
/* 
 * Pick up the highest-prio task: 
 */  
static inline struct task_struct *  
pick_next_task(struct rq *rq)  
{  
    const struct sched_class *class;  
    struct task_struct *p;  
  
    /* 
     * Optimization: we know that if all tasks are in 
     * the fair class we can call that function directly: 
     */  
    if (likely(rq->nr_running == rq->cfs.nr_running)) { //如果nr_running==cfs.nr_running,则说明当前rq队列中是没有rt任务的,  
                                                            //rt任务不在cfs队列中,它的优先级的设置和cfs不一样。  
        p = fair_sched_class.pick_next_task(rq); //在cfs队列中挑选即将被切换进去的进程,核心函数,  
        if (likely(p))  
            return p;  
    }  
  
    for_each_class(class) {   
        p = class->pick_next_task(rq);  
        if (p)  
            return p;  
    }  
  
    BUG(); /* the idle class will always have a runnable task */  
}
这里我们可以看到内核中对挑选调度器类的时候做了一些优化。由于系统中几乎都是普通的进程,所以,一旦进程的总数等于CFS可运行队列中进程的总数时,那么可以直接调用CFS调度器。如果不是,那么选择优先级最高的那个调度器类。每个调度器都定义了自己的 pick_next_task() 函数,且拥有自己的可运行队列,所以,总是可以选出最合适的需要调度的进程。
       7.3 通知调度
               当需要执行进程调度的时候,操作系统是怎样知道的呢?难道需要内核执行函数一个一个进程的“询问”吗?那样显然是太低效了,所以,为了方便通知内核需要实现调度,在进程描述符中有这样一个字段 -- need_resched ,一旦这个标志被设置,那么内核就知道此刻需要进程调度了。关于,什么时候实现检查这个标志的操作,在下文中会有详细的介绍。

8.抢占的实现

      抢占也是分为两种的:
             用户抢占和内核抢占
      用户抢占:内核在返回用户空间的时候,如果 need_resched 标志被设置,会导致 schedule() 被调用,此时就会发生用户抢占。内核在中断返回或者是系统调用返回时,会检查标志是否被设置,如果被设置,那么执行进程切换,如果没有,则继续执行原先的那个进程。
      所以,用户抢占发生在以下两种情况下:
                        从系统调用返回用户空间中
                         从中断处理程序返回用户空间中
       内核抢占:Linux支持内核抢占,即进程在内核中执行的时候,也是可以被抢占的,当然,这也是需要有条件保证的,那就是,此时,抢占是安全的。这里唯一需要保证的是进程此时没有持有锁。
      为了支持内核抢占,每个进程的 thread_info 字段中增加了 preempt_count 字段,这个标志代表的是进程持有锁的个数,初始时,这个值为0,一旦进程持有锁,那么计数加1,再次拥有其他的锁时,继续增加这个数值。当进程中的 preempt_count 和 need_resched,字段都被设置时,此时可以实现内核抢占。
       内核抢占发生的时机如下:
                      中断处理程序正在执行(处于中断上下文中),且返回内核空间之前
                      内核中的任务阻塞
                      内核中进程显示的调用 schedule() 函数

9.实时进程的调度策略

      实时进程,其实就是指那些优先级比较高的进程。它们的调度策略也有两种,分别是 SCHED_FIFO,SCHED_RR
      SCHED_FIFO:如果实时进程使用这种方式执行,那么当前的实时进程将会一直执行,除非是它自己显示的放弃处理器或者是被阻塞,或者是被优先级更高的进程抢占。
     SCHED_RR:它被称为是轮转调度算法,实时进程采用这种方法调度时,它有一个执行时间,当用完这个时间后,它就被挂起,接着选择下一个进程运行,依次轮转运行队列中的进程,直到所有的进程都执行完毕。
      这两种调度算法实现的都是静态优先级,内核不为它们计算动态优先级,这样能够保证给定优先级的进程能够抢占比它优先级低的进程。

Linux内核探讨-- 第二章相关推荐

  1. Linux内核探讨-- 第一章

          本文是个人分析<Linux内核设计与实现>而写的总结,欢迎转载,请注明出处:                                                   ...

  2. linux实验报告实验二,Linux实验报告 第二章

    linux实验报告 第二章 linux终端 1,概念 使用linux时并不是直接使用系统,而是通过shell这个中间程序来完成,为了实现在一个窗口中完成用户输入和现实输出,linux系统还有一个叫做终 ...

  3. LINUX内核分析第二周学习总结——操作系统是如何工作的

    LINUX内核分析第二周学习总结--操作系统是如何工作的 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course ...

  4. 十天学Linux内核之第二天---进程

    十天学Linux内核之第二天---进程 原文:十天学Linux内核之第二天---进程 都说这个主题不错,连我自己都觉得有点过大了,不过我想我还是得坚持下去,努力在有限的时间里学习到Linux内核的奥秘 ...

  5. linux系统管理设计ppt,操作系统原理与Linux实例设计--第二章.ppt

    操作系统原理与Linux实例设计--第二章.ppt 2.5.4 实时系统与实时任务调度 实时系统与实时任务 实时系统:能及时响应外部请求,并作出反应的系统. 是一个相对的概念. 是否周期执行来划分: ...

  6. Linux内核探讨-- 第七章

    本文是个人分析<Linux内核设计与实现>而写的总结,欢迎转载,请注明出处: http://blog.csdn.net/dlutbrucezhang/article/details/136 ...

  7. Linux内核探讨-- 第六章

    本文是个人分析<Linux内核设计与实现>而写的总结,欢迎转载,请注明出处: http://blog.csdn.net/dlutbrucezhang/article/details/130 ...

  8. Linux内核探讨-- 第五章

    本文是个人分析<Linux内核设计与实现>而写的总结,欢迎转载,请注明出处: http://blog.csdn.net/dlutbrucezhang/article/details/123 ...

  9. Linux内核探讨-- 第四章

    本文是个人分析<Linux内核设计与实现>而写的总结,欢迎转载,请注明出处: http://blog.csdn.net/dlutbrucezhang/article/details/122 ...

最新文章

  1. mysql的存储引擎详解_Mysql存储引擎详解
  2. pytorch 保存网络的时候值得注意的事情
  3. 微服务Apache ServiceComb 数据一致性Saga演进介绍
  4. Android-DataBinding源码探究
  5. python-I/O-文件操作
  6. linux 磁盘资源管理以及IO
  7. 四色着色问题 c语言编程,数据结构-图着色问题
  8. 计算机辅助普通话测试试题及答案,普通话测试试题及答案
  9. Java的poi技术遍历Excel时进行空Cell,空row,判断
  10. Unity 编辑器内建图标获得
  11. UE4之windows.h冲突
  12. 配置管理工具---SVN
  13. 阶段5 3.微服务项目【学成在线】_day01 搭建环境 CMS服务端开发_22-页面查询服务端开发-Dao-基础方法测试...
  14. VMware15.5.1+macos catalina 10.5+unlocker 3.1 解决unlocker下载文件报错问题
  15. 初学者有关Hadoop版本选择的考虑
  16. Oracle隐含参数查看
  17. CSS+DIV 网页重构技术
  18. 入侵oracle数据库时常用的操作命令整理
  19. Spring项目中 注解@Aspect无法被引入的原因
  20. SPFA算法+例题 :问题 A: 黑暗城堡

热门文章

  1. Java虚拟机中的栈和堆
  2. ByteBuf和相关辅助类
  3. Android 程序自动更新功能模块实现
  4. Windows打印体系结构之打印驱动框架
  5. 凌轩:中国电信在校园市场的困与囧
  6. 从《钢铁侠2》看软件测试的重要性
  7. LeetCode-166- Fraction to Recurring Decimal
  8. 直观获取redis cluster 主从关系
  9. 【2556】传说中的数据结构 sdutOJ
  10. JavaScript代码检验工具——JS Lint工具安装指南