操作系统原理_从零开始自制操作系统(13):中断系统原理

0021b9030a221742bbb3a458bd332ad0.png

本节主要讲解中断的原理,下一节中实现中断控制模块

1.中断简介

(1)如何构造一个多任务处理的系统

在讲解中断之前,先理解监听与回调的概念(学过Java的同学对这个应该不会陌生)。监听与回调指示了一个对象对于一个事件的处理的方式,监听是处理者不断监视是否有对应事件发生,当有事件发生时就采取相应的处理事件的措施,回调是事件发生后通过通过事件主动调用目标对象处理方法来处理事件的方式(这也属于一种通信方式)

举个形象一点的例子。小明期末考试后一直在等自己的期末考试成绩出来。他有两种方式来获得成绩。(1)小明不断通过查询考试成绩网站来查看成绩是否出来了,如果成绩已经被录入了,小明可以获取自己的成绩,并且采取相应的处理措施(考得好就问爸妈要点钱,考的不好就准备好被爸妈一顿批),我们管这种方式叫做 监听。 (2)老师为了防止学生隐瞒成绩,在成绩出来后直接通过短信的方式通知小明的父母,小明父母收到信息后直接采取相应的处理措施,我们管这种方式叫做 回调。

话说回来,监听/回调与中断有啥关系呢?其实,监听/回调也是处理多任务的一种方式,而中断是多任务处理中回调方式的具体实现。

我们知道,在计算机中管理的外围设备很多,管理起来非常复杂,并且这种管理的过程本身就是多任务的。因为外设输入的不确定性,处理器需要一种机制,当外设有输入请求时立即处理这种请求。为什么要立即处理外设请求呢?如果不立即处理的话,举个例子,键盘必须要等到计算机处理完正在进行的任务时才能输入。另一方面,诸如网卡这一类的设备,存在一个缓冲区,也就造成了网卡数据是有声明周期的,如果不立即处理,后边来的网络数据会由于缓冲区满了而导致前边的数据丢失。

显而易见,监听/回调的方式都是可以达成这一目的的

  • 假设我们使用监听的方式,我们可以将计算机执行的所有程序每隔10行设置一个检测外围设备信号的代码(注意不能够间隔某一时间来设置,因为时钟的信号是建立在中断系统之上的),显然,这种方式可以实现处理外围设备请求。但是,由于添加了监听代码,对CPU的计算资源开销比较大,并且也给程序设计带来了困难(你想每写十行代码就加个监听函数的调用吗?)
  • 不用多说,对构造这样一个系统,回调的方式才是最好的,有外围设备信号时,直接通过一种机制来调用CPU(如果抽象的够好的话,cpu可以看作一个可调用函数,我们只管往这个函数填充任务序列作为参数)执行处理的代码即可。

(2)中断概念的引入

实际上,直接采用回调的方式,CPU是不支持的,因为CPU是个只管读IP寄存器并且执行指令的傻子。

so,中断就是为了解决这个问题产生的。

中断的执行流程其实很简单:在CPU接受到事件的“回调”指令时,会通过中断来打断正在进行的任务的执行,并且将目前的一些任务数据保存起来,然后CPU会跳转执行中断处理的函数,当中断处理函数执行完毕后,CPU又会把刚刚保存的任务数据恢复,并且紧接着执行刚刚的任务

而中断打断CPU的执行是CPU本身支持的功能。

2.中断分类

首先来说一下中断的分类。中断由于产生的方式、处理方式以及一些细节问题上有所不同,所以可以分为以下两类

(1)外部中断

