1.1.1 进程的基本知识

1. 调度类型  
每个Linux进程总是按照下面的调度类型被调度:

l SCHED_FIFO

这是先进先出的实时进程。当调度程序把CPU分配给进程的时候,它把该进程描述符保留在运行队列链表的当前位置。如果没有其它可运行的更高优先级实时进程,进程就继续使用CPU,想用多久就用多久,即使还有其他具有相同优先级的实时进程处于可运行状态。

l SCHED_RR

时间片轮转的实时进程。当调度程序把CPU分配给进程的时候,它把该进程的描述符放在运行队列链表的末尾。这种策略保证对所有具有相同优先级的SCHED_RR实时进程公平地分配CPU时间。

l SCHED_NORMAL

普通的分时进程。当进程使用完成CPU时间片后,就会被退出活动队列,而加入到过期队列。只有当过期队列变成活动队列时,普通的时分进程才有机会获得CPU使用权。

2. 进程的优先级

在Linux,进程是分优先级的。进程调度器在调度进程时,会先选择最高优先级的进程运行。也是说,高优先级的进程会获得更多的运行机会。Linux-2.6进程的优先级分为0 ~ 139级,0级为最高优先级,139级为最低先级。在实时进程的优先级是在0 ~ 99之间。

进程的优先级可以用sched_setscheduler系统调用设置进程的静态优先级。

在进程调度时,选择进程是根据进程的动态优先级。SCHED_NORMAL进程的动态优先级是根据其静态优先级和进程的运行情况决定。也是说,SCHED_NORMAL进程的动态优先级会在运行时动态改变。使用sched_setscheduler设置SCHED_NORMAL进程的优先级后,在初始时,进程的动态优先级等于其静态优先级。

使用sched_setscheduler设置SCHED_RR或SCHED_FIFO的优先级后,其动态优级等于其静态优先级。在进程运行的过程中,其动态优先级不会发生改变。

我们都知道Linux进程的运行都基于时间片的。每个进程分一个时间片。当进程占用CPU并用完时间片后,调度程序会重设其时间片,然后调度下一个进程使用CPU。而进程的时间片设置与其静态优先级密切相关,一般能成正比关系。

3.运行状态进程的组织方法

在Linux-2.6摒弃了之前使用一个链表组织所有处于运行状态进程的方法。在Linux-2.6把运行队列分0 ~ 139的优先级,每个优先级维护一个链表。那么处于运行状态的进程就放在其动态优先级对应的链表中。另外,时间片已经用完的进程就放在过期队列中,过期队列与活动队列是一样的数据结构,图下所示:

调度程序选择进程使用CPU时,只在活动队列中找。只有活动队列中没有了任何进程,才把过期队列变成活动队列,原活动队列变成过期队列。空集用于接收过期进程。

在SMP系统中,每个CPU都独立维持一个运行队列。

还有每个CPU都有自己的swapper进程,其PID等于0。但它不在进行队列中。若调度程序不能在运行队列中找到可调度的进程,才运行swapper进程。

1.递减时间片

当SCHED_RR或SCHED_NORMAL进程获得CPU使用权时,需要不断递减其时间片。下面就介绍在标准Linux下是如何递减进程的时间片。

在Linux的0号中断是一个定时器中断。在固定的时间间隔都发生一次中断,也是说每秒发生该中断的频率都是固定的。该频率是常量HZ,该值一般是在100 ~ 1000之间。该中断的作用是为了定时更新系统日期和时间,使系统时间不断地得到跳转。另外该中断的中断处理函数除了更新系统时间外,还需要更新本地CPU统计数。指的是调用scheduler_tick递减进程的时间片,若进程的时间片递减到0,进程则被调度出去而放弃CPU使用权。具体情形如下图所示:

1.Linux的中断 在Linux的每个中断号都有自己的irq_desc_t描述符。所有的这些描述符组织在一起形成irq_desc数组,如下图所示:

