zoukankan      html  css  js  c++  java
  • 课本第四章读书笔记

    进程调度

    进程调度程序是确保进程能有效工作的一个内核子系统。负责决定哪个进程投入运行,何时运行以及运行多长时间。

    基本工作:最大限度的李颖处理器时间的原则,只要有可执行的进程就总有进程在执行,一旦某一给定时刻会有一些进程无法执行,这些进程等待运行,在一组可运行状态的进程中选择一个来执行

    4.1 多任务

    多任务操作系统就是能同时并发的执行多个进程的操作系统

    在单处理器上:会产生多个进程同时运行的幻觉
    在多处理器上:使多个进程真正同时并行的运行
    

    多任务操作系统可以分为两类:抢占式多任务和非抢占式多任务

    抢占:调度程序来决定什么时候停止一个进程的运行,以便其他进程能够得到执行机会

    时间片:预先设计好的进程在被抢占之前能运行的时间
    

    非抢占:除非进程自己主动停止运行(让步),否则他会一直执行

    缺点:程序无法对每个进程该执行多次时间做出同一规定,所以进程独占处理器的时间难以预料
    	 一个绝不做出让步的悬挂进程就能是系统崩溃
    

    4.2 Linux的进程调度

    从Linux2.5开发系列的内核中进程调度做了大的改进,采用了一种叫做O(1)调度程序的新调度程序,主要引入了今天时间片算法和针对每一处理器的运行队列

    由于O(1)虽然对大服务器的工作负载很理想,但是在有很多交互程序要运行的桌面系统上则表现不佳,因为其缺少交互进程

    从Linux2.6内核系统开发初期,为了解决这一缺陷引入了新的调度算法“反转楼梯最后期限法”,最终在2.6.23版本中代替了O(1)算法,此时被称为完全公平调度算法(CFS)

    4.3 策略

    策略决定调度程序在何时让什么程序运行。

    调度器的策略往往决定整个系统的整体印象,并且要优化使用处理器时间
    

    4.3.1 I/O消耗型和处理器消耗型的进程

    ● I/O消耗型

    大部分时间用来提交I/O请求或者是等待I/O请求。因此这样的程序通常处于可运行状态

    ● 处理器消耗型

    进程把时间大多数用在执行代码上,除非被抢占,否则一直执行

    从响应速度考虑,调度策略往往降低他们的调度频率
    

    这种划分并非绝对,进程可以同时展示这两种行为

    调度策略往往要在两个矛盾的目标中间寻找平衡:进程响应迅速(时间短)和最大系统利用率(高吞吐量)

    Unix系统的调度程序更倾向于I/O消耗型程序,以提供更好的程序响应速度

    Linux为了保证交互式应用和桌面系统的性能,对进程的响应做了优化(缩短响应时间),但也未忽略处理器消耗型的进程

    4.3.2 进程优先级

    调度算法中最基本的一类就是基于优先级的调度:根据进程的价值和对处理器时间的需求来对程序分级

    调度程序总是选择时间片未用尽且优先级最高的进程运行。用户和系统都可以通过设置进程的优先级来影响进系统的调度

    Linux采用的两种不同的优先级范围

    ● nice值(范围-20~+19):相比较高的nice值,低nice值的进程可以获得更多的处理器时间
    可以通过ps-el命令插卡系统中的而进程列表,结果中标记NI的一列就是进程对应的nice值
    
    ● 实时优先级(范围0~99):与nice值相反,越高的实时优先级数值意味着进程优先级越高。任何实时进程的优先级都高于普通进程
    可以通过
    	pe-eo state,uid,pid,ppid,rtprio,time,coom.
    命令查看系统中的进程列表,以对应的实时优先级(位于RTPRIO列下)
    

    4.3.3 时间片

    时间片是一个数值,表明进程在被抢占前佐能持续运行的时间

    调度策略必须规定一个默认的时间片,

    时间片过长:系统对交互的响应表现欠佳,让人觉得系统无法并发的执行应用程序
    时间片过短:增大进程切换带来的处理器耗时
    

    I/O消耗型和处理器消耗型的进程的矛盾显现:

    I/O型长的时间片二处理器消耗型则希望越长越好
    

    Linux的CFS调度器没有直接分配时间片到进程,而是将处理器的使用比划分给了进程,这样一来进程所获得的处理器时间其实是系统负载密切相关的。

    这个比例进一步还会受到nice值的影响,nice值将作为权重来调整进程所使用的处理器时间使用比

    在新的CFS调度器,其抢占的时间取决于新的可运行程序消耗了多少处理器使用比

    4.4 Linux调度算法

    4.4.1 调度器类

    Linux调度器是以模块方式提供的,这样做的目的是允许不同类型的进程可以有针对的选择调度算法

    这种模块化结构被称为调度器类,他允许多种不同的可动态添加的调度算法并存,调度属于自己范畴的进程

    基础的调度器代码定义在kernel/sched.文件中
    

    完全公平调度(CFS)是一个针对普通进程的调度类

    CFS算法的实现定义在kernel/sched_fair.c中
    

    4.2.2 Unix系统中的进程调度

    在Unix系统中,优先级以nice值的形式输出给用户空间。听起来简单,但是在现实中会导致许多反常的问题

    • 若要将nice的值映射到时间片。就必须将nice单位值对应到处理器的绝对时间,但这样导致进程切换无法最优化进行

        事实上,给定高nice值的进程往往是后台进程,而且多是计算密集型;而普通优先级的进程则更多是前台用户任务
        这种时间片分配方式显然是歌初衷背道而驰
      
    • 设计相对nice值,同时和前面的nice值到时间片映射关系也脱不了关系

        nice值通常都是使用相对值,也就是说把进程的nice值减小1所带来的效果极大的取决于其nice的初始值
      
    • 如果执行nice值到时间片的映射,我们需要能分配一个必须能在内核的、测试范围内的绝对时间片

        最小时间片必然是定时器节拍的整数倍
        系统定时器限制了两个时间片的差异
        时间片会随着定时器节拍改变
      
    • 为了进程能更快的投入运行,而去对新要唤醒的进程提升优先级,即使他们的时间片已经用完了

    实质问题:分配绝对的时间片引发固定的切换频率,给公平性造成很大的变数

    4.4.3 公平调度

    CFS的出发点基于一个简单的理念:进程调度的效果应如同系统具备一个理想中完美的多任务处理器(在一定时间内同时运行程序,分享使用处理器的能力),但现实并不能做到

    CFS的做法是:

    运行每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程
    不在采用分配给每个进程时间片的做法
    CFS在所有可运行进程总数基础上计算出一个进程该运行多久,而不是依靠nice值来计算时间片
    nice值在CFS中作为进程获得处理器运行比的权重
    每个进程按其权重在全部可运行进程中所占比例来运行
    

    4.5 Linux调度的实现

    CFS是如何得以实现的。相关代码位于kernel/sched_fair.c

    • 时间记账
    • 进程选择
    • 调度器入口
    • 睡眠和唤醒

    4.5.1 时间记账

    所有的调度器都必须对进程运行时间做记账

    1、调度器实体结构

    CFS不在有时间片概念,但是他也必须维护每个进程运行的时间记账,确保每个进程只在公平分配给他的处理器时间内运行

    2、虚拟实时

    vruntime变量存放进程的虚拟运行时间,该运行时间的计算是经过了可运行进程总数的饿标准化

    update_curr()计算了当前进程的执行时间,并且将其存放在变量delta_exec中
    然后将运行时间传递给了__update_curr()
    由此根据当前可运行进程数对运行时间进行加权计算
    最终将上述的去那种与当前运行进程的vruntime相加
    

    4.5.2 进程选择

    当CFS需要选择下一个运行进程时,体会挑一个具有最小vruntime的任务——核心

    红黑树:自平衡搜索二叉树,以树节点形式存储的数据都会对应一个键值,通过这些键值来快速检索节点上的数据

    1、挑选下一个任务

    从树的根节点沿着左边的子节点一直向下找,一直找到叶子节点

    函数:__ pick _next _entity()

    定义在:kernel/sched _fair.c

    static struct sched_rntity *__pick_next_entity(struct cfs_rq *cfs_rq)
    {
    	struct rb_node *life = cfs_rq->rb_leftmost'
    	if(!left)
    		return NULL;
    	return br_entry(left,struct sched_entity,run_node);
    }
    
    2、向树中加入进程

    发生在进程变为可运行状态或通过fork调用第一次创建进程时

    函数:enqueue_entity

    该函数更新运行时间和其他数据统计,然后调用__ enqueue_ entity进行繁重的插入操作。把数据插入到红黑树中

    平衡二叉树的基本规则:

    如果键值小于当前节点的键值,则需转向树的左分支。大于则右转;
    一旦走过右分支说明插入的过程不会是新的最左节点,设置letfmost为0
    如果一直左移,letfmost维持1
    
    3、从树中删除进程

    4.5.3 调度器入口

    进程调度的主要入口点是函数schedule(),定义在kernel/sched.c中。是内核其他部分用于调用进程调度器的入口。他会调用pick_next_tast()函数以优先级为序,从高到低依次检查每一个调度类

    每一个调度类都实现了pick _next _tast()函数,返回指向下一个可运行进程的指针或NULL

    4.5.4 睡眠和唤醒

    休眠(被阻塞)的进程处于一种特殊的不可执行状态

    无论内种休眠原因,内核的操作都相同

    进程把自己标志成休眠状态,从可执行红黑树中移出,放入等待队列
    调用schedule(),选择一个执行的其他进程
    
    1、等待队列

    等待队列是由等待某些事件发生的进程组成的简单的链表

    内核用wake _queue _head _t来代表等待队列

    静态创建:DECLARE_WAITQUEUE()
    动态创建:init_waitqueue_head()
    

    针对休眠的接口

    /*q是希望休眠的等待队列*/
    DEFINE_WAIT(wait);      //创建一个等待队列的项
    add_wait_queue(q,&wait);       //把自己加入到一个等待队列中
    while(!condition){/*condition是我们等待的事件*/
    	prepare_to_wait(&q,&wait,TASK_INTERRUPTABLE);
    		//将进程的状态改变
    	if(signal_pending(corrnt))
    		/*处理信号,检查条件是否为真*/
    	schedule()
    }
    finish_wait(&q,&wait);   //把自己移出等待队列
    

    函数inotify_ read(),位于fs/notify/inotify/inotify_user.c中,负责从通知文件描述符中读取信息

    2、唤醒

    通过wake _ up函数进行,唤醒指定的等待队列上的所有进程。调用try-o-wake-up()

    4.6 抢占和上下文切换

    上下文切换:从一个可执行进程切换到另一个可执行进程,有context_ switch负责。完成以下两项基本工作:

    调用声明在<asm/mmu_context.h>中的switch_mm(),该函数负责把虚拟内存从上一个进程映射切换到新进程中
    调用声明在<asm/system.h>中的switch_to(),该函数负责从上一个进程的处理器状态新的进程的处理器状态
    

    4.6.1 用户抢占

    原因:内核即将返回用户空间的时候,如果need-resched标志被设置,会导致schedule被调用,此时就会发生抢占

    用户抢占发生的情况:

    从系统调用返回用户空间时
    从中断处理程序返回用户空间时
    

    4.6.2 内核抢占

    只要重新调度室安全的,内核就可以在任何时间抢占正在执行的任务。即只要没有持有锁(非抢占区域的标志),内核就可以抢占进程

    内核抢占发生的情况:

    中断处理程序正在执行,且返回内核空间之前
    内核代码再一次具有可抢占性的时候
    如果内核中的任务显示的调用schedule
    如果内核中的任务阻塞
    

    4.7 实时调度策略

    SCHED_FIFO实现了一种简单的先入先出的调度算法

    SCHED_RR进程在消耗事先分配给他的时间后就不能再执行了

    这两种算法实现的都是静态优先级。内核补位实时进程计算动态优先级

    软实时:内核调度进程尽力使进程在他的限定时间来到前运行,单内核不能总保证这些要求

    硬实时:保证在一定条件下可以满足任何调度的要求

    4.8 与调度相关的系统调用

    4.9 总结

    进程调度程序是内核的重要组成部分。Linux内核新的cfs调度程序尽量满足了各个方面的需求,并且以较完善的可伸缩性和新颖的方法提供了最佳的解决方案

  • 相关阅读:
    梦断代码第8章总结
    <<梦断代码>>读后感
    站立会议第四篇
    购买一批书的最低价格
    NABCD分析
    首尾相连的二维数组求最大字数组的和
    站立会议第三篇
    站立会议第二篇
    站立会议第一篇
    牛客算法周周练16D Rinne Loves Dynamic Graph(分层图最短路 + 堆优化dijkstra)
  • 原文地址:https://www.cnblogs.com/javajy/p/5380783.html
Copyright © 2011-2022 走看看