zoukankan      html  css  js  c++  java
  • CPU中断的工作原理

      前言

      中断的概念属于硬件层。虽然在进行软件编程时不会直接使用中断,但理解它对我们来说依然重要。

      在使用线程切换及状态管理、异常处理、硬件与处理器的交互、I/O操作等指令时,中断都在默默的服务。

      处理器基于硬件封装对外的指令集,底层语言封装指令集提供更加简单的抽象,高级语言基于底层语言赋予程序更明确的语义。可以看到在这条关系链条中,下层的变动会牵一发而动全身影响上层。而上层想要提高效率,改变机制也必须得到下层的支持。

      像 I/O 处理的不断演进,从占用CPU等待到通过中断阻塞等待到多路复用与异步,如果没有下层的支持上层是不可能实现的。而不了解下层的原理也很难真正理解一个机制的高效的原因。因此了解下层的原理对应用层工程师来说同样重要。

      什么是中断

      现代计算机具有多任务处理的能力,往往一台办公使用的普通计算机上都会同时运行着几十上百的任务(进程)。很难想象,点击一下鼠标,需要等待计算机的进程调度模块调度到鼠标处理任务后再做出响应,这是不可接受的。

      现实是在点击鼠标或键盘时(正如我现在在做的事情),计算机会立即给我反馈处理结果,计算机与我们之间是在进行实时交互的。而实时性的实现便是依赖了中断,中断是为了顺应人们对实时性交互的需求而产生的技术。中断之所以有用,是因为它会立刻停下当前的程序(软件)去做另外一件事。

      1956年,IBM 7049机器上首先使用了中断技术,并开始使用“中断”这一术语。

      为了了解中断的运作机制,需要理解下面这些概念:

      时钟周期

      虽然每个数字电路系统可能包含有组合电路,但是在实际应用中绝大多数的系统还包括存储元件,这样的系统叫做时序电路。时序电路是由最基本的逻辑门电路加上反馈逻辑回路(输出到输入)或器件组合而成的电路,与组合电路最本质的区别在于时序电路具有记忆功能。至于时序电路的相关概念和原理放到下一小节。现在只需要知道CPU便是一个大的时序电路。
      时序电路的输出并不只依赖与输入,往往还会依赖于上个周期的电路状态,因此往往会包含大量的存储单元。除此之外,经常要求他们在同一时刻同步动作。触发器和锁存器是相应时钟周期并协调整个电路动作一致性的关键,在每个存储单元电路上引入一个时钟脉冲(CLK)作为控制信号,只有当CLK到来时电路才被“触发”而动作,并根据输入信号改变输出状态。
      时序电路需要依靠时钟信号工作,时钟信号对时序电路来说就像人类的心跳,所有的动作都是跟随着心跳的节拍进行,心跳过快或者过慢都会产生严重的问题。对于时序电路来说,一次心跳所花的时间叫做时钟周期(从一次高电平到相邻最近一次高电平之间的间隔),时钟周期是时序电路最基本的时间单位。
      借用一副图片:
      

       时钟周期是由CPU时钟定义的定长时间间隔,是CPU工作的最小时间单位,也称节拍脉冲或T周期。

      时序电路

       看过上面的章节,缺少硬件基础又想要刨根问底的朋友肯定存在一些疑问,大概包含以下几个问题:

      1.为什么CPU要用时序电路,时序电路与普通逻辑电路有什么区别。

      2.触发器、锁存器以及时钟脉冲对时序电路的作用是什么,它们是如何工作的。

      带着这两个问题,从头了解一下逻辑电路。要了解逻辑电路,首先便要了解组成逻辑电路的基本单位:逻辑门。

      逻辑门

      逻辑门是数字电路组成的基本单元,它们的输出是它们输入位值的布尔函数。最常用的逻辑门便是我们熟知的与、或、非。

      

       对于与门,只有a、b输入都为1时,输出才为1。

       对于或门,输入a、b只要有一个为1,输出便为1。

       对于非门,若输入为1则输出为0,输入为0则输出为1。

      以上三种是最基本的逻辑门,可以通过它们的组合来实现复杂的逻辑推演,最简单的比如与或、异或等。再复杂的逻辑,都可以用最基本的逻辑的特定组合实现出来,就像应用层工程师靠有限的语法可以创造出近乎无限的应用一样。

      逻辑门总是活动的,一旦一个门的输入变化了,在很短的时间内,输出便会相应的变化。

      使用很多的逻辑门构建成一张网,便得到了一个实现的复杂逻辑的计算块(computational Block),称为组合电路。组合电路的构成遵循以下原则:

      1. 每个逻辑门的输入必须连接了一个系统的输入、某个存储单元的输出或某个逻辑门的输出(三选一)。

      2. 两个或多个逻辑门的输出不能连接在一起,否则可能产生互相矛盾的信号造成错误或电路故障。

      3. 这个网必须是无环的,也就是说不能有回路,否则会使网的计算逻辑有歧义。

      了解了基本的逻辑门和组合电路的概念,再介绍一下电路中的控制信号是如何实现的。

      控制信号

      再复杂的逻辑也可以靠最基本的逻辑组合而成,输入输出的控制逻辑也不例外。为了更好的理解控制信号是如何工作的,介绍一种非常有用的组合电路:位多路复用器。

      

       其中的 s 输入控制着两个与门。其中位于上方的与门的逻辑是 !s && b ,位于下方的与门的逻辑是  s && a 。

      也就是说当 s 的输入为 1 时,b的输入是无法到达或门的, 0&&b 恒为零。此时 a 的输入便是整个电路的输出。

      反之当 s 输入为 0 时,a 的输入是无法到达或门的 ,0&&a 恒为零。此时 b 的输入便是整个电路的输出。

      可以看到 s 输入的电位高低直接决定着 a、b 中哪个输入有效,这便是控制信号工作的一个典型例子。

      看到控制信号是如何工作的,假想一下简单的寄存器是如何依赖一个时钟控制信号工作的。一个时钟信号周期的由低电平变为高电平再由高电平变为低电平。

       以该信号作为寄存器的控制信号,寄存器内每一位输入位都与该信号进行与逻辑后再进入寄存器,那么可以想象,在时钟周期内的低电平部分,任何信号无法写入寄存器,等待时钟信号变为高电平时寄存器才变为可写入状态。这就是一个简单的触发器了,随着时钟信号周而复始的改变着自己的状态。

      当然真正的触发器的实现比这复杂的多,这里只是方便理解举了最简单的例子。

      明白了上述基本概念,看一下普通组合逻辑电路与时序电路的区别。

      普通组合逻辑电路与时序电路的区别

      1.毛刺容忍

      组合逻辑电路从本质上讲,不存储任何信息。它们只是简单的响应输入信号,产生符合输入的某个逻辑表达式结果的输出。

      而时钟电路是拥有自己的状态的,时序电路某一个状态除了依赖当前的输入外,还依赖电路的上一个状态。

      而想要电路拥有自己的状态并可以基于这个状态进行计算,必须在组合逻辑电路中引入存储设备和控制存储设备的周期性变化的时钟信号。

      引入存储设备是容易理解的,没有存储设备的电路当然是没有自己的状态的,因为它没有存储状态信息的载体,存储器就是存储电路状态信息的载体。

      而时钟周期的作用相对就不是那么容易理解,举个例子来理解时钟信号的作用:

      来看一个没有时钟信号的组合逻辑电路:

       前面说过,逻辑门总是活动的,一旦一个逻辑门的输入改变,则输出会在很短的时间内发生改变。

      但是需要注意的是,这个“很短时间内”的描述。由于元器件的质量/种类不同、路线的长度不同等物理因素的限制,不同的输入到达输出的时间是不同的。

      比如图中,c 输入到达下方的与门与到达上方的与门的两条路线中,到达下方与门的路线多出了一个非门。那么 c 信号到达上方的与门自然要比到达下方的与门的速度快。

       所以当 c 信号发生改变时,有一段时间内,F 端的输出是错误的,因为 A&&C 已经到达 F 端但 B&&!C 还没有到达,也就是说 F1 是比 F0 到达的慢的,存在延迟,如下图所示:

      这种情况被称为“毛刺”。

      虽然毛刺出现的时间是很短暂的,但是对于一个电路系统的输出来说却是致命的。如果在发生毛刺的时间内将错误的输出写入存储器,接下来的逻辑会一错再错并让人摸不到头脑。

      而时序电路则不会出现上述问题,将A/B/C的输入到F的输出看作一个完整的动作,在一个时钟周期内完成。那么,A/B/C的输入将在时钟沿触发,F也将在时钟沿采集结果。而在采集结果时,F的输出已经跨越了毛刺处于稳定状态。当然,这样时一个钟周期内高电平持续的时间必须足够使 F 输出达到稳定状态。

      这样,下一个动作(发生在下一个时钟周期)如果基于 F 输出,将得到正确的结果。这是时序电路与普通逻辑电路的区别之一:对毛刺的容忍。

      可以看到,通过时钟周期,组合逻辑电路中输入的变化可以看作一个一个的动作。而在一个时钟周期内,电路完成一个最基本的动作,保证下个时钟周期的动作可以获取正确的电路状态。

      如果无法理解电路按动作的意义,看一个非常简单的例子:

    int a=0;
    int b=a;

      需要将 a=0 执行完后,执行 b=a 才有意义。a=0没有执行完成或未执行时,b=a 的执行完全没有意义。这就是程序按指令运转的重要性,正如电路按动作运转的重要性。

      时钟周期将一个一个的动作隔离开来,确保每个动作在执行时,上一个动作已经完全执行完成了。而存储器则记录电路的状态,每个动作的执行结果放在存储器中供下个动作使用。

      这正是CPU所需要的,CPU执行一条一条的指令正可以看作一个一个的动作(当然这里并不是指的一条指令,CPU的基本动作是比指令更加细化的单位,尤其是在流水线的引入之后。指令正是由一个个基本的动作构成的,这些基本动作指的是取指令/指令译码/指令执行/访存/写回/PC增加等等)。

      时钟周期像人类的心跳,CPU随着时钟节拍快速又有条不紊的运行。正如前面所说,一个时钟周期必须足够CPU完全完成耗时最长的基本动作,时钟周期对于不同的CPU来说并不是固定的,确定一个CPU的时钟周期也是一个非常复杂的任务。

      2.支持反馈逻辑

      如果要实现一个计数器,如果用非时序电路实现是这样的:

      上述电路是完全无法使用的,电路的下一个输出依赖电路现在时刻的状态,除了上一节所述的毛刺现象会造成结果的不可预计外,电路本身的逻辑存在死循环。

      要支持反馈逻辑,必须使用寄存器将结果暂存起来,由时钟沿控制数据的反馈更新。

     

      说完了时序电路的特性,看看时序电路如何组成处理器。

    时序电路构成处理器

      可以看到,一个最基本的处理器是这样一个电路:

      1. 可以完成逻辑的运算。

      2. 电路需要有自己的状态。

      3. 每一个输出除了基于输入和处理逻辑外,还需要基于当前电路的状态。

      时序电路可以很好的满足上述特性。对于时序电路来说,时钟脉冲便是电路的心跳,而寄存器是协同整个电路按心跳节拍运转的动脉瓣。

      大多数时候,寄存器处于一种稳定状态,产生的输出等于它的当前状态。信号沿着寄存器前面的组合电路传播。这时产生一个新的寄存器输入,但当当前时钟脉冲处于低电位时,寄存器的输出仍保持不变。直到时钟脉冲变为高电位,输入信号便写入到寄存器中,成为下一个状态。直到下一个时钟上升沿,寄存器的状态和输出都不会发生改变。

      电信号畅通无阻的在组合电路中传播,而寄存器就成为这种传播的屏障。只有在每个时钟的上升沿时,信号才可以通过寄存器进入下一个组合电路。

      而一个个的组合电路执行着不同的动作,对于整个电路而言,时钟脉冲与寄存器的配合使得电路在每个动作执行完成后才会执行下一个动作。处理器在一个时钟周期内,执行完一个动作并把状态更新到寄存器。直到下一个时钟周期再执行下一个动作,此时上个动作已经完全执行完成了,而电路的最新状态也已经通过寄存器传播到了负责当前动作的电路中来。

      换个角度,时钟周期保证了每个周期结束时,这个周期内的输入已经完整的转化为了输出。而这个输出保存在寄存器内供下个周期的动作使用。时钟周期和寄存器的配合将电路要执行的动作与动作之间隔离开来。一个一个动作有条不紊的执行,周而复始。

      上面便是一个最简单的处理器结构,左边标识了每部分电路对应的动作。

      可以使一个时钟周期内执行完成整个指令执行(上述所有动作),这样下一个时钟周期执行下一条指令时可以保证上条指令可以执行完成。虽然这样时钟周期会长到让人难以接受,但它保证了指令流的正常流转。

      可以想象,这样一个完整的过程是从读PC计数器数值并取指令开始的。PC计数器中的数值造成了后面一系列电路状态的变化,在PC不改变时,电路处于一个稳定的状态,也就是完整执行完一个指令的状态。

      而当PC计数器一旦发生改变,将引起整个电路的新一轮的状态改变。指令执行的最后一个动作便是改变PC计数器,这样在下一个时钟周期,整个电路将执行新的指令。

      或者可以将负责各个动作的电路间用寄存器隔离开来,一个时钟周期内只执行一个动作而不是一条指令,这样可以大大加快电路的整体效率。事实上流水线便是这样做的,为了更高的效率,许多流水线的层级非常深,一个取指/译码/执行三个动作可能被拆分成十五个甚至更多个动作。这样一个时钟周期内,就可以处理多条指令(当然它们处于不同的动作阶段)。

      典型的流水线简图如下:

       用寄存器将负责每一个动作的模块隔离开来,然后将时钟周期设为每个模块刚好可以向本模块寄存器写入数据的时间(而不是信号从头传播到尾的时间)。这样一个时钟周期内,每个模块都执行一次完整的动作。在单个时钟周期内,每个模块在服务不同的指令,而不是所有模块服务同一个指令(如果这样则每个模块只在高电平持续时间的一小块时间内工作)。在单位时间内,整个逻辑电路服务的指令总数大大增加,也就是吞吐量得到了增加

      因为增加了电路的复杂性,对于一条指令而言,从头走到尾所需的时间变长了,但对整个电路而言,吞吐量增加了。这便是流水线机制的意义。

      我们在试图理解流水线的动作时,不要将关注点放在逻辑电路上,而要将关注点放在寄存器值的变化上。因为组合逻辑电路不受时钟信号影响,仅负责信号的传播,真正依赖时钟信号的是寄存器的写入行为。我们的目标便是寄存器的值随着时钟周期发生正确的变化。当组合逻辑A前的输入(也就是PC寄存器值)发生变化后,每过一个时钟周期,该变化便依次传递到后面模块的寄存器中。

      流水线听起来很完美,但也存在一些缺陷。比如很难将各个模块的延迟变为一致的,整个电路的速度将受限于最慢的模块。时钟周期必须大于最慢模块的整体计算时间,这就给其它模块带来了延迟。另外,流水线的层级也并非是越深越好。随着流水线层架的加深,寄存器的增多将导致整体电路延迟的增加,当层级到达一定深度时,该延迟占用总计算时间的比例增大,造成收益的减小。

      指令如流水一样进入处理器,而不是一条指令执行完成后下一条指令才进入处理器。虽然将指令的执行拆分成多个小动作会带来许多麻烦,比如流水线冒险,但其带来的吞吐量及缩短时钟周期的收益是值得花费精力来解决这些麻烦的。

      一个最基本的处理器的实现需要组合逻辑电路和两种存储设备:时钟寄存器(程序计数器和指令状态寄存器)和随机访问存储器(指令内存/数据内存和寄存器文件)。

      组合逻辑不需要任何时序或控制,只要输入变化了,值就通过逻辑门网络传播。

      那么还有四个硬件需要用时序控制:程序计数器/指令状态寄存器/数据内存和寄存器文件。因为时序控制的都是写入操作,而指令内存不需要写入操作,所以也不需要时序控制。

      时钟脉冲控制着上述四个元器件的写入操作。时钟信号触发将值写入到指令状态寄存器和随机访问存储器。

      处理器真的是一个非常宏大的话题,笔者能力极其有限,只能尽量的从非常宏观的角度上描述一下对处理器的认识(依然很吃力),如果有疑问欢迎评论区讨论。

      机器周期

      而机器周期是CPU执行一个基本动作所需要的最小时间。在计算机中,为了便于管理,常把一条指令的执行过程划分为若干个阶段,每一阶段完成一项工作。例如,取指令、存储器读、存储器写等,这每一项工作称为一个基本操作。完成一个基本操作所需要的时间称为机器周期。一般情况下,一个机器周期由 若干个S周期(状态周期)组成。通常用内存中读取一个指令字的最短时间来规定CPU周期,(也就是 计算机通过内部或外部总线进行一次信息传输从而完成一个或几个微操作所需要的时间)),它一般由12个时钟周期(振荡周期)组成。

      指令周期

      指令周期是取出一条指令并执行这条指令的时间。一般由若干个机器周期组成,是从取指令、分析指令到执行完所需的全部时间。指令周期类型有非访内指令的指令周期、取数指令的指令周期、存数指令的指令周期、空操作指令和转移指令的指令周期。需要注意的是,不同指令有不同的指令周期。

      对中断响应的理解便是建立在这些基础的概念上的。CPU对中断的响应并不是靠中断引脚的电平发生了改变,CPU便立即放下手头的事情取做中断源分配的任务,其中还包含着识别中断源、找到中断程序、保存当前任务的各个寄存器状态、进入中断处理程序后的返回等一系列复杂的操作。对于外部中断,CPU在执行当前指令最后一个时钟周期去查询INTR引脚,若查询到中断请求信号有效,同时在系统开中断(即IF=1)的情 况下,CPU向发出中断请求的外设回送一个低电平有效的中断应答信号,作为对中断请求INTR的应答,系统自动进入中断响应周期(即中断响应指令的指令周期)。

      如果认真阅读了上一章节,那么我们可以理解检查中断引脚对CPU来说也是由一个特定的组合电路模块完成。该模块属于流水线的一部分,处在整个电路的末端,每个时钟周期都在服务着不同的指令。

      而检测到中断信号后,上述的识别中断源、找到中断程序、保存当前任务的各个寄存器状态、进入中断处理程序后的返回等操作是由中断隐指令来完成的。

      中断隐指令

      中断隐指令引导CPU在响应中断信号时随机做出的一系列动作,这些动作是在检测到中断信号后便随即发生的,因而不能由软件来完成,而是由硬件来处理。中断隐指令并不是指令系统中的一条真正的指令,它没有操作码,所以中断隐指令是一种不允许、也不可能为用户使用的特殊指令。其所完成的操作主要有:

      保存现场

      为了保证在中断服务程序执行完毕能正确返回原来的程序,必须将原来程序的断点(即程序计数器(PC)的内容)保存起来。断点可以压入堆栈,也可以存入主存的特定单元中。

      暂不允许中断

      暂不允许中断即关中断。在中断服务程序中,为了保护中断现场(即CPU主要寄存器的内容)期间不被新的中断所打断,必须要关中断,从而保证被中断的程序在中断服务程序执行完毕之后能接着正确地执行下去。并不是所有的计算机都在中断隐指令中由硬件自动地关中断,也有些计算机的这一操作是由软件(中断服务程序)来实现的。但是大部分计算机还是靠硬件来进行相关动作,因为硬件具有更好的可靠性和实时性

      引出中断服务程序

      引出中断服务程序的实质就是取出中断服务程序的入口地址送程序计数器(PC)。对于向量中断和非向量中断,引出中断服务程序的方法是不相同的。

      中断分发

          硬件中断处理。在Windows所支持的硬件平台上,外部I/O中断进入到中断控制器的一根线上。该控制器接着在某一根线上中断处理器。处理器一旦被中断,就会询问控制器以获得此中断请求(IRQ)。中断控制器将该IRQ转译成一个中断号,利用该编号作为索引,在一个称为中断分发表(IDT)的结构中找到一个IDT项,并且将控制权传递给恰当的中断分发例程。每个处理器都有单独的IDT,所以,如果合适,不同的处理器可以运行不同的ISR。

      下面借用教材上几句话:

      1.不可以由软件去保护断点,因为当中断以后,cpu的PC值已经被改变了 软件访问不到原来的PC值;但是可以由软件去保护现场,而且好像很多cpu确实也这么做的;恢复断点当然是软件去做的,硬件又不知道你什么时候想返回

      2.软件的是顺序执行的,至少是有规律的;但是中断本身就是一个无法预料的事情,程序无法预料何时发生中断,何时执行中断处理函数,所以只能在某个地址写上一段中断处理代码,等中断到来时由硬件将程序强行跳转到你的中断处理函数去处理中断。

                                                    ---摘自《计算机组成原理(蒋本珊)》

  • 相关阅读:
    eclipse插件egit安装使用
    github 项目版本控制
    div box container随主体内容自动扩展适应的实现
    持续构建与每日集成
    Xshell连接Linux下Oracle无法回退的解决办法
    Java Data JPA +hibernate 保存或者是查询遇到的坑
    C#控件DropDownList下拉列表默认打开
    window.open居中显示
    CSV文件转JSON
    Vue自定义事件,$on(eventName) 监听事件,$emit(eventName) 触发事件
  • 原文地址:https://www.cnblogs.com/niuyourou/p/11907858.html
Copyright © 2011-2022 走看看