现在我们再看看,当n号中断发生时,我们的中断处理函数是如何被启动的。在很多单片机系统中,每个中断向量都指向不同的中断服务函数。但是在Linux,大多的外部中断向量指向同一中断服务函数do_IRQ。

当我们使用request_irq在n中断号安装中断处理函数时,实际上是生成一个irqaction对象,该对象的中断服务例程指针指向我们的中断处理函数。然后把irqaction对象插到irq_desc数组第n个元素的action指向(irq_desc[n].action)的irqaction链表中的未尾。

中断产生后,内核代码将由中断向量导航到do_IRQ函数。在do_IRQ 调用函数__do_IRQ,并传入中断号n作为参数。在__do_IRQ函数把irq_desc数组的第n个元素action指向的中断处理函数全部启动,包括我们之前注册的中断处理函数,如 REF _Ref206988749 \h 图下所示。由此可知,在Linux实现了可以在同一个中断号中安装多个中断处理函数。也是说实现了中断复用。

在do_IRQ函数中调用__do_IRQ完成后,还要调用irq_exit作后步处理才返回。而irq_exit对我们的进程调度十分重要。

现在我们再分析一下Linux中断延迟。

所谓中断延迟是指从外部中断信号输入到目的中断处理函数开始执行第一条指令所经历的时间。从本小节上文中,我们知道:在Linux中断向量并不是直接指中断处理函数。这会对中断延迟会造成一定的影响。我们现在假设在理想情况下(中断没有被禁用,中断处理时没有被嵌套,中断号没有被复用)分析Linux的中断延迟。

从中断信号输入到中断处理函数的第一条指令执行,所经历的时间可以分为三段:从中断信号输入到do_IRQ函数的开始执行;从do_IRQ函数开始执行到找到目标irq_desc_t对象;到中断处理函数的第一条指令开始执行。我们分别对这三段时间加以分析。

由于中断向量是直接指向do_IRQ函数的,所以从中断信号输入到do_IRQ函数开始执行第一条指令的时间固定。这时,内核代码以中断号作为参数,调用__do_IRQ函数。在__do_IRQ函数,以中断号作为下标直接定位到目标中断处理函数对应的irq_desc_t对象。这段时间也是固定的。而这时,在该irq_desc_t对象下的action元素直接向我们中断处理函数所在的irqaction对象。也是在理想情况下Linux的中断延迟是固定的。

也是说,标准Linux的中断延迟不是硬件实时的。

然而在Linux的外部中断是不分优先级的,在执行中断处理程序时,也可能会被其它中断所嵌套而造成延缓,在网络繁忙而CPU主频不高的情况下尤其明显。另外在内核代码是有些执行点关中断的,这也会造成中断延迟的不确定性。

1. 调度器

Linux的进程调度器的主函数是schedule。在shedule函数中有两个重要变量:prev是当前正在使用CPU的进程;next是下一个将要使用CPU的进程。调度程序的一个很大的任务就是找到next。

schedule的主要工作可以分为两步。

l 找到next

1、schedule()检查prev的状态。如果不是可运行状态,而且它没有在内核态被抢占,就应该从运行队列删除prev进程。不过,如果它是非阻塞挂起信号,而且状态为TASH_INTERRUPTIBLE,函数就把该进程状态设置为TASK_RUNNING,并将它插入运行队列。这个操作与把处理器分配给prev是不同的,它只是给prev一次选中执行的机会。在内核抢占的情况下,该步不会被执行。

2、检查本地运行队列中是否有进程。如果没有则在其它CPU的运行队列中迁移一部份进程过来。如果在单CPU系统或在其它CPU的运行队列中迁移进程失败,next只能选择swapper进程,然后马上跳去switch_tasks执行进程切换。

3、若本地运行队列中有进程,但没有活动进程队列为空集。也就是说运行队列中的进程都在过期进程队列中。这时把活动进程队列改为过期进程队列,把原过期进程队列改为活动进程队列。空集用于接收过期进程。