第一类的外部中断是指CPU之外的一些硬件设备向CPU发出的中断信号(所以也可以叫做硬件中断 or 硬中断,当然也有叫做软中断的,但这两者不是对立关系,计算机通过这种中断来管理外围设备以及处理一些突发情况。

举个比较典型的外部中断应用:计算机中都是有与定时器相关的芯片的,在x86中一般使用8254芯片,8254会以一定的频率向CPU发出中断信号(每一次发出中断信号成为一次“tic”),并且8254是可编程的,所以可以通过程序控制芯片发出的tic频率,这样,在预知频率以及tic次数的前提下,可以计算出所计时的时长。实际上,计时器是多线程的基石之一(我们在后边的多线程实现章节会讲到),但是计时器是以中断为基础的,所以, 多线程是靠中断来驱动的。

但是,外部中断也可以分为两类

  • 可屏蔽中断:这一类中断是可以屏蔽的,并且屏蔽过后不会对计算机底层运行造成巨大影响(但是对于上层软件可能就会有影响了),这种中断主要是一些外围设备产生的中断,比如,网卡向CPU发出的中断就是可屏蔽中断,网卡收到网络数据后会通过中断来通知CPU取出相关的数据,但是我们可以通过编程来使CPU可以屏蔽并且无视这种中断信号,并且不会出现系统宕机等情况(顶多就是没网了~)
  • 不可屏蔽中断:与上边相对应的一类中断是不可屏蔽的,主要是系统遇到了硬件层面的问题,比如计算机突然断电,对于这种情况的中断信号,CPU是不可无视的 ,主要的原因是CPU遇到这种情况后是无法正常执行接下来的程序。

实际上,外围设备数量是很多的,所以相应的外部中断的种类也很多,但是,真正的与CPU相连接的外部中断信号线只有两根(单根8bits带宽,可传输256种中断)。

f895efc677899f9f7a74dd519f99e8fc.png
外部中断
图中INTR表示可屏蔽中断,NMI(Non-Maskable Interrupt)表示不可屏蔽中断

由于,中断是否屏蔽是程序编写者主观上的动机,所以,计算机必须提供相应的编程方式来控制,也就是下边所说的IF位控制

回顾一下之前的内容,计算机中有EFLAGS寄存器,这个寄存器可以存放程序执行的一些状态,比如可进位加法的进位标志

b3ab6471de90df566862b853e9f9b2e5.png
eflags寄存器结构

IF全称interrupt enable flag,通过字面意思可以看出这个是控制中断的,IF位为1表示可以接收可屏蔽中断,为0表示不接收可屏蔽中断。

x86提供了控制IF位指令:

STI       //start interrupt 设置IF为1,打开中断
CLI       //close interrupt 设置IF为0,关闭中断

*扩展:其实,除了这两个指令,也可以直接操作eflags位来控制,由于eflags不能直接改变位,可以通过将目标eflags值push入栈,再通过pop eflags的方式改变寄存器值

MOV EAX,EFLAGS         //获得eflags值
AND EAX,0xFFFFFFFF     //通过改变这个值可以控制位
PUSH EAX
POP EFLAGS         

(2)内部中断

内部中断是指CPU执行代码时引起的中断,由于是CPU引起的,所以没有中断信号线发送中断信号。

根据中断产生原因的不同 ,内部中断被分为两类:

  • 软中断:软中断是程序主动发起的中断,这种中断大多数是为了实现程序的一些实时的系统功能需求
  • 异常:异常中断是程序被动发起的中断,并且会产生错误的结果导致CPU无法继续执行指令。
再举两个例子区分软中断与异常,当用户需要立即知道计算机运行的时间时,可以通过使用time函数来获取,实际上,time函数中触发了一个定义好的软中断,这个软中断通知CPU打断执行接下来的代码,并且获取内核中记录的时间信息,最后返回到用户代码中。这个过程,是程序主动发出的中断,所以是软中断。当CPU遇到类似100除以0这样的代码时,由于这样的写法是错误的,所以会触发异常中断产生(程序被动产生的中断)。 实际上软中断与异常是没有明确界限定义的,某些中断是由程序主动产生,但是又具有错误的执行结果,这样就有了这两种中断的共同属性,并且 异常中断也分为三种:Fault/Trap/Abort,这三种中断的区别是产生中断后的结果不同,Fault类型异常是可以修复的,Trap是CPU引导的中断(比如调试类型中断int3,CPU通过这个中断来完成程序断点调试),Abort类型异常是比较严重的,一般会导致程序崩溃并且无法修复这个异常。

中断的类型比较多,所以我做了个图来便于大家理解

908bf8d8ac2da59de2dfd60eaf5d3b0f.png

(3)中断编号

中断的数量比较多(想象一下你的电脑控制多少外围设备),并且种类也比较多,所以为了识别以及管理中断,每种中断都对应了一个中断号,如下表:

fd567bdad5b0ed6b7ed2ffb3efa5f0d1.png
中断编号表
重点解释下这张表,2号中断表示了 不可屏蔽中断,0到19号中断中除了2号中断,其余都是内部中断中的异常中断, 但是0-19号中断也有共同点:他们都是CPU自动编号的中断,我们是无法通过编程来改变这些中断对应的中断事件的,20-31号中断时系统保留给操作系统开发人员使用的中断,并且是内部中断中的软中断,我们使用这些中断号来进行 系统调用程序的编写(由于只有这种中断能够程序自发且无故障地执行代码),32-255号中断时用来编号可屏蔽中断的,我们可以为每个外围设备编号一个在这个范围内的中断号,实际上这个范围内中断是使用专用中断控制芯片(在x86中一般是可编程8259A)来完成中断号映射以及优先级排序等等一系列操作。

*3. 8259A简介(只做了解)

*注:由于8259A内容比较多,考虑到繁琐性,此处只作为了解,后续8259A编程时可不做为重点

我们现在知道了,只有外部中断是通过中断信号线与CPU连接的,不可屏蔽中断由于中断数量少,可以直接与CPU相连,但是可屏蔽中断数量非常多,不可能为每个中断都预留一个中断信号线,并且外围中断可能同时触发,比如我可以一边使用键盘一边移动鼠标,这个时候键盘与鼠标控制器会同时发出中断信号,由于单核CPU只能同时执行一个中断处理程序,所以我们有必要为这些可屏蔽中断定义一个优先级,来管理这些中断的处理顺序。这个时候8259A就登场了。

9057d78b3db20e485affc93f55de146a.png
8259A一端与CPU相连,另一端可以最多连接8个中断设备。并且可以使用级联的方式,让另一片8259A连接到第二个通道上,并且最多可以级联成64级的向量优级中断系统。

使用8259A组成的控制系统可以实现1(CPU)对N(中断源)的中断信号处理。计算机对于硬件的控制一般是使用读写硬件的控制寄存器来完成的,8259A也可以使用这种方式来控制中断的优先级以及是否屏蔽等问题(除了使用CLI指令可以屏蔽所有中断以外,对中断控制器的编程可以屏蔽对应的单个中断)。

4.中断是如何被处理的?

中断的处理过程实际上非常简单。不知道大家知不知道一个概念叫做:执行流。

执行流表示了现在计算机需要执行的代码序列,一般来说,如果不改变EIP寄存器的内容的话,CPU就会按照EIP指向的内存中的指令顺序地向下取指令来执行。

在中断时,我们需要切换到中断代码的执行中去,这种切换实际上就是执行流的切换。这样的切换过程只需要计算机提供简单的改变EIP的指令即可(实际上,JMP之类的指令就可以改变执行流了)。However,在改变执行流之前,我们必须切换上下文。上下文就是CPU正在处理任务的一些关键性信息。对于计算机来说,上下文就是各个寄存器的值。

我们来看看中断处理过程全貌:

当中断产生时,我们需要将现在的上下文存储起来,并且把目标任务(中断处理程序)的上下文填充入寄存器中,再改变执行流到中断处理程序中。这样CPU就会执行这段处理程序,在处理程序的出口处,设置另一个切换上下文以及执行流的代码块,这样我们可以把原先存放起来的上下文填充回来,并且改变执行流,继续执行中断之前的任务

98732269e326cbbddc22eb67c2f58383.png
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值