4、现在可以在活动进程队列中搜索一个可运行进程了。首先,schedule()搜索活动进程队列的集合位掩码的第一个非0位。当对应的优先级链表不空时,就把位掩码的相应位置1。因此,第一个非0位下标对应包含最佳运行进程的链表。随后,返回该链表的第一个进程。值得一提的是,在Linux-2.6下这步能很短的固定的时间内完成。

这时next找到了。

5、检查next是否不是实时进程以及是否从TASK_INTERRUPTIBLE或TASK_STOPPED状态被唤醒。如果这两个条件都满足,重新计算其动态优先级。然后把next从原来的优先级撒离插入到新的优先级中。

也是说,实时进程是不会改变其优先级的。

l 切换进程

找到next后,就可以实施进程切换了。

1、把next的进程描述符第一部分字段的内容装入硬件高速缓存。

2、清除prev的TIF_NEED_RESCHED的标志。

3、设置prev的进程切换时刻。

4、重新计算并设置prev的平均睡眠时间。

5、如果prev != next,切换prev和next硬件上下文。

这时,CPU已经开始执行next进程了。

在Linux-2.6,schedule函数在寻找next时,其算法效率得到极大的提高,时间复杂达到O(1)。而schedule函数性能好坏最关键也就这里。整体来说,schedule的时间复杂度为O(1)。

1.1.2   内核抢占分析

1.    场景分析

现在我们分析这种情况:一个实时进程为了等待外部事件而进入休眠;当外部事件发生时产生中断而触发中断处理函数,并在中断处理函数中唤醒实时进程;当实时进程被唤醒后,占抢当前进程,获得CPU使用权,响应外部事件。下面我们看看当内核遇到这种情况时,内部是如何动作的,并由此考察标准Linux的抢占延迟。

假设实时进程是next,当前进程是current。

在next进程的内核态中,为了等待外部事件调用wait_event_interruptible使进程进入休眠,这时next进程就被加入到等待队列。调度程序则调度其它进程使用CPU,该进程暂时称为current。

当外部事件发生时,产生了中断信号,触发了中断处理函数。在中断处理函数中,调用wake_up_interruptible把next唤醒。在调用wake_up_interruptible函数时,传入参数是等待队列。wake_up_interruptible函数会试图唤醒等待队列中的所有进程。为了方便起见,这里假设等待队列中只有一个正处于等待状态的进程next。在wake_up_interruptible会调用try_to_wake_up试图把等待进程next唤醒。

在单CPU系统中,try_to_wakey_up把next插入到本地运行队列中。若next的优先级比当前进程current进程优先级高,则调用set_tsk_need_resched函数设置current进程内核堆栈中的TIF_NEED_RESCHED标志。该标志会强制把当前进程current调度出去。然后把next的运行状态改为TASK_RUNNING。也是说,当next被唤醒时,若next的优先级比current的优先级高,就允许抢占当前current进程;否则仅是把next插入运列队列和把运行状态改为TASK_RUNNING。

正如前面的所述,中断发生后,在do_IRQ中断服务函数调用__do_IRQ函数完成后会调用irq_exit函数作后步处理。irq_exit函数退出后,do_IRQ函数也退出。这是内核执行路径进入一段汇编代码。这里有一个内核抢占检查点,在x86平台该检查点的代码如下程序清单所示。

  1. need_resched:
  2. movl 0x8(%ebp), %ecx
  3. testb $(1<
  4. jz restore_all
  5. testl $0x00000200, 0x30(%esp)
  6. jz restore_all
  7. call preempt_schedule_irq
  8. jmp need_resched

如果当前进程current的TIF_NEED_RESCHED标志为0,说明没有需要切换的进程,因此,程序跳转到restore_all处。如果需要进行进程切换,就调用preempt_schedule_irq函数。在preempt_shedule_irq函数中,关掉本地中断和内核抢占,然后调用schedule()函数实施调度。如果next在运行进行队列中优先级最高且在其优先级中唯一,next进程获得CPU使用权。

下面有三个宏是值得注意的:

内核抢占的情形大致就是这样。其中,从实时程序被唤醒到该实时程序真正开始使用CPU的这一段时间就是抢占延迟。上述情形也告诉我们,只有内核在运行中断/异常程序时才会发生内核抢占。当然若中断被禁止或内核抢占被禁止,都不可能发生内核抢占。

  1. preempt_disable()
  2. preempt_enable_no_resched()
  3. preempt_enable()

preempt_disable是递减内核抢占标志。若该标志被递减到0内核抢占启用。

preempt_enable_no_resched是递增内核抢占标志。若该标志被递增到大于0内核抢占禁用。

preempt_enable不但递减内核抢占标志。它还在递减完成后,检查TIF_NEED_RESCHED标志是否被设置。若被设置,会检查内核抢占标志是否为0及是否允许中断。如果这些条件都为真,则调用schedule函数实施进程调度。当启用可延迟函数的时候,preempt_enable会被执行。

由此可见,内核抢占还会发生在启用可延迟函数的时候。

2.    抢占延迟分析

我们考察一下在标准Linux-2.6的抢占延迟的情况。

下面我们对抢占延迟一步一步进行分析。内核抢占过程的时间顺序如图下:

外部事件发生后,产生中断信号。这时由中断向量指向的do_IRQ 会马上被执行。在do_IRQ函数会执行__do_IRQ函数,并由此去执行我们的中断处理函数。在中断处理函数中,唤醒实时进程。这时,抢占延时开始计时。执行完成中断处理函数后,__do_IRQ函数退出,转而执行exit_IRQ函数。在exit_irq函数中,会检查系统中是否有挂起的软中断。如果有挂起的软中断,就执行挂起的软中断处理函数。软中断处理完成后,exit_irq退出,do_IRQ函数也退出。由于挂起软中断的数目不确定(虽然只执行小于10个)以及软中断处理函数的运行时间不确定,exit_irq函数的运行时间不可预测。然后内核执行路径进入一段汇编代码中,调用schedule函数实施调度。由于schedule的时间复杂为O(1),而且关中断和禁用抢占,schedule的执行时间可以确定。若实时进程的优先级为最高,实时进程就会被调度执行。这时,抢占延时计时结束。

也是说,在do_IRQ函数中,由于执行完中断处理函数后就处理软中断,而造成抢占延迟的不确定。还有,在处理软中断时,系统是不关中断的,而且处理软中断的时间比较长,被其它硬件中断嵌套的机会比大。这进一步影响了标准Linux内核抢占的实时性。

schedule-调度器相关推荐

  1. Linux 调度器内幕

    内核中这个非常重要的组件的最新版本改进了可伸缩性 M. Jones 2006 年 9 月 07 日发布 WeiboGoogle+用电子邮件发送本页面 2 本文将回顾一下 Linux 2.6 的任务调度 ...

  2. Cocos2Dx之调度器-欧阳左至

    有的时候我们还需要使用其他的时间触发机制,比如一个重复性动作2秒之后再执行,并且重复间隔为3秒.怎么实现呢? 通过前面的分析,我们知道每个帧间隔时间到期后,都会调用CCDirector的mainLoo ...

  3. xxl-job源码解读:调度器schedule

    xxl-job源码解读:调度器schedule 本文基于xxl-job的2.3.1版本 基本说明 基本原理概述 调用器主要的用于判断定时任务的执行时间,按时调用触发器(trigger),再由触发器去获 ...

  4. golang源码分析:调度器chan调度

    golang调度机制chan调度 golang的调度策略中,碰见阻塞chan就会将该chan放入到阻塞的g中,然后再等待该chan被唤醒,这是golang调度器策略的主动调度策略之一,其中还有其他的主 ...

  5. mysql存储过程结构体_八、mysql视图、存储过程、函数以及时间调度器

    1.create or replace view emp_view as select * fromt4 ;给t4表创建一个名为emp_view的视图2.drop viewemp_view 删除视图= ...

  6. mysql事件调度定时任务_详解MySQL用事件调度器Event Scheduler创建定时任务

    前言 事件调度器相当于操作系统中的定时任务(如:Linux中的cron.Window中的计划任务),但MySql的事件调度器可以精确到秒,对于一些实时性要求较高的数据处理非常有用. 1. 创建/修改事 ...

  7. [MySQL 5.1 体验]MySQL 事件调度器(Event Scheduler)

    作/译者:叶金荣(Email: ),来源:http://imysql.cn,转载请注明作/译者和出处,并且不能用于商业用途,违者必究. 一.概述 事件调度器是在 MySQL 5.1 中新增的另一个特色 ...

  8. mysql originator_MySQL中的事件调度器EVENT

    MySQL中的事件调度器,EVENT,也叫定时任务,类似于Unix crontab或Windows任务调度程序. EVENT由其名称和所在的schema唯一标识. EVENT根据计划执行特定操作.操作 ...

  9. JStorm与Storm源码分析(三)--Scheduler,调度器

    Scheduler作为Storm的调度器,负责为Topology分配可用资源. Storm提供了IScheduler接口,用户可以通过实现该接口来自定义Scheduler. 其定义如下: public ...

  10. Linux 调度器发展简述

    引言 进程调度是操作系统的核心功能.调度器只是是调度过程中的一部分,进程调度是非常复杂的过程,需要多个系统协同工作完成.本文所关注的仅为调度器,它的主要工作是在所有 RUNNING 进程中选择最合适的 ...

最新文章

  1. Kaggle经典数据分析项目:泰坦尼克号生存预测!
  2. [C++] 指向常量的指针 VS 指针类型的常量
  3. qt on android qml,Qt on Android: Qt Quick 之 Hello World 图文详解
  4. Angular ngTemplateOutlet 元素的学习笔记
  5. 赞扬精心设计:基于属性的测试如何帮助我成为更好的开发人员
  6. 关于HTML的盒子的一些小问题
  7. PostgreSQL 字符串分隔函数(regexp_split_to_table、regexp_split_to_array)  发表于 2020-06-01 |  阅读次数: 394
  8. 在SQL Server中批量复制,导入和导出的技术
  9. QT开发_弹出窗口禁用父窗口并移动到父窗口中心位置
  10. java 实现宠物领养_基于jsp的宠物领养-JavaEE实现宠物领养 - java项目源码
  11. Mac系统下 安装并使用DOSBox编写汇编语言
  12. 全面解读数据中台、数据仓库和数据湖
  13. 【Proteus仿真】波形信号发生器(4种波形可选,频率可调)
  14. 预充电电路工作原理_电动汽车电控系统预充电原理
  15. Unity3d发布WebGL打包AssetBundle的材质球丢失问题
  16. python实现最小二乘法
  17. PTA 数据结构 修理牧场
  18. 怎样才能保证单元测试效果
  19. 微信公众平台帐号迁移条件及流程
  20. C语言中和||的用法

热门文章

  1. 96PIN直插DIP千兆四口网络变压器 千兆交换机路由器网络滤波器
  2. 魔兽各服务器位置,魔兽世界怀旧服矿点分布位置介绍 全地图采矿位置一览
  3. 小程序 WeUI导入时导入失败,出现Component is not found in path 的错误
  4. Leetcode刷题 2021.02.15
  5. Deep Stream Ai落地--初体验
  6. bzoj:1922: [Sdoi2010]大陆争霸 (luogu 2446)
  7. 用Java来解析torrent文件
  8. el-input 输入框的正则
  9. Method of Four Russians 算法
  10. 【记录】关于知乎“国外博士的能力真的比国内博士强吗”的讨论