精华内容
下载资源
问答
  • 文章目录第八章 中断系统8.1 中断的基本概念8.1.1 中断概念的引入及描述中断方式示意(以输入中断为例)**中断**的定义8.1.2 中断源及中断分类中断的分类8.1.3 中断类型码中断类型码中断向量中断向量表中断向量表的...

    文章目录

    第八章 中断系统

    分三次课讲述、各次的重点为

    • 中断的分类(知道了解)以及中断向量(计算,知道中断向量的引导过程,操作系统响应中断的过程)
    • 微机系统中的可屏蔽中断,中断管理器8259(工作过程,自己语言描述,8259级联,pc机中时两片管理15级,从接在主的IR2上)

    8.1 中断的基本概念

    8.1.1 中断概念的引入及描述

    查询方式当外设数据未准备好时,CPU需要不断查询外设数据是否准备好的状态,效率低

    中断方式示意(以输入中断为例)

    现行程序输入接口输入设备1.启动2.cpu执行现行程序3.数据4.中断请求5.中断响应6.中断现行程序并执行中断服务子程序7.数据9.结束子程序返回现行程序现行程序输入接口输入设备CPU采用中断方式与外设交换信息(以输入为例)

    中断的定义

    中断:CPU在执行程序的过程中,由于某种外部或内部事件的作用,使CPU停止当前正在执行的程序而转去为该事件服务,待事件服务结束后,又能自动返回到被中止了的程序中继续执行的过程。

    • 被中断的源程序称为主程序
    • 中断处理程序称为中断服务子程序
    • 主程序被中止的地方,称为断点,也就是下一条指令所在内存的地址

    8.1.2 中断源及中断分类

    中断源:是能够引发CPU中断的信息源,分为两大类。

    • (CPU)外部中断源(硬件中断源)通过I/O接口发中断
      • I/O设备 如键盘、显示器、打印机
      • 数据通道 如磁带、磁盘
      • 时钟 如8254 0#,由此引发的中断
      • 故障源 如掉电、存储器奇偶校验错误
    • (CPU)内部中断源(软件中断源、异常)
      • 执行INT软件中断指令 如执行指令INT 21H
      • CPU指令执行产生的异常 如被0除、单步执行

    中断的分类

    中断根据中断源可以分为:

    • 由外部事件所引发的中断,即由CPU以外的设备发出,并由CPU的中断请求信号引脚输入所引发的中断称为外部中断,也称为硬件中断(80x86中 INTR 可屏蔽硬件中断,NMI非屏蔽硬件中断
    • 由CPU内部事件,即由CPU硬件故障程序执行中的指令所引发的中断称为内部中断。内部中断可以进一步分为软件中断和异常
      • 执行有定义的INT N指令而引发的中断称为软件中断。软件中断可分为BIOS中断、DOS中断。DOS中断又分为DOS专用中断、DOS保留中断、用户可调用的DOS中断以及保留给用户开发的中断。
      • 由于CPU本身故障、程序故障等引发的中断,称为异常
    中断
    外部中断
    INTR 可屏蔽硬件中断
    NMI 非屏蔽硬件中断
    内部自动产生中断类型码
    内部中断
    异常
    软件中断
    BIOS中断
    DOS中断
    DOS专用中断
    DOS保留中断

    外部中断和异常的发生是随机的。

    常见的异常

    • 除法错中断——当除法结果溢出或者除数为0时,发生的中断。
    • 单步或陷阱中断——执行每条指令后,如果标志寄存器的T标志(陷阱标志)为1时,则产生中断。
    • 断点中断——CPU执行“INT 3”指令后,进入相应的中断服务程序。INT 3指令通常用于调试程序时设置程序断点。
    • 溢出中断——CPU执行“INT 4”指令,或者标志寄存器O标志(溢出标志)为1时,执行“INTO”指令,进入相应的中断服务程序。

    8.1.3 中断类型码

    中断类型码

    为了区别各种不同的中断,微机系统给每一个中断分配了一个中断号n,即中断类型码,其取值范围是0~255。微机系统可以处理256种中断,在这256个中断中,intel 在它各种微处理器中都保留了前32个(0—31)为系统所专用,后224个可由用户设定。

    常用中断号 对应中断
    0型中断 除法错中断
    1型中断 单步或陷阱中断
    2型中断 非屏蔽硬件中断
    3型中断 断点中断
    4型中断 溢出中断
    5型中断 屏幕打印
    08H~0FH型中断 可屏蔽硬件中断
    10H~1FH型中断 BIOS中断(10H,16H)
    20H~3FH型中断 DOS中断(21H)

    中断优先级

    中断向量

    ⭐️**中断向量是实模式下,中断服务子程序的入口地址。**它由两部分组成:

    • 服务程序入口的偏移地址 2个字节(4n~4n+1)
    • 服务程序所在代码段的段基址 2个字节(4n+2~4n+3)

    例:若四个单元内容为11H,22H,33H,44H,则入口地址为:(中断向量表的表地址与中断类型的关系)
    2211H  4433H2211H+44330H=46541H 偏移地址:2211H\ \ 段基址:4433H\\ 2211H+44330H=46541H

    中断向量表

    中断类型码通过一个地址指针表中断服务程序的入口地址相联系。

    • 实模式下:该表称为中断向量表
    • 保护模式下:该表称为中断描述符表。
    中断向量表的设置

    CPU规定:在实模式下,中断向量表需设置在系统的RAM最低端的1k单元(000000H~003FFH)256(28)×4=210=1k256(2^8)\times4=2^{10}=1k

    n4n4n+3n型中断向量存放在4n\sim4n+3

    中断向量表的初始化

    谁(DOS,BIOS,用户)设计(开发)的程序,由谁将它写入中断向量

    DOS设计的两个专门用于中断向量的读出与写入的代码

    INT21H 35H子功能 25H子功能
    功能 读出n型中断向量 写入n型中断向量
    入口 AL=中断类型码 DS=中断服务程序所在代码段的段基址
    DX=中断服务程序入口的偏移地址
    AL=中断类型码
    出口 ES:BX=n型中断向量
    将中断向量写入中断向量表
    ;方法一、用户自己编写程序填写中断向量
    CLI;关中断
    PUSH DS;程序会破坏DS内容,所以先将DS保存
    MOV AX,0000H
    MOV DS,AX;给DS赋值
    MOV BX,4*n
    MOV AX,OFFSET SERVICE
    MOV [BX],AX;写入程序起始地址的偏移地址,这里需要使用DS间址寻址,省略了段基址
    MOV AX,SEG SERVICE
    MOV [BX+2],AX;写入段基址
    POP DS
    STI:开中断
    ;采用方法2,使用DOS自带的子程序,对应的程序段如下:
    CLI
    PUSH DS
    PUSHA
    MOV AX,SEG SERVICE
    MOV DS,AX;写入段基址
    MOV DX,OFFSET SERVICE;写入偏移地址
    MOV AH,25H
    MOV AL,n
    INT 21H
    POPA
    POP DS
    STI
    
    
    中断响应和处理过程

    微机系统各种类型中断的响应和处理过程不完全相同,主要区别在于中断类型码的获得方式不同,当CPU获得了中断类型码后的处理过程基本类似。

    中断类型 中断类型码
    非屏蔽硬件中断请求 CPU内部会自动产生中断类型码2
    可屏蔽硬件中断请求 当CPU处于开中断状态时,由外部中断控制器将相应的中断类型码送给CPU
    异常 中断类型码也是自动形成的
    INT n指令 中断类型码即为指令中给定的n

    CPU获得了中断类型码n后,中断的处理过程如下:

    1. F寄存器→栈(保存中断处理之前的F状态)使F中的
      • T标志置0——禁止单步操作
      • I标志置0——CPU处于关中断状态
    2. 断点地址→栈
      1. 先:断点基地址(CS) →栈,
      2. 后:断点偏移地址(IP)→栈
    3. CPU从4n ~4n+3单元取出n型服务程序入口地址→IP:CS,从而转入n型中断服务程序。
    4. 服务程序执行完毕,执行中断返回指令。中断返回指令的功能是按顺序恢复断点处的IP值、CS值和之前保护的相应中断前的标志寄存器内容→标志寄存器。CPU根据恢复后的CS:IP返回断点,继续执行主程序。
    中断向量的引导作用

    在这里插入图片描述

    中断服务子程序的结构
    ISR PROC
     ;保护现场
     ;中断处理
    
     ;恢复现场
     ;中断返回是IRET弹出六个字节,RET不能实现要求
    ISR ENDP
    

    8.2 多级中断管理

    1.中断优先与中断分级

    当有多个中断源在同一时刻提出请求时,CPU对中断响应的次序称中断优先级。(中断响应的次序是用排队器硬件实现的,可以由程序控制改变实际的中断处理次序)前文提到的中断类型码

    80X86响应中断的优先级如下:

    中断类型优先级
    1 除法错中断最高
    2 软件中断INT n ↓
    3 断点中断↓
    4 溢出中断INTO↓
    5 NMI中断↓(非屏蔽)
    6 INTR中断↓(可屏蔽)
    7 单步中断最低

    2.禁止中断与中断屏蔽

    • 禁止中断:产生中断请求后,CPU不能中断现行程序的执行。
    • 中断屏蔽:用程序有选择地封锁部分中断,而允许其余部分仍可得到响应。
    • 软件中断与硬件中断甚至可以“相互中断”

    3.中断嵌套

    中断嵌套:在执行中断服务程序时,仍可再响应中断申请。

    4.中断系统应具备的基本功能

    • 对于硬件中断,接口电路应具备‘屏蔽’和‘开放’的功能,这种功能由程序员通过软件去控制。
    • 能实现中断判优(中断排队),当有多个中断源提出请求时,应能优先响应高级别的中断源。
    • 能够实现中断嵌套
    • 响应中断后,能自动转入中断处理,处理完毕能自动返回断点。

    8.3 80x86的中断指令

    1. 开中断指令STI:指的是CPU可否响应中断请求,STICLI只对可屏蔽硬件中断INTR有用
      • 功能:使F寄存器中I标志置1,CPU处于开中断状态。
    2. 关中断指令CLI
      • 功能:使F寄存器中I标志置0,CPU处于关中断状态。
    3. 软件中断指令INT n
      • n为中断类型码,n为0~255之间有定义的无符号整数。功能:无条件转向n型中断服务子程序。
    4. 中断返回指令IRET: IRET 是中断服务子程序的出口指令
      • 功能:依次从栈顶弹出6个元素→IP,CS,F。如果栈顶是INT n 的断口地址,则执行IRET后,返回断点,否则不能。SP会变大(加六)
    5. 溢出中断指令INTO
      • 功能:先判别F寄存器中O标志位是否为1,如是则直接调用类型为4的中断子程序,用以处理溢出中断。

    INT n指令的执行过程

    与中断响应和处理过程的1.2.3步一致

    ❌软件中断指令 INT n,n是0~255之间的任意无符号整数。❎

    8.4 中断控制器8259A

    8.4.1 8259A功能

    1. 一片8259A中断控制器可以管理8级中断
    2. 每一级中断都可以通过设置内部屏蔽字进行屏蔽或允许。
    3. 在中断响应周期,8259A可以向CPU提供相应的中断类型码。
    4. 8259A是很复杂的中断控制器,可以通过编程从中断触发方式、中断屏蔽方式、中断优先级管理方式、中断结束方式和总线连接5个方面对中断进行管理。

    8259A主从级联

    2片8259A通过级联,采用1主1从的方式,可管理15级中断。

    通过级联,采用1主8从的方式,可扩展管理64级中断

    8.4.2 8259A结构

    8259A内部结构

    1. 中断请求寄存器( IRR)
      寄存引脚IR0~IR7的中断请求信号,IRRi位置1,表明IRi引脚上有了中断请求信号
    2. 中断屏蔽寄存器(IMR)
      寄存程序员写入的中断屏蔽字,屏蔽字某位=1(IMRi位=1),则与该位对应的中断请求信号(IRRi位)就不能送到中断优先权电路
    3. 优先权电路(排队电路)
      1. 比较同时送达优先权电路的中断请求,哪一个级别最高。
      2. 比较CPU正为之服务的中断源和刚进入优先权电路的中断源,哪一个级别更高。
      3. 通过判优‘选中’其中级别最高的中断源,然后通过控制电路,从INT端向CPU提中断请求。中断控制器8259A内部结构
      4. 中断控制电路
      5. 中断服务寄存器(ISR)
        1. 通过判优电路IRR0位的请求被选中,8259A向CPU发中断请求,通过INTA\overline{INTA}收到第一个中断响应信号后,ISR0位置1,IRR0位置0。ISR0位置1,表明CPU正在准备(或正在)执行IR0的服务程序。
        2. 反之,如果ISR0位由1 →0,表明IR0的中断服务程序执行完了所以ISR的每一位都是响应中断源的中断服务标志位。中断控制器8259A内部结构
    4. 数据总线缓冲器
      • 完成与CPU数据线配接
      • 接收初始化命令字,操作命令字
      • 当收到第二个中断响应脉冲时,通过他们向CPU送出被选中的中断源的中断类型码n
    5. 读/写控制模块
    6. 级联/缓冲比较器

    控制逻辑电路

    在这里插入图片描述

    8259A外部中断

    在这里插入图片描述

    中断控制器8259A中断过程——CPU响应可拼比硬件中断的过程

    1. 首先由中断请求寄存器寄存加到引脚IR0IR7IR_0\sim IR_7的中断请求
    2. 在中断屏蔽寄存器的管理下,没有被屏蔽的中断请求被送到优先权电路判优
    3. 经过优先权电路的判别,选中当前级别最高的中断源,然后从引脚INT向CPU发出中断请求信号
    4. CPU满足一定条件后,向8259A发出2个中断响应信号(负脉冲)
    5. 8259A从引脚INTA\overline{INTA}收到第1个中断信号之后,立即使中断服务寄存器中与被选中的中断源对应的那位置1,同时把中断请求寄存器中的相应位清0
    6. 从引脚INTA\overline{INTA}收到第2个中断响应信号后,8259A把选中的中断源类型码n,通过数据线送往CPU
    7. 在实模式下,CPU从4×n4×n+34\times n\sim 4\times n +3单元取出该中断源的中断向量IPCS\longrightarrow IP 、CS,从而引导CPU执行该中断源的中断服务程序

    8.4.3 8259A的中断管理方式

    8259A中断管理方式
    中断触发方式
    边缘触发
    电平触发
    中断屏蔽方式
    常规屏蔽方式
    特殊屏蔽方式
    中断优先级管理方式
    完全嵌套方式
    特殊嵌套方式
    优先级循环方式
    常规EOI循环方式
    自动EOI循环方式
    特殊EOI循环方式
    中断结束方式
    自动EOI方式
    非自动EOI方式
    常规EOI方式
    特殊EOI方式
    总线连接方式
    缓冲方式
    非缓冲方式

    中断触发方式

    • 边沿触发IRiIR_i出现上升沿表示有中断请求)
    • 电平触发IRiIR_i出现高电平表示有中断请求)

    80X86微机中采用边沿触发方式

    屏蔽中断源方式

    • 常规屏蔽方式
      MR屏蔽字决定了允许或禁止某位IRiIR_i所对应的中断:IMi=1IM_i=1禁止,IMi=0IM_i=0允许
    • 特殊屏蔽方式
      • 提供了允许较低优先级的中断能够得到响应的特殊手段
      • 当一个中断被响应时,仅屏蔽同级别的再次中断,而允许较低和较高级别的中断源中断正在执行的当前中断服务程序
        80x86采用常规屏蔽方式

    中断优先级管理方式

    优先级控制方式

    1. 固定优先级方式
      • 所有中断请求IRiIR_i的中断优先级固定不变
      • 加电后8259A的默认方式,默认优先级顺序从高到低为IR0IR7IR_0\sim IR_7
    2. 循环优先级方式
      • 中断源轮流处于最高优先级,即自动中断优先级循环
      • 某中断请求IRiIR_i被处理后,其优先级别自动降为最低,原来比它低一级的中断上升为最高级

    中断嵌套方式

    在中断处理过程中允许被更高优先级的时间所中断称为中断嵌套。8259A有两种中断嵌套方式:

    • 普通全嵌套方式(默认方式)
      一中断正被处理时,只有更高优先级的时间可以打断当前的中断处理过程而被服务
    • 特殊全嵌套方式
      一中断正被处理时,允许统计或更高优先级的事件可以打断当前的中断处理过程

    80x86采用固定优先级、普通全嵌套方式

    中断结束处理方式

    当某一IRiIR_i中断被服务时,ISR中的相应位ISRi=1ISR_i=1.当服务结束后,则必须清零该ISRiISR_i。当服务结束后,则必须清零该ISRiISR_i位。使ISRi=0ISR_i=0是通过向8259A发出中断结束命令(EOI命令)实现的。

    三种EOI命令

    • 自动EOI(AEOI)1——(自动EOI方式)
    • 指定EOI(SEOI)2——(特殊EOI方式)
    • 非指定EOI(NSEOI)3——(正常/常规EOI方式)

    80x86微机中采用NSEOI(常规中断结束)方式

    8259A的编程

    20H是最常用的中断结束命令字

    8.5.2 非屏蔽中断

    ⭐️非屏蔽中断:输入到CPU的NMI引脚的请求信号引发的中断

    ⭐️响应非屏蔽中断的条件

    1. 有非屏蔽中断请求,没有DMA请求
    2. 一条指令执行完

    CPU响应非屏蔽中断的过程

    CPU在每一条指令的最后一个时钟周期,检测NMI引脚。处理器不屏蔽来自NMI的中断请求。
    处理器在响应NMI中断时,不从外部硬件接收中断向量号。在80X86中,非屏蔽中断所对应的中断向量号固定为2。为了非屏蔽中断的嵌套,每当接受一个NMI中断,处理器就在内部屏蔽了再次响应NMI,这一屏蔽过程直到执行中断返回指令IRET后才结束。所以,NMI处理程序应以IRET指令结束。

    8.5.3 可屏蔽中断

    可屏蔽中断:输入到INTR引脚的中断请求信号,引发的中断

    硬件中断的级别:DMA请求>非屏蔽中断>可屏蔽中断

    1.响应可屏蔽中断的条件

    1. 有可屏蔽中断请求,没有DMA请求,没有非屏蔽中断请求
    2. CPU一条指令执行完毕
    3. CPU处于开中断状态(I标为1)

    2.CPU响应可屏蔽中断过程

    ​ CPU在每一条指令的最后一个时钟周期,检测INTR引脚,当检测到有可屏蔽中断请求时,在满足上述条件的前提下,通过总线控制器向系统8259A发出中断响应信号(2个负脉冲)。在获得8259A送来的中断类型码之后,在实模式下查询中断向量表,从而转向相应中断源的中断服务程序。

    3.PC系列机可屏蔽中断的硬件结构

    1.使用2篇8259级连,管理15级中断
    2.中断源与中断类型
    主8259 中断源 中断类型 从8259 中断源 中断类型
    IR0 日时钟 08H IR0 实时时钟 70H
    IR1 键盘 09H IR1 用户中断 71H改向0AH
    IR2 从8259 IR2 保留 72H
    IR3 辅串口 0BH IR3 保留 73H
    IR4 主串口 0CH IR4 保留 74H
    IR5 并行口2 0DH IR5 协处理器 75H
    IR6 软盘 0EH IR6 硬盘 76H
    IR7 并行口1 0FH IR7 保留 77H
    3.系统分配的8259口地址
    中断屏蔽寄存器口地址 接收中断结束命令的寄存器口地址
    主8259 21H 20H
    从8259 0A1H A0H
    4.硬件可屏蔽中断的中端级别
    5.中断结束命令
    1. 命令字为20H
    2. 接入主8259A IR0 ,IR1,IR3IR7~的中断源,其服务程序结束要向主8259送中断结束命令字
    3. 接入从8259的中断源,经主8259A的IR2向INTR提中断请求,其服务程序结束应分别向主、从8259各送一个中断结束命令字 。
    ;接入主8259A  IR0,IR1,IR3 ~IR7的中断源其服务程序结束要向主8259送中断结束命令字
    MOV  AL,20H
    OUT  20H,AL
    ;恢复现场
    IRET
    ;接入从8259的中断源,经主8259A的IR2向INTR提中断请求,其服务程序结束应分别向主、从8259各送一个中断结束命令字 。
    MOV    AL,20H
    OUT    20H,AL
    OUT    0A0H,AL
    ;恢复现场
    IRET
    

    8.6 微型计算机系统中用到的中断及应用举例

    8.6.1 日时钟中断

    BIOS系统规定:40H:6CH~40H:6FH这4个单元
    (共32位)为日时钟计数器,每55ms加1次,计数
    到:001800B0H,为24小时,其计数值供系统软件使用。
    系统启动时CPU执行BIOS中的一段程序,读取CMOS实
    时时钟电路的时间值→计数值→40:6CH~40:6FH做
    为日时钟计数器的计数初值。

    1. 中断源:系统8254 0#计数器,每55ms发一次中断请求
    2. 中断类型:08H
    3. 日时钟中断处理流程:(CPU转入8型中断后,完成下列工作)
      1.开中断,保护现场(DS压栈)
      2.40H给DS,对“日时钟级数器”加1
      3.测算软驱马达关闭时间
      4.执行 INT 1CH
      5.向主8259送中断结束命令
      6.恢复现场DS,IRET

    关于1CH服务程序(软件中断)

    • 8# 服务程序,每隔55ms在DS=40H的前提下,调用一次1CH服务程序,之后又返回8型
    • 所以1CH中断是日时钟的附加中断,用户可开发新的1CH中断服务程序(完成每55ms一次的定时操作)取代系统原来的中断服务子程序

    软件中断和硬件中断可以相互嵌套他们之间的优先级是指当中断请求同时发生时高优先级的中断优先响应,不存在低优先级无法打断高优先级的说法

    系统日时钟中断的开发应用

    1. 置换中断向量,(08H或1CH)
      • 当用户程序的某项定时操作,其定时周期等于55ms的整数倍时,可定义用户程序的定时操作为1CH中断。此时需要置换1CH型中断向量,调用DOS系统25H号子程序把用户定时中断服务程序入口地址写入4×1CH~4×1CH +3单元。
      • 当用户程序的定时操作,其定时周期不等于55ms的整数倍或者小于55ms时,需要重新对8254 0#计数器进行编程,定义输出周期为用户定时周期与55ms的最大公约数,此时用户中断服务程序为08H型,此时需要置换08H型中断向量,调用DOS系统25H号子程序把用户定时中断服务程序入口地址写入4×08H~4×08H+3单元。
    2. 用户中断服务程序结束
      • 若用户定时中断定义为1CH型,服务程序结束前不需要向主8259A送结束命令。
      • 若用户定时中断定义为08H型, 服务程序结束前,需要先向主8259A送中断结束命令
    3. 中断服务程序的执行时间
      • 定时中断服务程序的执行时间,必须远远小于定时中断的时间间隔
    4. 避免DOS重入(DOS不允许重入)

    8.6.2 键盘中断

    可屏蔽硬件中断

    1. 键盘中断源是主板键盘接口电路
    2. 中断类型:09H

    8.6.3 实时中断

    1. 中断源是主板中的实时时钟电路
    2. 中断类型:70H

    8.6.4 用户中断

    1. 系统用户中断的中断源是系统的ISR总线**B4**端子(IRQ9)引入的中断请求信号,接到从8259的IR1经过INT接到主8259的IR2端,通过INT接到INTR
    2. 中断类型71H(转向0AH)
    3. 系统用户中断处理流程BIOS设计的71H型中断服务程序处理流程如下:
      1. 开中断、保护现场;
      2. 向从8259A发出中断结束命令
        MOV AL, 20H;OUT 0A0H, AL
      3. 执行INT 0AH,转向0AH服务程序。
        0AH服务程序是用户预先设计好的,其中断向量已经存放在系统RAM 4×0AH~4×0AH+3单元。

    用户中断是微机系统为用户开发可屏蔽中断预备的中断口。在用户中断程序的设计中,需要采取以下措施:

    1. 把外扩中断源的中断请求(由低电平到高电平的跃变)接入ISA总线B4端子
    2. 开放用户中断
    3. 置换中断向量

    CPU响应用户中断后,自动转向“71H型”服务程序。根据BIOS设计的71H型中断服务程序处理流程,可以知道,用户自行设计的用户中断服务程序类型可以是“71H”,也可以是0AH。

    1. 定义用户中断服务程序为0AH型,置换0AH型中断向量,调用DOS系统25H号子程序把用户中断的服务程序入口地址写入4×0AH~4×0AH+3单元。

    2. 定义用户中断服务程序为71H型 ,置换71H型中断向量,调用DOS系统25H号子程序把用户中断的服务程序入口地址写入4×71H~4×71H+3单元。

    用户中断请求的途径

    • 用户中断请求从ISA总线B4端子(IRQ9)引入,经过主8259,从8259二级中断管理,最后由主8259向CPU提中断。

    • 只有从8259IMR1置0,主8259IMR2置0,其中断请求方能送到CPU。 (日时钟中断和键盘中断是开放的)

    ;实现用户中断必须对主、从8259应用编程,保证中断申请由8259中断控制器提向CPU
    IN        AL,   0A1H
    AND       AL,   11111101B
    OUT       0A1H, AL      ; 从8259
    
    IN        AL,   21H
    AND       AL,   11111011B
    OUT       21H,  AL        ;开放主8259中断
    

    1. 在第2个INTA#结束时,由8259A使ISR~i自动复位;⭐️因不保留当前正在服务的中断的状态,故AEOI不能用于中断嵌套方式 ↩︎

    2. 由CPU发出一条SEOI命令,该EOI命令中指出了所要复位的ISR的位号。⭐️用于特殊屏蔽方式 ↩︎

    3. 由CPU发出正常EOI命令没,该EOI命令使ISR~i=1的位中优先级最高的那一位复位。⭐️用于普通全嵌套方式 ↩︎

    展开全文
  • MTK ACC Mode配置使用ACCDET2引脚做中断源------之进制转换超详细通俗讲解

    在做 MTK 方案的时候,修改有关 ACC 模块的时候,配置宏的时候,需要添加进制代码,因为进制是比较重要的东西所以单独拿出

    来给大家说说怎么掌握好进制,希望大家在看完本篇文章之后 能更游刃有余的掌握进制以及进制的转换在日常的软硬交互、C/S交

    互开发中,或指针、进制都充当着不可或缺的角色,那怎么样掌握好进制以及进制之间的转换呢

    博主今天就带你玩转进制,让你很轻松的就能掌握,博主将按照最简单的方法配备图文,让你更加加深对进制的理解


    开篇前,我们来想想,何谓进制?通俗的说就是逢几进一,比如十进制,就是逢十进一八进制,就是逢八进一,十六进制,就是逢

    十六进一,然而很多这样的进制都在我们的日常生活中充当着必不可少的角色和组成部分

    比如:我们的数学算术,采用的是国际标准,十进制,计算机的计算,显示都是十进制,除了计算机机器语言(二进制语言)和汇编语

    言(变相的二进制英文代替语言,C的最原始语言)手机时钟、秒表、手表等,采用的是  60 进制,逢 60 进一,除了ms,也就是秒后

    面的微秒年月日的月份采用的就是 十二进制,这些用到进制的案例都数不胜数,所以我们作为一位现代开发者,掌握进制是很重要

          十六进制要加 0x表示,例如:0xA1,0x13,0xFF
          十进制直接表示,例如:1,2,3,4,5,6
          八进制要加零表示,例如:00,01,02,03,04,05


    八进制是 0-7 ,十进制是 0-9 ,唯独十六进制比较独特,我们看看它的对应关系

        /**
         * 十六进制对应关系
         * 
         * 0 → 0
         * 1 → 1
         * 2 → 2
         * 3 → 3
         * 4 → 4
         * 5 → 5
         * 6 → 6
         * 7 → 7
         * 8 → 8
         * 9 → 9
         * A → 10
         * B → 11
         * C → 12
         * D → 13
         * E → 14
         * F → 15
         * 
         * */

          ① 十六进制


          我们就以上述图片浅蓝色框里面的几个进制数开篇吧,分别拆分开来,并且换算成十进制表示形式


          0x500 1 0x1F0 0x800 0x20 ,首先我们去掉 0x 表示符号,对进制数字逐一进行分解


    0x500 = 500  =  5*16+ 0*161  + 0*160  = 1280

    0x1F0 = 1F0  =  1*16+ 15*161 + 0*160  = 496

    0x800 = 800  =  8*16+ 0*161  + 0*160  =2048

    0x20 = 20 =  2*161 + 0*160  =32

    1 = 0x01


    然后在给大家通俗一下十六进制的表示形式的意义,如下图:


    我这样解释不知道你们是不是理解了,我就拿一个例子来更细化更详细的讲吧,就此如0xA1这个数,我们先去掉0x表示符号,那

    剩下的就是A1了,因为A是代表10的,因为10没有满16所以不能进一,但是又不能直接写一个10,所以在计算机里用木字母A来

    表示10,以此类推,还可以更通俗的理解成 A是十位数,1是个位数,1没有满16,所以不能进一,故个位数是1,然后十位数是

    10,因为没有满 16 ,故不能进一位,所以写 10 ,但是在计算机里面 写10 是不规范的,而且计算机或语言开发者为了方便计算

    就限定了十六进制9以上16以下 的数字用字母代替,表示位数一,即代表一位数字


    因为在开发中,用的最多的就是十和十六进制,所以本篇重点只讲了十和十六进制,反正大致逻辑和关系都表里如一,只是表现

    方式不一样和进制位不一样罢了,整体思想都一样


    还有就是给大家一个小技巧来验证进制转换是否有误,如下代码:

        public static void main(String args[]) {
        	
            // h:十六进制
        	int x = 0x1F0;
        	
            // d:十进制
        	int y = 16;
        	
            // o:八进制
        	int z = 015;
        	
        	System.out.println(String.format("以十进制输出0x1F0为:%o", x));
        	System.out.println(String.format("以十进制输出0x1F0为:%d", x));
        	System.out.println(String.format("以十进制输出0x1F0为:%h", x));
        	System.out.println("**************************************");
        	System.out.println(String.format("以十进制输出0x1F0为:%o", y));
        	System.out.println(String.format("以十进制输出0x1F0为:%d", y));
        	System.out.println(String.format("以十进制输出0x1F0为:%h", y));
        	System.out.println("**************************************");
        	System.out.println(String.format("以十进制输出0x1F0为:%o", z));
        	System.out.println(String.format("以十进制输出0x1F0为:%d", z));
        	System.out.println(String.format("以十进制输出0x1F0为:%h", z));
        	
        }

    输出的测试结果:

    以十进制输出0x1F0为:760
    以十进制输出0x1F0为:496
    以十进制输出0x1F0为:1f0
    **************************************
    以十进制输出0x1F0为:20
    以十进制输出0x1F0为:16
    以十进制输出0x1F0为:10
    **************************************
    以十进制输出0x1F0为:15
    以十进制输出0x1F0为:13
    以十进制输出0x1F0为:d


    相信看了这边博客,想必你已经对进制掌握已经相当熟练了吧?那还等什么,赶紧去练练手呗!谢谢观博!

    展开全文
  • 1.中断结构系统的中断源 2.特殊功能寄存器三个主要位置及功能 3.中断的初始化以及中断服务函数 ———————————————————————————————————————— 中断结构系统的中断源 中断...

    导航

    1.中断结构系统的中断源
    2.特殊功能寄存器三个主要位置及功能
    3.中断的初始化以及中断服务函数
    ————————————————————————————————————————

    中断结构系统的中断源

    中断源 符号 类型 产生条件 中断请求标志
    外部中断0 INT0 外部 P3.2/INTO引脚,低电平或者下降沿 IEO
    定时/计数器0 T0 内部 T0的计数器满模 TF0
    外部中断1 INT1 外部 P3.3/INTO引脚,低电平或者下降沿 IE1
    定时/计数器1 T1 内部 T1的计数器满模 TF1
    串行I/O端口 TI/RI 内部 串口发送完一帧数据,串口接收一帧数据 TI / RI

    特殊功能寄存器三个主要位置及功能

    1.中断允许控制寄存器 IE(Interrupt Enable)

    功能:控制各中断源的打开与关闭
    字节地址为0xa8,各个位都分配地址,进行位访问

    中间有2个未定义功能,6个有功能的
    分别为

    EA 全局中断允位
    ES 串行I/O口中断允许位
    ET1 定时/计数器1中断允许位
    EX1 外部中断1允许位
    ET0 定时/计数器0中断允许位
    EX0 外部中断0允许位

    赋值0为关闭,1为打开

    0xaf-0xa8位置:EA X X ES ET1 EX1 ET0 EX0

    复位后统一变为0x00

    例1:单片机使用外部中断0,定时中断0,如何设置IE

    IE => 10000011B ,为0x83 EX0 ,ET0,EA置1,其余置0

    例2:外部中断0,外部中断1,和串行中断,怎么设置

    EX0 = 1 EX1 = 1 ES = 1 EA = 1

    开放中断的规则:先开启局部中断,再开启总中断也就是EA


    2.定时器控制寄存器 TCON(Timer Control Register)

    位地址0x88开始的
    位置0x8f-0x88:TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0

    复位后用0x00表示

    TF1 TR1:定时/计数器1
    TF0 TR0:定时/计算器0
    IE1 IT1:外部中断1
    IE0 IT0:外部中断0

    IT i:是外部中断的触发方式选择位

    IT i = 0 //外部中断采用低电平触发
    IT I=1 //外部中断采用下降沿的方式触发

    IE i :置1后结果:

    若开放外部中断(EX i = 1且 EA = 1),CPU会自动进入对应的外部中断服务函数中去执行中断服务


    3.中断的优先级寄存器 IP(Interrupt Priority) 中断优先级

    地址位从0x68开始的
    位置0x6f-0x68:X X X PS PT1 PX1 PT0 PX0

    PS 串行优秀级控制位
    PT1 定时/计数器1的中断优先级控制位
    PX1 外部中断1的中断优先级控制位
    PT 0 定时/计数器0的中断优先级控制位
    PX 0 外部中断0的中断优先级控制位

    置1位高优先级,0为低优先级

    若是同级向cpu发出中断,优先级排序高->低为:

    (外中断0)IE0 (定时/计数器中断0)TF0 (外中断1)IE1 (定时/计算器中断1)TF1 (串行中断)T2/R2

    若是同时开放T0 T1 INT0 顺序 高到低为:

    INT0 T0 T1

    若是设置T1,INTO,T0顺序响应,怎么编写?

    PT1设置为1,其余不设置,按照原本设置就是INTO TO
    PT1 = 1; //另外两个省略


    中断的初始化

    1.选择外部中断方式 IT0 = 0(低电平) 或者 IT0 = 1 (下降沿)
    2.设置中断的优先级 Px0 = 0(低优先级) 或者 Px0 = 1(高优先级)
    3.开局部中断 EX0 = 1
    4.总中断 EA = 1

    void main(void)
    {
    	IT0 = 1;  //外部中断0 ,下降
    	Px0 = 1; //外部中断0 采用高优先级
    	Ex0 = 1;  //开局部中断
    	EA = 1;   //开全局中断
    }
    

    中断服务函数:

    void 函数名(void) iterrupt n [using m]

    其中iterrupt 和 using 关键字
    n 对应的是中断号 根据中断源分配中断号
    m 对应的寄存器组 (一般选择1-3,0是复位后自动选择的)

    中断源 中断请求标志 中断号
    外部中断0 IE0 0
    定时/计数器0 TF0 1
    外部中断1 IE1 2
    定时/计数器 TF1 3
    串行发送 TI 4
    串行接收 RI 5

    using m:寄存器组间

    m CPU使用的工作寄存器组间 R0-R7的地址
    0 第0组 0x00-0x07
    1 第1组 0x08-0x0f
    2 第2组 0x10-0x17
    3 第3组 0x18

    注意点:
    中断服务函数不是通过调函数来实现的
    应在函数中做好初始化

    展开全文
  • Linux代码阅读——中断

    千次阅读 2014-02-24 09:24:29
    Linux代码阅读——中断 目录 为什么要有中断 中断的作用中断的处理原则 Linux 中断机制 中断控制器中断描述符中断数据结构中断的初始化内核接口 中断处理过程 CPU 的中断处理流程保存中断信息处理...
    
    

    Linux源代码阅读——中断

    目录

    1. 为什么要有中断
      • 中断的作用
      • 中断的处理原则
    2. Linux 中断机制
      • 中断控制器
      • 中断描述符
      • 中断数据结构
      • 中断的初始化
      • 内核接口
    3. 中断处理过程
      • CPU 的中断处理流程
      • 保存中断信息
      • 处理中断
      • 从中断中返回
      • 编写中断处理程序
    4. 软中断、tasklet与工作队列
      • 上半部与下半部
      • 软中断
      • tasklet
      • 工作队列

    1 为什么要有中断

    1.1 中断的作用

    处理器的运算速度一般要比外部硬件快很多。以读取硬盘为例,如果是简单的顺序执行,CPU 必须等待很长时间,不停地轮询硬盘是否读取完毕,这会浪费很多 CPU 时间。中断提供了这样一种机制,使得读取硬盘这样的操作可以交给硬件来完成,CPU 挂起当前进程,将控制权转交给其他进程,待硬件处理完毕后通知 CPU,操作系统把当前进程设为活动的,从而允许该进程继续执行,处理读取硬盘的结果。

    另一方面,有些事件不是程序本身可预见的,需要硬件以某种方式告诉进程。例如时钟中断为定时器提供了基础,如果没有时钟中断,程序只能每执行几条指令就检查一下当前系统时间,这在效率上是不可接受的。

    从广义上说,中断是改变 CPU 处理指令顺序的硬件信号。分为两类:

    • 异步的:在程序执行的任何时刻都可能产生,如时钟中断
    • 同步的:在特殊或错误指令执行时由 CPU 控制单元产生,称为异常

    1.2 中断的处理原则

    中断处理的基本原则就是“快”。如果反应慢了,数据可能丢失或被覆盖。例如键盘按键中断,所按下的键的 keycode 放在 KBDR 寄存器中,如果在中断被处理之前用户又按了一个键,则 KBDR 的值被新按下的键的 keycode 覆盖,早先按下的键对应的数据就丢失了。

    当一个中断信号到达时,CPU 必须停止当前所做的事,转而处理中断信号。为了尽快处理中断并为接收下一个中断做好准备,内核应尽快处理完一个中断,将更多的处理向后推迟。

    为达到“快”这一目标,内核允许不同类型的中断嵌套发生,即在中断处理的临界区之外可以接受新的中断。这样,更多的 I/O 设备将处于忙状态。

    2 Linux 中断机制

    2.1 中断控制器

    中断控制器是连接设备和 CPU 的桥梁,一个设备产生中断后,需要经过中断控制器的转发,才能最终到达 CPU。时代发展至今,中断控制器经历了 PIC(Programmable Interrupt Controller,可编程中断控制器) 和 APIC (Advanced Programmable Interrupt Controller,高级可编程中断控制器) 两个阶段。前者在 UP(Uni-processor,单处理器) 上威震四方,随着 SMP (Symmetric Multiple Processor,对称多处理器) 的流行,APIC 已广为流行并将最终取代 PIC。

    8259A (PIC) 管脚图

    上图中的管脚说明:

    • IR0~IR7 (Interrupt Request0~7,用于连接设备)
    • INT (连接 CPU,当有中断请求时,拉高该管脚以通知 CPU 中断的到来)
    • INTA (连接 CPU,CPU 通过该管脚应答中断请求,并通知 PIC 提交中断的 vector 到数据线)
    • CS (片选,用于将两个 8259A 串联成可连接 15 个设备的 PIC)

    8259A 中的寄存器:

    • ICW: Initialization Command Word,初始化命令寄存器,用于初始化 8259A
    • OCW: Operation Command Word,操作命令字,用于控制 8259A
    • IRR: Interrupt Request Register,中断请求寄存器,共 8bit,对应 IR0~IR7 八个中断管脚。当某个管脚的中断请求到来后,若该管脚没有被屏蔽,IRR 中对应的 bit 被置1。表示 PIC 已经收到设备的中断请求,但还未提交给 CPU。
    • ISR: In Service Register,服务中寄存器,共 8bit,每 bit 意义同上。当 IRR 中的某个中断请求被发送给 CPU 后,ISR 中对应的 bit 被置1。表示中断已发送给 CPU,但 CPU 还未处理完。
    • IMR: Interrupt Mask Register,中断屏蔽寄存器,共 8bit,每 bit 意义同上。用于屏蔽中断。当某 bit 置1时,对应的中断管脚被屏蔽。

      arch/x86/kernel/i8259_32.c 中通过位运算来开启和关闭中断。

       63 void disable_8259A_irq(unsigned int irq)
       64 {
       65         unsigned int mask = 1 << irq;
       66         unsigned long flags;
       67 
       68         spin_lock_irqsave(&i8259A_lock, flags);		// 用 spinlock 锁住
       69         cached_irq_mask |= mask;				// 将 IRQ 的相应位置1,屏蔽中断
       70         if (irq & 8)
       71                 outb(cached_slave_mask, PIC_SLAVE_IMR);	// IR2 管脚负责 8259A 的级联(见下图),为0时使用主片,为1时使用从片
       72         else
       73                 outb(cached_master_mask, PIC_MASTER_IMR);
       74         spin_unlock_irqrestore(&i8259A_lock, flags);	// 解开自旋锁
       75 }
      
       77 void enable_8259A_irq(unsigned int irq)
       78 {
       79         unsigned int mask = ~(1 << irq);
       80         unsigned long flags;
       81 
       82         spin_lock_irqsave(&i8259A_lock, flags);		// 用 spinlock 锁住
       83         cached_irq_mask &= mask;				// 将 IRQ 的相应位置0,开启中断
       84         if (irq & 8)
       85                 outb(cached_slave_mask, PIC_SLAVE_IMR);	// IR2 管脚负责 8259A 的级联(见下图),为0时使用主片,为1时使用从片
       86         else
       87                 outb(cached_master_mask, PIC_MASTER_IMR);
       88         spin_unlock_irqrestore(&i8259A_lock, flags);	// 解开自旋锁
       89 }
      

    PIC 的每个管脚具有优先级,连接号码较小的设备具有较高的中断优先级。

    在 PIC 默认的 Full Nested 模式下,通过 PIC 发起中断的流程如下:

    • 一个或多个 IR 管脚上产生电平信号,若对应的中断没有被屏蔽,IRR 中相应的 bit 被置1。
    • PIC 拉高 INT 管脚通知 CPU 中断发生。
    • CPU 通过 INTA 管脚应答 PIC,表示中断请求收到。
    • PIC 收到 INTA 应答后,将 IRR 中具有最高优先级的 bit 清零,并设置 ISR 中对应的 bit。
    • CPU 通过 INTA 管脚第二次发出脉冲,PIC 收到后计算最高优先级中断的 vector,并将它提交到数据线上。
    • 等待 CPU 写 EOI (End of Interrupt)。收到 EOI 后,ISR 中最高优先级的 bit 被清零。如果 PIC 处于 AEOI 模式,当第二个 INTA 脉冲收到后,ISR 中最高优先级的 bit 自动清零。

    PIC 还有优先级轮转模式,即 PIC 在服务完一个管脚之后将其优先级临时降低,并升高未服务管脚的优先级,以实现类似轮询的模式,避免一个管脚持续发出中断导致其他设备“饿死”。

    下图是一个典型的 PIC 中断分配,管脚基本上都被古董级设备占据了。

    arch/x86/kernel/i8259_32.c 中 8259A 引脚的分配(function init_8259A)

    292         outb_pic(0x11, PIC_MASTER_CMD); /* ICW1: select 8259A-1 init */
    293         outb_pic(0x20 + 0, PIC_MASTER_IMR);     /* ICW2: 8259A-1 IR0-7 mapped to 0x20-0x27 */
    294         outb_pic(1U << PIC_CASCADE_IR, PIC_MASTER_IMR); /* 8259A-1 (the master) has a slave on IR2 */
    295         if (auto_eoi)   /* master does Auto EOI */
    296                 outb_pic(MASTER_ICW4_DEFAULT | PIC_ICW4_AEOI, PIC_MASTER_IMR);
    297         else            /* master expects normal EOI */
    298                 outb_pic(MASTER_ICW4_DEFAULT, PIC_MASTER_IMR);
    299 
    300         outb_pic(0x11, PIC_SLAVE_CMD);  /* ICW1: select 8259A-2 init */
    301         outb_pic(0x20 + 8, PIC_SLAVE_IMR);      /* ICW2: 8259A-2 IR0-7 mapped to 0x28-0x2f */
    302         outb_pic(PIC_CASCADE_IR, PIC_SLAVE_IMR);        /* 8259A-2 is a slave on master's IR2 */
    303         outb_pic(SLAVE_ICW4_DEFAULT, PIC_SLAVE_IMR); /* (slave's support for AEOI in flat mode is to be investigated) */
    

    从上图可见,PIC 能接的设备数量实在太少了,而且不支持多处理器。

    为了使用 8259A 级联连接较多的设备,可以采用两种方式:

    • IRQ 共享:中断处理程序执行多个中断服务程序(ISR),每个 ISR 是一个与共享 IRQ 线相关的函数。

      IRQ 共享需要满足两个条件:

      • 每个 ISR 都愿意共享 IRQ,即 request_irq() 时指定了 IRQF_SHARED
      • 所有 ISR 具有相同的触发条件(电平触发或边沿触发、高低电平或上下边沿)
    • IRQ 动态分配:在可能的最后时刻,才把 IRQ 线分配给一个设备。

    当然,APIC 是现代的解决方案。即使是 APIC,也需要使用 IRQ 共享。

    I/O APIC 的组成为:一组 24 条 IRQ 线,一张 24 项的中断重定向表,可编程寄存器,通过 APIC 总线发送和接收 APIC 信息的一个信息单元。

    与 8259A 不同,中断优先级不与引脚号相关联,中断重定向表中的每一项都可以被单独编程以指明中断向量和优先级、目标处理器和选择处理器的方式。

    来自外部硬件设备的中断以两种方式在可用 CPU 之间分发:

    • 静态分发
    • 动态分发

    2.2 中断描述符

    Intel 提供了三种类型的中断描述符:任务门、中断门及陷阱门描述符。

    Linux 使用与 Intel 稍有不同的分类,把中断描述符分为五类:

    • 中断门(interrupt gate):用户态的进程不能访问Intel中断门(门的DPL字段为0)。所有的Linux中断处理程序都通过中断门激活,并全部限制在内核态。
      set_intr_gate(n,addr)

      上述系统调用在 IDT 的第 n 个表项插入一个中断门。门中的段选择符设置成内核代码的段选择符,偏移量设置为中断处理程序的地址 addr,DPL 字段设置为0。

    • 系统门(system gate):用户态的进程可以访问Intel陷阱门(门的DPL字段为3)。通过系统门来激活三个Linux异常处理程序,它们的向量是4,5及128,因此,在用户态下,可以发布into、bound及int $0x80三条汇编语言指令。
      set_system_gate(n,addr)
    • 系统中断门(system interrupt gate):能够被用户态进程访问的Intel中断门(门的DPL字段为3)。与向量3相关的异常处理程序是由系统中断门激活的,因此,在用户态可以使用汇编语言指令int3。
      set_system_intr_gate(n,addr)
    • 陷阱门(trap gate):用户态的进程不能访问的一个Intel陷阱门(门的DPL字段为0)。大部分Linux异常处理程序都通过陷阱门来激活。
      set_trap_gate(n,addr)
    • 任务门(task gate):不能被用户态进程访问的Intel任务门(门的DPL字段为0)。Linux对“Double fault”异常的处理程序是由任务门激活的。
      set_task_gate(n,gdt)

      门中的段选择符中存放一个TSS的全局描述符表的指针,该TSS中包含要被激活的函数。

    在 IDT 中插入门的函数定义在 include/asm-x86/desc.h 中。

    这些函数以不同的参数调用内部函数 _set_gate()。_set_gate 调用两个内部函数

    • pack_gate: 设置门的数据结构:中断号、门类型、处理函数地址、DPL、ist、目录段寄存器
       38 static inline void pack_gate(gate_desc *gate, unsigned type, unsigned long func,
       39                              unsigned dpl, unsigned ist, unsigned seg)
       40 {
       41         gate->offset_low = PTR_LOW(func);        // 处理函数低内存偏移
       42         gate->segment = __KERNEL_CS;             // 内核代码段
       43         gate->ist = ist;                         // ist
       44         gate->p = 1;
       45         gate->dpl = dpl;                         // DPL
       46         gate->zero0 = 0;
       47         gate->zero1 = 0;
       48         gate->type = type;                       // 门类型(宏定义)
       49         gate->offset_middle = PTR_MIDDLE(func);  // 处理函数中内存偏移
       50         gate->offset_high = PTR_HIGH(func);      // 处理函数高内存偏移
       51 }
      
    • write_idt_entry: 宏定义为 native_write_idt_entry,用 memcpy 将设置好的门写入 IDT。

    2.3 中断数据结构

    在 Linux 中,中断描述符的核心数据结构是 include/linux/irq.h 中的 irq_desc 结构体。每个 irq_desc 实例描述一条中断线。

    153 struct irq_desc {
    154         irq_flow_handler_t      handle_irq;		// 中断事件处理函数,下面会介绍
    155         struct irq_chip         *chip;		// irq_chip 指针,描述了一些硬件信息,下面会介绍
    156         struct msi_desc         *msi_desc;
    157         void                    *handler_data;	// chip 中使用的数据
    158         void                    *chip_data;		// chip 中使用的数据
    159         struct irqaction        *action;        /* IRQ action list */ // irqaction 指针,下面会介绍
    160         unsigned int            status;         /* IRQ status */      // IRQ 线状态标志
    161 
    162         unsigned int            depth;          /* nested irq disables */
    163         unsigned int            wake_depth;     /* nested wake enables */
    164         unsigned int            irq_count;      /* For detecting broken IRQs */ // 中断计数
    165         unsigned int            irqs_unhandled;	// 无法处理的中断计数
    166         unsigned long           last_unhandled; /* Aging timer for unhandled count */
    167         spinlock_t              lock;		// 自旋锁
    168 #ifdef CONFIG_SMP
    169         cpumask_t               affinity;		// 多处理器中的处理器亲和性
    170         unsigned int            cpu;
    171 #endif
    172 #if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
    173         cpumask_t               pending_mask;
    174 #endif
    175 #ifdef CONFIG_PROC_FS
    176         struct proc_dir_entry   *dir;		// 在 /proc 文件系统中的目录
    177 #endif
    178         const char              *name;		// 中断名称
    179 } ____cacheline_internodealigned_in_smp;
    

    irq_desc 在 kernel/irq/handle.c 中被使用,此文件是 IRQ 机制的核心入口,描述了各中断线。

     50 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
     51         [0 ... NR_IRQS-1] = {
     52                 .status = IRQ_DISABLED,				// 默认屏蔽中断
     53                 .chip = &no_irq_chip,				// 没有与 chip 相关联
     	// 未知(坏的)IRQ 处理程序,输出 IRQ 信息供调试,更新 CPU IRQ 次数计数器,回应 IRQ。
     54                 .handle_irq = handle_bad_irq,			
     55                 .depth = 1,						// 默认是第一层(没有嵌套中断)
     56                 .lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock),	// 还没有自旋锁
     57 #ifdef CONFIG_SMP
     58                 .affinity = CPU_MASK_ALL				// 处理器亲和性未定义
     59 #endif
     60         }
     61 };
    

    下面介绍 irq_desc 中的主要数据成员。

    handle_irq
    handle_irq 是函数指针,指向 kernel/irq/chip.c 中的中断事件处理函数。
    • handle_simple_irq
    • handle_level_irq
    • handle_fasteoi_irq
    • handle_edge_irq
    • handle_percpu_irq

    这个函数指针是由 kernel/irq/chip.c 中的 __set_irq_handler() 设置的。

    chip
    chip 是 irq_chip 结构体指针,include/linux/irq.h 中的 irq_chip 结构体定义了对每根中断线的底层硬件操作:
     99 struct irq_chip {
    100         const char      *name;					// 中断线名称
    101         unsigned int    (*startup)(unsigned int irq);		// 初始化中断的函数指针
    102         void            (*shutdown)(unsigned int irq);		// 停止中断的函数指针
    103         void            (*enable)(unsigned int irq);		// 启用中断的函数指针
    104         void            (*disable)(unsigned int irq);		// 关闭中断的函数指针
    105 
    106         void            (*ack)(unsigned int irq);			// 确认中断的函数指针
    107         void            (*mask)(unsigned int irq);			// 屏蔽中断的函数指针
    108         void            (*mask_ack)(unsigned int irq);		// 确认并屏蔽中断的函数指针
    109         void            (*unmask)(unsigned int irq);		// 取消屏蔽中断的函数指针
    110         void            (*eoi)(unsigned int irq);			// 中断处理结束的函数指针
    111 
    112         void            (*end)(unsigned int irq);
    113         void            (*set_affinity)(unsigned int irq, cpumask_t dest);		// 设置处理器亲和性
    114         int             (*retrigger)(unsigned int irq);				// 重新出发中断
    	// 设置中断触发类型,根据 IRQ_TYPE 宏定义,包括上边沿、下边沿、边沿、高电平、低电平等
    115         int             (*set_type)(unsigned int irq, unsigned int flow_type);
    116         int             (*set_wake)(unsigned int irq, unsigned int on);		// 唤醒中断
    117 
    118         /* Currently used only by UML, might disappear one day.*/
    119 #ifdef CONFIG_IRQ_RELEASE_METHOD
    120         void            (*release)(unsigned int irq, void *dev_id);
    121 #endif
    122         /*
    123          * For compatibility, ->typename is copied into ->name.
    124          * Will disappear.
    125          */
    126         const char      *typename;
    127 };
    
    action
    action 是 irqaction 结构体指针,指向一个 irqaction 链表。irqaction 在 include/linux/interrupt.h 中定义,每个结构体描述一个中断处理程序。
     60 struct irqaction {
     61         irq_handler_t handler;		// 中断处理程序的函数指针
     62         unsigned long flags;
     63         cpumask_t mask;			// 处理器亲和性
     64         const char *name;			// 中断处理程序名称,显示在 /proc/interrupts 中
     65         void *dev_id;			// 设备 ID
     66         struct irqaction *next;		// 指向链表中的下一个 irqaction 结构体
     67         int irq;				// 中断通道号
     68         struct proc_dir_entry *dir;		// 在 /proc 文件系统中的目录
     69 };
    
    status
    status 是描述 IRQ 线状态的一组标志。在同一文件中宏定义:
     49 #define IRQ_INPROGRESS          0x00000100      /* IRQ handler active - do not enter! */
     50 #define IRQ_DISABLED            0x00000200      /* IRQ disabled - do not enter! */
     51 #define IRQ_PENDING             0x00000400      /* IRQ pending - replay on enable */
     52 #define IRQ_REPLAY              0x00000800      /* IRQ has been replayed but not acked yet */
     53 #define IRQ_AUTODETECT          0x00001000      /* IRQ is being autodetected */
     54 #define IRQ_WAITING             0x00002000      /* IRQ not yet seen - for autodetection */
     55 #define IRQ_LEVEL               0x00004000      /* IRQ level triggered */
     56 #define IRQ_MASKED              0x00008000      /* IRQ masked - shouldn't be seen again */
     57 #define IRQ_PER_CPU             0x00010000      /* IRQ is per CPU */
    

    综上所述,内核中的中断描述符表是一个 irq_desc 数组,数组的每一项描述一根中断线的信息,包括芯片中断处理程序、底层硬件操作函数、注册的中断处理程序链表等。

    中断向量表可以通过 /proc/interrupts 查看:

    [boj@~]$ cat /proc/interrupts 
               CPU0       CPU1       
      0:    3652701          2   IO-APIC-edge      timer
      1:      34517          0   IO-APIC-edge      i8042
      8:          1          0   IO-APIC-edge      rtc0
      9:      48512         19   IO-APIC-fasteoi   acpi
     12:         12          0   IO-APIC-edge      i8042
     14:      29337          0   IO-APIC-edge      ata_piix
     15:      38002          0   IO-APIC-edge      ata_piix
     16:     263352          1   IO-APIC-fasteoi   uhci_hcd:usb5, yenta, i915
     18:          0          0   IO-APIC-fasteoi   uhci_hcd:usb4
     19:     105769          0   IO-APIC-fasteoi   uhci_hcd:usb3
     21:      34677          0   IO-APIC-fasteoi   eth0
     22:        151          0   IO-APIC-fasteoi   firewire_ohci
     23:          2          0   IO-APIC-fasteoi   ehci_hcd:usb1, uhci_hcd:usb2, mmc0
     42:     360215          0   PCI-MSI-edge      iwl3945
     43:        656          0   PCI-MSI-edge      hda_intel
    NMI:          0          0   Non-maskable interrupts
    LOC:     253429    2025163   Local timer interrupts
    SPU:          0          0   Spurious interrupts
    PMI:          0          0   Performance monitoring interrupts
    IWI:          0          0   IRQ work interrupts
    RES:    1063515    1286501   Rescheduling interrupts
    CAL:       3762       2967   Function call interrupts
    TLB:      13274      13115   TLB shootdowns
    TRM:          0          0   Thermal event interrupts
    THR:          0          0   Threshold APIC interrupts
    MCE:          0          0   Machine check exceptions
    MCP:         32         32   Machine check polls
    ERR:          0
    MIS:          0
    

    负责打印 /proc/interrupts 的代码位于 arch/x86/kernel/irq_32.c。

    242 int show_interrupts(struct seq_file *p, void *v)

    2.4 中断的初始化

    中断机制的初始化分为三步:

    1. arch/x86/kernel/head_32.S 中 setup IDT,在内核引导分析报告中已经阐述。
    2. init/main.c 的 start_kernel() 中的 trap_init()
    3. init/main.c 的 start_kernel() 中的 init_IRQ()
    trap_init()

    trap_init() 定义于 arch/x86/kernel/traps_32.c,作用是设置中断向量。

    1. 初始化 APIC 映射表
    2. 调用 set_trap_gate、set_intr_gate、set_task_gate、set_system_gate 等,初始化中断描述符表。
    3. 调用 set_system_gate,初始化系统调用
    4. 将已设置的中断向量置保留位
    5. 将已设置的系统调用置保留位
    6. 初始化 CPU 作为屏障
    7. 执行 trap_init 的钩子函数
    init_IRQ
    init_IRQ() 定义于 arch/x86/kernel/paravirt.c,由 paravirt_ops.init_IRQ() 和 native_init_IRQ() 二者组成。

    native_init_IRQ() 定义于 arch/x86/kernel/i8259.c。该函数主要将 IDT 未初始化的各项初始化为中断门。

    • pre_intr_init_hook() 调用 init_ISA_irqs(),初始化 irq_desc 数组、status、action、depth。

    • 在循环中,对于所有在 FIRST_EXTERNAL_VECTOR(0x20) 与 NR_VECTOR(0x100)之间的、不是系统中断的 256 - 32 - 1 = 223 项,调用 set_intr_gate(),初始化为中断门。

    • 现在我们关心的是,这些中断门的中断处理程序是什么?在 x86 体系结构下没找到 interrupt 数组的定义,因此使用 64 位体系结构的做说明:

      // arch/x86/kernel/i8259_64.c
       80 static void (*__initdata interrupt[NR_VECTORS - FIRST_EXTERNAL_VECTOR])(void) = {
       81                                           IRQLIST_16(0x2), IRQLIST_16(0x3),
       82         IRQLIST_16(0x4), IRQLIST_16(0x5), IRQLIST_16(0x6), IRQLIST_16(0x7),
       83         IRQLIST_16(0x8), IRQLIST_16(0x9), IRQLIST_16(0xa), IRQLIST_16(0xb),
       84         IRQLIST_16(0xc), IRQLIST_16(0xd), IRQLIST_16(0xe), IRQLIST_16(0xf)
       85 };
      
    • 以上是 interrupt 数组的定义。在下面的代码中,## 是将字符串连接起来,这样宏定义的函数 IRQ(0x4,6) 就是 IRQ0x46_interrupt,生成 224 个这样的函数填入数组。
      // arch/x86/kernel/i8259_64.c
       70 #define IRQ(x,y) \
       71         IRQ##x##y##_interrupt
       72 
       73 #define IRQLIST_16(x) \
       74         IRQ(x,0), IRQ(x,1), IRQ(x,2), IRQ(x,3), \
       75         IRQ(x,4), IRQ(x,5), IRQ(x,6), IRQ(x,7), \
       76         IRQ(x,8), IRQ(x,9), IRQ(x,a), IRQ(x,b), \
       77         IRQ(x,c), IRQ(x,d), IRQ(x,e), IRQ(x,f)
      
    • 那么这 224 个函数在哪里呢?通过下面的宏可以生成一个汇编函数,它调用了 common_interrupt 函数:
      // include/asm/hw_irq_64.h
      155 #define IRQ_NAME2(nr) nr##_interrupt(void)
      156 #define IRQ_NAME(nr) IRQ_NAME2(IRQ##nr)
      162 #define BUILD_IRQ(nr)                           \
      163         asmlinkage void IRQ_NAME(nr);           \
      164         asm("\n.p2align\n"                      \
      165             "IRQ" #nr "_interrupt:\n\t"         \
      166             "push $~(" #nr ") ; "               \
      167             "jmp common_interrupt");
      
    • common_interrupt 是汇编函数,这个函数最终调用了 do_IRQ,这是我们下章要介绍的核心中断处理函数。
      // arch/x86/kernel/entry_32.S
       613 common_interrupt:
       614         SAVE_ALL
       615         TRACE_IRQS_OFF
       616         movl %esp,%eax
       617         call do_IRQ
       618         jmp ret_from_intr
       619 ENDPROC(common_interrupt)
      
    • 看来,只需要调用 BUILD_IRQ 就能生成中断处理函数了。Linux Kernel 正是这样做的:
      // arch/x86/kernel/i8259_64.c
       37 #define BI(x,y) \
       38         BUILD_IRQ(x##y)
       39 
       40 #define BUILD_16_IRQS(x) \
       41         BI(x,0) BI(x,1) BI(x,2) BI(x,3) \
       42         BI(x,4) BI(x,5) BI(x,6) BI(x,7) \
       43         BI(x,8) BI(x,9) BI(x,a) BI(x,b) \
       44         BI(x,c) BI(x,d) BI(x,e) BI(x,f)
      ....................................................
       61                                       BUILD_16_IRQS(0x2) BUILD_16_IRQS(0x3)
       62 BUILD_16_IRQS(0x4) BUILD_16_IRQS(0x5) BUILD_16_IRQS(0x6) BUILD_16_IRQS(0x7)
       63 BUILD_16_IRQS(0x8) BUILD_16_IRQS(0x9) BUILD_16_IRQS(0xa) BUILD_16_IRQS(0xb)
       64 BUILD_16_IRQS(0xc) BUILD_16_IRQS(0xd) BUILD_16_IRQS(0xe) BUILD_16_IRQS(0xf)
      
    • 不得不发表一下感慨,Linux Kernel 对 C 语言宏的运用真是炉火纯青。如果是我写代码,很可能不得不先写个代码生成器,用它生成 224 个函数的源码。但 Linux Kernel Source 里出现几百个几乎一模一样的函数太不优雅了,于是利用 C 语言的预处理机制,采用二级宏定义,尽可能降低代码量。

    2.5 内核接口

    中断处理程序不是编译内核时就完全确定的,因此要为开发者留下编程接口。

    2.6.17 内核引入了 generic IRQ 机制,支持 i386、x86-64 和 ARM 三个体系结构。generic IRQ 层的引入,是为了剥离 IRQ flow 和 IRQ chip 过于紧密的耦合。为驱动开发者提供通用的 API 来 request/enable/disable/free 中断,而不需要知道任何底层的中断控制器细节。

    这些中断 API 是在内核中用 EXPORT_SYMBOL 导出的。

    请求中断
    kernel/irq/manage.c 中的 request_irq:
    536 int request_irq(unsigned int irq, irq_handler_t handler,
    537                 unsigned long irqflags, const char *devname, void *dev_id)
    

    参数

    • irq: 中断通道号,无符号整数
    • handler:中断处理程序的函数指针 (irq_return_t isr_func(int irq, void *dev_id))
    • irq_flags:标志位
      • IRQF_SHARED: 共享中断通道
      • IRQF_DISABLED: 中断处理程序执行时关中断
      • IRQF_SAMPLE_RANDOM: 随机发生中断,可用于产生随机数
      • IRQF_TRIGGER_LOW:2.6.26 中没有,低电平有效
      • IRQF_TRIGGER_HIGH: 2.6.26 中没有,高电平有效
      • IRQF_TRIGGER_RISING: 2.6.26 中没有,上升沿有效
      • IRQF_TRIGGER_FALLING: 2.6.26 中没有,下降沿有效
    • dev_name:名称,显示在 /proc/interrupts 中
    • dev_id:设备 ID,区分不同的设备

    内部机制:

    1. 检查输入数据的合法性
    2. 为临时变量 irqaction 分配内存空间,初始化 irqaction 数据结构
    3. 如果是调试模式,测试是否运行正常
    4. 进入工作函数 setup_irq(unsigned int irq, struct irqaction *new)
      1. 如果是 IRQF_SAMPLE_RANDOM 模式,随机初始化 irq
      2. 上自旋锁
      3. 如果希望共享中断通道,所有中断处理程序需要有相同的触发特性标识、PERCPU 特性
      4. 把新 irqaction 结构体挂在链表尾部
      5. 如果设置了 IRQF_TRIGGER_MASK,初始化触发特性
      6. 初始化 irq 状态、嵌套深度
      7. 启动(enable)此 IRQ
      8. 释放自旋锁
      9. 调用 /kernel/irq/proc.c 中的 register_irq_proc() 和 register_handler_proc(),建立 /proc 文件系统中的相关数据结构
      10. 返回成功(0)
      11. 如果出错,输出内核调试信息,释放自旋锁,返回错误
    5. 释放 irqaction 的内存空间,返回 setup_irq 的返回值
    清除中断
    kernel/irq/manage.c 中的 free_irq:
    435 void free_irq(unsigned int irq, void *dev_id)

    参数

    • irq: 中断通道号,无符号整数
    • dev_id: 请求中断时指定的设备 ID

    内部机制:

    1. 不能在中断上下文中调用
    2. 上自旋锁
    3. 循环,沿链表查找要删除的中断处理程序
      1. 如果发现是已经释放的,则输出内核调试信息,释放自旋锁
      2. 如果 dev_id 不对,沿着 irqaction 链表继续向下寻找
      3. 如果找到了,从链表中移除这个 irqaction
      4. 关闭此 IRQ,关闭硬件,释放自旋锁,从 /proc 文件系统中删除对应目录
      5. 同步 IRQ 以防正在其他 CPU 上运行
      6. 如果是调试模式,测试驱动程序是否知道此共享 IRQ 已移除
      7. 释放内存空间,返回
    启用中断
    kernel/irq/manage.c 中的 enable_irq:
    153 static void __enable_irq(struct irq_desc *desc, unsigned int irq)

    内部调用了 __enable_irq,首先上自旋锁,找到 irq_desc 结构体指针,判断嵌套深度,刷新 IRQ 状态,释放自旋锁。

    参数

    • desc: 指向 irq_desc 结构体的指针
    • irq: 中断通道号
    关闭中断
    kernel/irq/manage.c 中的 disable_irq:
    140 void disable_irq(unsigned int irq)

    参数

    • irq: 中断通道号
    关闭中断 (无等待)
    disable_irq 会保证存在的 IRQ handler 完成操作,而 disable_irq_nosync 立即关中断并返回。事实上,disable_irq 首先调用 disable_irq_nosync,然后调用 synchronize_irq 同步。
    111 void disable_irq_nosync(unsigned int irq)
    同步中断 (多处理器)
     30 void synchronize_irq(unsigned int irq)
    设置 IRQ 芯片
    kernel/irq/chip.c: set_irq_chip()
     93 int set_irq_chip(unsigned int irq, struct irq_chip *chip)
    设置 IRQ 类型
    kernel/irq/chip.c: set_irq_type()
    122 int set_irq_type(unsigned int irq, unsigned int type)
    设置 IRQ 数据
    kernel/irq/chip.c: set_irq_data()
    150 int set_irq_data(unsigned int irq, void *data)
    设置 IRQ 芯片数据
    kernel/irq/chip.c: set_irq_chip_data()
    202 int set_irq_chip_data(unsigned int irq, void *data)

    3 中断处理流程

    3.1 CPU的中断处理流程

    本节摘自参考文献之 中断的硬件环境

    每个能够发出中断请求的硬件设备控制器都有一条名为 IRQ 的输出线。所有现有的 IRQ 线都与一个名为可编程中断控制器(PIC)的硬件电路的输入引脚相连,可编程中断控制器执行下列动作:

    1. 监视 IRQ 线,检查产生的信号。如果有两条以上的 IRQ 线上产生信号,就选择引脚编号较小的 IRQ 线。
    2. 如果一个引发信号出现在 IRQ 线上:
      1. 把接收到的引发信号转换成对应的向量号
      2. 把这个向量存放在中断控制器的一个 I/O 端口(0x20、0x21),从而允许 CPU 通过数据总线读此向量。
      3. 把引发信号发送到处理器的 INTR 引脚,即产生一个中断。
      4. 等待,直到 CPU 通过把这个中断信号写进可编程中断控制器的一个 I/O 端口来确认它;当这种情况发生时,清 INTR 线。
    3. 返回第1步。

    当执行了一条指令后,CS和eip这对寄存器包含下一条将要执行的指令的逻辑地址。在处理那条指令之前,控制单元会检查在运行前一条指令时是否已经发生了一个中断或异常。如果发生了一个中断或异常,那么控制单元执行下列操作:

    1. 确定与中断或异常关联的向量i (0 ≤ i ≤ 255)。
    2. 读由idtr寄存器指向的 IDT表中的第i项(在下面的分析中,我们假定IDT表项中包含的是一个中断门或一个陷阱门)。
    3. 从gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中的选择符所标识的段描述符。这个描述符指定中断或异常处理程序所在段的基地址。
    4. 确信中断是由授权的(中断)发生源发出的。首先将当前特权级CPL(存放在cs寄存器的低两位)与段描述符(存放在GDT中)的描述符特权级DPL比较,如果CPL小于DPL,就产生一个“General protection”异常,因为中断处理程序的特权不能低于引起中断的程序的特权。对于编程异常,则做进一步的安全检查:比较CPL与处于IDT中的门描述符的DPL,如果DPL小于CPL,就产生一个“General protection”异常。这最后一个检查可以避免用户应用程序访问特殊的陷阱门或中断门。
    5. 检查是否发生了特权级的变化,也就是说,CPL是否不同于所选择的段描述符的DPL。如果是,控制单元必须开始使用与新的特权级相关的栈。通过执行以下步骤来做到这点:
      1. 读tr寄存器,以访问运行进程的TSS段。
      2. 用与新特权级相关的栈段和栈指针的正确值装载ss和esp寄存器。这些值可以在TSS中找到(参见第三章的“任务状态段”一节)
      3. 在新的栈中保存ss和esp以前的值,这些值定义了与旧特权级相关的栈的逻辑地址。
    6. 如果故障已发生,用引起异常的指令地址装载CS和eip寄存器,从而使得这条指令能再次被执行。
    7. 在栈中保存eflags、CS及eip的内容。
    8. 如果异常产生了一个硬件出错码,则将它保存在栈中。
    9. 装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量字段。这些值给出了中断或者异常处理程序的第一条指令的逻辑地址。

    控制单元所执行的最后一步就是跳转到中断或者异常处理程序。换句话说,处理完中断信号后,控制单元所执行的指令就是被选中处理程序的第一条指令。

    中断或异常被处理完后,相应的处理程序必须产生一条iret指令,把控制权转交给被中断的进程,这将迫使控制单元:

    1. 用保存在栈中的值装载CS、eip或eflags寄存器。如果一个硬件出错码曾被压入栈中,并且在eip内容的上面,那么,执行iret指令前必须先弹出这个硬件出错码。
    2. 检查处理程序的CPL是否等于CS中最低两位的值(这意味着被中断的进程与处理程序运行在同一特权级)。如果是,iret终止执行;否则,转入下一步。
    3. 从栈中装载ss和esp寄存器,因此,返回到与旧特权级相关的栈。
    4. 检查ds、es、fs及gs段寄存器的内容,如果其中一个寄存器包含的选择符是一个段描述符,并且其DPL值小于CPL,那么,清相应的段寄存器。控制单元这么做是为了禁止用户态的程序(CPL=3)利用内核以前所用的段寄存器(DPL=0)。如果不清这些寄存器,怀有恶意的用户态程序就可能利用它们来访问内核地址空间。

    3.2 保存中断信息

    Linux 内核的中断处理机制自始至终贯穿着 “重要的事马上做,不重要的事推后做” 的思想。

    中断处理程序首先要做:

    • 将中断号压入栈中,以便找到对应的中断服务程序
    • 将当前寄存器信息压入栈中,以便中断退出时恢复上下文

    显然, 这两步都是不可重入的。因此在进入中断服务程序时,CPU 已经自动禁止了本 CPU 上的中断响应。

    上章中断初始化过程的分析中,已经介绍了 interrupt 数组的生成过程,其中索引为 n 的元素中存放着下列指令的地址:

    pushl n-256
    jmp common_interrupt

    执行结果是将中断号 - 256 保存在栈中,这样栈中的中断都是负数,而正数用来表示系统调用。这样,系统调用和中断可以用一个有符号整数统一表示。

    现在重述一下 common_interrupt 的定义:

    // arch/x86/kernel/entry_32.S
     613 common_interrupt:
     614         SAVE_ALL
     615         TRACE_IRQS_OFF
     616         movl %esp,%eax	# 将栈顶地址放入 eax,这样 do_IRQ 返回时控制转到 ret_from_intr()
     617         call do_IRQ	# 核心中断处理函数
     618         jmp ret_from_intr	# 跳转到 ret_from_intr()
    

    其中 SAVE_ALL 宏将被展开成:

    cld
    push %es		# 保存除 eflags、cs、eip、ss、esp (已被 CPU 自动保存) 外的其他寄存器
    push %ds
    pushl %eax
    pushl %ebp
    pushl %edi
    pushl %edx
    pushl %ecx
    pushl %ebx
    movl $ _ _USER_DS, %edx
    movl %edx, %ds		# 将用户数据段选择符载入 ds、es
    movl %edx, %es
    

    3.3 处理中断

    前面汇编代码的实质是,以中断发生时寄存器的信息为参数,调用 arch/x86/kernel/irq32.c 中的 do_IRQ 函数。

    我们注意到 unlikely 和 unlikely 宏定义,它们的含义是

    #define likely(x)       __builtin_expect((x),1)
    #define unlikely(x)     __builtin_expect((x),0)
    

    __builtin_expect 是 GCC 的内部机制,意思是告诉编译器哪个分支条件更有可能发生。这使得编译器把更可能发生的分支条件与前面的代码顺序串接起来,更有效地利用 CPU 的指令流水线。

    do_IRQ 函数流程:

    1. 保存寄存器上下文
    2. 调用 irq_enter:
      // kernel/softirq.c
      281 void irq_enter(void)
      282 {
      283 #ifdef CONFIG_NO_HZ		
      // 无滴答内核,它将在需要调度新任务时执行计算并在这个时间设置一个时钟中断,允许处理器在更长的时间内(几秒钟)保持在最低功耗状态,从而减少了电能消耗。
      284         int cpu = smp_processor_id();
      285         if (idle_cpu(cpu) && !in_interrupt())
      286                 tick_nohz_stop_idle(cpu);	// 如果空闲且不在中断中,则停止空闲,开始工作
      287 #endif
      288         __irq_enter();
      289 #ifdef CONFIG_NO_HZ
      290         if (idle_cpu(cpu))
      291                 tick_nohz_update_jiffies();	// 更新 jiffies
      292 #endif
      293 }
      
      // include/linux/hardirq.h
      135 #define __irq_enter()                                   \
      /* 在宏定义函数中,do { ... } while(0) 结构可以把语句块作为一个整体,就像函数调用,避免宏展开后出现问题 */
      136         do {                                            \  
      137                 rcu_irq_enter();                        \
      138                 account_system_vtime(current);          \
      139                 add_preempt_count(HARDIRQ_OFFSET);      \ /* 程序嵌套数量计数器递增1 */
      140                 trace_hardirq_enter();                  \
      141         } while (0)
      
    3. 如果可用空间不足 1KB,可能会引发栈溢出,输出内核错误信息
    4. 如果 thread_union 是 4KB 的,进行一些特殊处理
    5. 调用 desc->handle_irq(irq, desc),调用 __do_IRQ() (kernel/irq/handle.c)
      1. 取得中断号,获取对应的 irq_desc
      2. 如果是 CPU 内部中断,不需要上锁,简单处理完就返回了
      3. 上自旋锁
      4. 应答中断芯片,这样中断芯片就能开始接受新的中断了。
      5. 更新中断状态。

        IRQ_REPLAY:如果被禁止的中断管脚上产生了中断,这个中断是不会被处理的。当这个中断号被允许产生中断时,会将这个未被处理的中断转为 IRQ_REPLAY。

        IRQ_WAITING:探测用,探测时会将所有没有中断处理函数的中断号设为 IRQ_WAITING,只要这个中断管脚上有中断产生,就把这个状态去掉,从而知道哪些中断管脚上产生过中断。

        IRQ_PENDING、IRQ_INPROGRESS 是为了确保同一个中断号的处理程序不能重入,且不能丢失这个中断的下一个处理程序。具体地说,当内核在运行某个中断号对应的处理程序时,状态会设置成 IRQ_INPROGRESS。如果发现已经有另一实例在运行了,就将这下一个中断标注为 IRQ_PENDING 并返回。这个已在运行的实例结束的时候,会查看是否期间有同一中断发生了,是则再次执行一遍。

      6. 如果链表上没有中断处理程序,或者中断被禁止,或者已经有另一实例在运行,则进行收尾工作。
      7. 循环:
        1. 释放自旋锁
        2. 执行函数链:handle_IRQ_event()。其中主要是一个循环,依次执行中断处理程序链表上的函数,并根据返回值更新中断状态。如果愿意,可以参与随机数采样。中断处理程序执行期间,打开本地中断。
        3. 上自旋锁
        4. 如果当前中断已经处理完,则退出;不然取消中断的 PENDING 标志,继续循环。
      8. 取消中断的 INPROGRESS 标志
      9. 收尾工作:有的中断在处理过程中被关闭了,->end() 处理这种情况;释放自旋锁。
    6. 执行 irq_exit(),在 kernel/softirq.c 中:
      1. 递减中断计数器
      2. 检查是否有软中断在等待执行,若有则执行软中断。
      3. 如果使用了无滴答内核看是不是该休息了。
    7. 恢复寄存器上下文,跳转到 ret_from_intr (跳转点早在 common_interrupt 中就被指定了)

    在中断处理过程中,我们反复看到对自旋锁的操作。在单处理器系统上,spinlock 是没有作用的;在多处理器系统上,由于同种类型的中断可能连续产生,同时被几个 CPU 处理(注意,应答中断芯片是紧接着获得自旋锁后,位于整个中断处理流程的前部,因此在中断处理流程的其余部分,中断芯片可以触发新的中断并被另一个 CPU 开始处理),如果没有自旋锁,多个 CPU 可能同时访问 IRQ 描述符,造成混乱。因此在访问 IRQ 描述符的过程中需要有 spinlock 保护。

    3.4 从中断中返回

    上面的中断处理流程中隐含了一个问题:整个处理过程是持续占有CPU的(除开中断情况下可能被新的中断打断外),这样

    • 连续的低优先的中断可能持续占有 CPU, 而高优先的某些进程则无法获得 CPU
    • 中断处理的这几个阶段中不能调用可能导致睡眠的函数

    对于第一个问题,较新的 linux 内核增加了 ksoftirqd 内核线程,如果持续处理的软中断超过一定数量,则结束中断处理过程,唤醒 ksoftirqd,由它来继续处理。

    对于第二个问题,linux 内核提供了 workqueue(工作队列)机制,定义一个 work 结构(包含了处理函数),然后在上述的中断处理的几个阶段的某一步中调用 schedule_work 函数,work 便被添加到 workqueue 中,等待处理。

    工作队列有着自己的处理线程, 这些 work 被推迟到这些线程中去处理。处理过程只可能发生在这些工作线程中,不会发生在内核中断处理路径中,所以可以睡眠。下章将简要介绍这些中断机制。

    3.5 编写中断处理程序

    本节编写一个简单的中断处理程序 (catchirq) 作为内核模块,演示捕获网卡中断。

    1. catchirq.c
      #include <linux/module.h>
      #include <linux/kernel.h>
      #include <linux/init.h>  
      #include <linux/interrupt.h> 
      #include <linux/timer.h>
      
      #define DEBUG  
      
      #ifdef DEBUG  
      #define MSG(message, args...) printk(KERN_DEBUG "catchirq: " message, ##args)  
      #else  
      #define MSG(message, args...)  
      #endif  
        
      MODULE_LICENSE("GPL");  
      MODULE_AUTHOR("boj");  
      
      int irq;
      char *interface;
      
      // module_param(name, type, perm)
      module_param(irq, int, 0644);
      module_param(interface, charp, 0644);
      
      int irq_handle_function(int irq, void *device_id)
      {
          static int count = 1;
          MSG("[%d] Receive IRQ at %ld\n", count, jiffies);
          count++;
          return IRQ_NONE;
      }
      
      int init_module()
      {
          if (request_irq(irq, irq_handle_function, IRQF_SHARED, interface, (void *)&irq))
          {
              MSG("[FAILED] IRQ register failure.\n");
              return -EIO;
          }
          MSG("[OK] Interface=%s IRQ=%d\n", interface, irq);
          return 0;
      }
      
      void cleanup_module()
      {
          free_irq(irq, &irq);
          MSG("IRQ is freed.\n");
      }
      
    2. Makefile(编写说明参见 Documentation/kbuild/)
      obj-m := catchirq.o
      KERNELDIR := /lib/modules/$(shell uname -r)/build
      
      default:
      	make -C $(KERNELDIR) M=$(shell pwd)
      
      clean:
      	make -C $(KERNELDIR) M=$(shell pwd) clean
      
    3. 命令:make
    4. [boj@~/int]$ ls
      built-in.o  catchirq.c  catchirq.ko  catchirq.mod.c  catchirq.mod.o  catchirq.o  Makefile  modules.order  Module.symvers
      
    5. 查看 /proc/interrupts(前面章节已经贴出来了),获知我们想截获的网卡(eth0)是 21 号中断。通过 insmod 的 interface 和 irq 指定模块加载参数(源文件中的 module_params 指定的)
      sudo insmod catchirq.ko interface=eth1 irq=21
    6. 成功插入一个内核模块:
      [boj@~]$ lsmod | grep catchirq
      catchirq               12636  0 
      
    7. 我们看到,/proc/interrupts 的 21 号中断增加了一个中断处理程序:eth1
      [boj@~/int]$ cat /proc/interrupts 
                 CPU0       CPU1       
        0:   23443709         27   IO-APIC-edge      timer
        1:     205319          0   IO-APIC-edge      i8042
        8:          1          0   IO-APIC-edge      rtc0
        9:     170665         80   IO-APIC-fasteoi   acpi
       12:         12          0   IO-APIC-edge      i8042
       14:     135310          0   IO-APIC-edge      ata_piix
       15:     205712          1   IO-APIC-edge      ata_piix
       16:    1488409         29   IO-APIC-fasteoi   uhci_hcd:usb5, yenta, i915
       18:          0          0   IO-APIC-fasteoi   uhci_hcd:usb4
       19:     477290          5   IO-APIC-fasteoi   uhci_hcd:usb3
       21:     107049          0   IO-APIC-fasteoi   eth0, eth1
       22:        806          0   IO-APIC-fasteoi   firewire_ohci
       23:          2          0   IO-APIC-fasteoi   ehci_hcd:usb1, uhci_hcd:usb2, mmc0
       42:    1803270          2   PCI-MSI-edge      iwl3945
       43:      11783          0   PCI-MSI-edge      hda_intel
      NMI:          0          0   Non-maskable interrupts
      LOC:    2013602   12644870   Local timer interrupts
      SPU:          0          0   Spurious interrupts
      PMI:          0          0   Performance monitoring interrupts
      IWI:          0          0   IRQ work interrupts
      RES:    6046340    7106551   Rescheduling interrupts
      CAL:      20110      14839   Function call interrupts
      TLB:      33385      36028   TLB shootdowns
      TRM:          0          0   Thermal event interrupts
      THR:          0          0   Threshold APIC interrupts
      MCE:          0          0   Machine check exceptions
      MCP:        172        172   Machine check polls
      ERR:          0
      MIS:          0
      
    8. dmesg 中可以看到大量如下形式的内核信息。这恰好是我们在源码中的 DEBUG 模式通过 printk 输出的。
      // [Time] module_name: [count] Receive IRQ at jiffies
      [51837.231505] catchirq: [499] Receive IRQ at 12884307
      [51837.232803] catchirq: [500] Receive IRQ at 12884308
      [51837.232849] catchirq: [501] Receive IRQ at 12884308
      [51837.269587] catchirq: [502] Receive IRQ at 12884317
      [51844.585799] catchirq: [503] Receive IRQ at 12886146
      [51844.586724] catchirq: [504] Receive IRQ at 12886146
      
    9. 演示完毕,卸载内核模块:
      sudo rmmod catchirq
    10. 根据 dmesg,catchirq 模块输出了最后一句话,被正常卸载。从 /proc/interrupts 看到,中断处理程序表恢复原状。
      [52413.797952] catchirq: [2245] Receive IRQ at 13028449
      [52413.815899] catchirq: [2246] Receive IRQ at 13028453
      [52413.815990] catchirq: [2247] Receive IRQ at 13028453
      [52413.841763] catchirq: IRQ is freed.
      

    4 软中断、tasklet与工作队列

    4.1 上半部与下半部

    软中断、tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来。下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任务队列的函数都消失了,只剩下了前三者。

    上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务。举个例子:在网络传输中,网卡接收到数据包这个事件不一定需要马上被处理,适合用下半部去实现;但是用户敲击键盘这样的事件就必须马上被响应,应该用中断实现。

    两者的主要区别在于:中断不能被相同类型的中断打断,而下半部依然可以被中断打断;中断对于时间非常敏感,而下半部基本上都是一些可以延迟的工作。由于二者的这种区别,所以对于一个工作是放在上半部还是放在下半部去执行,有一些参考标准:

    • 如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
    • 如果一个任务和硬件相关,将其放在中断处理程序中执行。
    • 如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。
    • 其他所有任务,考虑放在下半部去执行。

    4.2 软中断

    软中断作为下半部机制的代表,是随着 SMP 的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。特性是:

    • 产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。
    • 可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。

    4.3 tasklet

    由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。它具有以下特性:

    • 一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
    • 多个不同类型的tasklet可以并行在多个CPU上。
    • 软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。

    tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择。

    一般而言,在可延迟函数上可以执行四种操作:初始化/激活/执行/屏蔽。屏蔽我们这里不再叙述,前三个则比较重要。下面将软中断和tasklet的三个步骤分别进行对比介绍。

    • 初始化

      初始化是指在可延迟函数准备就绪之前所做的所有工作。一般包括两个大步骤:首先是向内核声明这个可延迟函数,以备内核在需要的时候调用;然后就是调用相应的初始化函数,用函数指针等初始化相应的描述符。

      如果是软中断则在内核初始化时进行,其描述符定义如下:

        struct softirq_action
                {
                         void (*action)(struct softirq_action *);
                         void *data;
                };
      

      kernel/softirq.c 中的软中断描述符数组:static struct softirq_action softirq_vec[32]

      前 6 个已经被内核注册使用:

      • tasklet 使用的 HI_SOFTIRQ
      • tasklet 使用的 TASKLET_SOFTIRQ
      • 网络协议栈使用的 NET_TX_SOFTIRQ
      • 网络协议栈使用的 NET_RX_SOFTIRQ
      • SCSI 存储
      • 系统计时器

      其余的软中断描述符可以由内核开发者使用。

               void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
      

      例如网络子系统通过以下两个函数初始化软中断(net_tx_action/net_rx_action是两个函数):

      	open_softirq(NET_TX_SOFTIRQ, net_tx_action);
      	open_softirq(NET_RX_SOFTIRQ, net_rx_action);
      

      当内核中产生 NET_TX_SOFTIRQ 软中断之后,就会调用 net_tx_action 这个函数。

      tasklet 则可以在运行时定义,例如加载模块时。定义方式有两种:

      • 静态声明
        DECLARE_TASKET(name, func, data)
        DECLARE_TASKLET_DISABLED(name, func, data)
        
      • 动态声明
        void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data)
        

        其参数分别为描述符,需要调用的函数和此函数的参数。初始化生成的就是一个实际的描述符,假设为 my_tasklet。

    • 激活

      激活:标记一个可延迟函数为挂起(pending)状态,表示内核可以调用这个可延迟函数。类似处于 TASK_RUNNING 状态的进程,处在这个状态的进程只是准备好了被 CPU 调度,但并不一定马上就会被调度。

      软中断使用 raise_softirq() 函数激活,接收的参数就是上面初始化时用到的数组索引 nr。

      tasklet 使用 tasklet_schedule() 激活,该函数接受 tasklet 的描述符作为参数,例如上面生成的 my_tasklet:

      tasklet_schedule(&my_tasklet)
    • 执行

      执行就是内核运行可延迟函数的过程,但是执行只发生在某些特定的时刻。

      每个CPU上都有一个32位的掩码__softirq_pending,表明此CPU上有哪些挂起(已被激活)的软中断。此掩码可以用local_softirq_pending()宏获得。所有的挂起的软中断需要用do_softirq()函数的一个循环来处理。

      对于 tasklet,软中断初始化时设置了发生 TASKLET_SOFTIRQ 或 HI_SOFTIRQ 软中断时所执行的函数:

      	open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
      	open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
      

      tasklet_action 和 tasklet_hi_action 内部实现不同,软中断和 tasklet 因此具有了不同的特性。

    4.4 工作队列

    上面的可延迟函数运行在中断上下文中(如上章所述,软中断的一个检查点就是 do_IRQ 退出的时候),于是导致了一些问题:软中断不能睡眠、不能阻塞。由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。但可阻塞函数不能用在中断上下文中实现,必须要运行在进程上下文中,例如访问磁盘数据块的函数。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。

    因此在 2.6 版的内核中出现了在内核态运行的工作队列(替代了 2.4 内核中的任务队列)。它也具有一些可延迟函数的特点(需要被激活和延后执行),但是能够能够在不同的进程间切换,以完成不同的工作。

    参考文献




     This document is available from http://home.ustc.edu.cn/~boj/courses/linux_kernel/2_int.html
    展开全文
  • 中断机制和中断描述符表、中断和异常的处理

    千次阅读 多人点赞 2013-09-16 10:09:26
    Intel x86 系列微机共支持256 种向量中断,为使处理器较容易地识别每种中断源,将它们从0~256 编号,即赋予一个中断类型码 n,Intel 把这个8 位的无符号整数叫做一个向量,因此,也叫中断向量。所有256 种中断可...
  • 中断系统

    2018-11-22 13:53:34
    51单片机中断系统中有5个中断源,2个优先级,可实现二级中断嵌套。 中断请求标志 ①TF0/TF1 :T0/T1 的溢出中断请求标志。从初值做加1计数,计满溢出后TF0/TF1置1,发出中断请求,响应中断后硬件自...
  • 中断控制 ( 基于 S3C6410 开发板 ) 1. 关闭中断的两个步骤 (1) 关闭中断步骤 2. CPRS 寄存器中的中断控制位 (1) CPRS 寄存器位 3. (1) CPRS 寄存器位 一. 中断控制 ( 基于 S3C6410 开发板 ) ...
  • 中断系统与按键中断

    2019-09-06 21:49:37
    中断系统与按键中断控制LED
  • 51单片机之中断系统

    千次阅读 2019-03-25 18:38:39
    MCS-51单片机设置了5个中断源,其中内部有3个中断源,包括定时/计数器T0、T1和片内串口,外部有两个中断请求输入,当系统产生中断时,5个中断源的中断请求标志分别由特殊功能寄存器TCON和SCON的相应位来锁存。...
  • 单片机学习笔记--外部中断,定时器,串口中断 CPU收到中断请求,停下正在处理的工作A,去处理事件B,处理完后继续回到中断的地方继续执行事件A的过程,称为中断 ...5个中断源符号、名称及产生的条件如下。
  • Latex相关符号

    千次阅读 2017-03-23 22:30:11
    函数、符号及特殊字符 声调 语法 效果 语法 效果 语法 效果 \bar{x} latex数学符号表(2)" style="border:none; max-width:100%"> \acute{\eta} latex数学符号表(2)" style="border:none; max...
  • LaTeX 各种命令,符号

    万次阅读 多人点赞 2016-06-12 15:27:42
    函数、符号及特殊字符 声调 语法 效果 语法 效果 语法 效果 \bar{x} latex数学符号表(2)" style="border:none; max-width:100%"> \acute{\eta} latex数学符号表(2)" style="border:none; max...
  • 一,中断的定义 二 ,中断处理的过程 三,8086/8088CPU可以处理256种不同类型的终端 四,中断服务程序的设计方法 五中断向量表的建立
  • ARM-中断

    千次阅读 2016-04-18 19:21:15
    ARM-中断GIC:中断源管理系统 ICD:中断混合器 ICC:中断接口 SGI:软中断 PPI:私有中断 SPI:共享中断 A9中断执行流程设置中断源 GPX1.CON |= (0xf ); //设置GPIO为中断模式 EXT_INT41_CON = (EXT_INT41_CON ...
  • 51单片机中断

    千次阅读 多人点赞 2021-01-27 23:59:10
    51单片机中断51单片机中断原理中断的概念:中断作用中断源及相关寄存器中断源及优先级定时器/计数器控制寄存器 TCON中断允许寄存器 IE中断优先寄存器 IP工作方式寄存器TMOD定时器初值寄存器THx 和 TLx计数器初值的...
  • 中断应用—时钟中断

    千次阅读 2019-08-20 15:40:54
    在所有的外部中断中,时钟中断起特殊的作用。因为计算机是以精确的时间进行数值运算和数据处理的,最基本的时间单元是时钟周期,例如取指令、执行指令、存取内存等,这里讨论的是操作系统建立的时间系统,这个时间...
  • 51单片机中断系统

    2013-06-29 18:05:43
    5个中断源符号、名称及产生的条件如下: INT0:外部中断0,由P3.2端口线引入,低电平或下跳沿引起。 INT1:外部中断1,由P3.3端口线引入,低电平或下跳沿引起。 T0:定时器/计数器0中断,由T0计满回零引起。 T1...
  • 单片机中断技术

    千次阅读 2011-08-30 16:28:15
    以52单片机来说,一共有6个中断源,其说明如下(序号用于中断程序的编写): 中断源名称 默认级别 序号 说明 INT0 最高 0 外部中断0,由P3.2端口线引入,低电平或...
  • 51单片机中断详解

    千次阅读 多人点赞 2019-06-03 14:37:17
    中断源 默认中断级别 序号(C语言用) INT0---外部中断0 最高 0 T0---定时器/计数器0中断 第2 1 INT1---外部中断1 第3 2 T1----定时器/...
  • 二、中断源与内部寄存器 1.中断源 52单片机共有6个中断源中断源 解释 端口线 INT0 外部中断0,低电平或下降沿引起 P3.2 INT1 外部中断1,低电平或下降沿引起 P3.3 T0 定时器/计数器0,由T0计数器计满回...
  • 中断源:引起CPU中断的根源。 断点:被中断的地方。 优点 分时操作:可以分时为多个I/O设备服务 实时响应 可靠性高 中断源 五个中断源(2个优先级,二级中断嵌套) 外部中断 INT0‾\overline{INT0}INT0 INT1‾\...
  • 中断源:引起MCU发生中断的事件。以KEA128为例,中断源分为内核中断,非内核中断。 内核中断:内核中断主要是异常中断,在出现错误时候会触发中断,可能会复位芯片或者做出其他操作。 非内核中断:指MCU的各个模块被...
  • TX2440 看手册学习2440-深入理解中断处理机制(ADS...中断源未决寄存器:一个SRCPND中断源未决寄存器只有32位,是根本不够放60个中断源的.所以三星公把一些相似的中断源放到另外一些寄存器里成为一组,给这些组员集合定一
  • ESP32 官方文档(十一)高级中断

    千次阅读 2018-09-02 16:29:25
    在 ESP32 上,中断复用器允许使用中断分配器将大多数中断源路由到这些中断. 通常,中断将以 C 语言写入,但 ESP-IDF 也允许在汇编中写入高级中断,从而允许非常低的中断延迟. 中断级别 级别 标识 ...
  • 51单片机中断机制(定时器)

    千次阅读 2020-07-10 11:00:27
    52单片机一共有6个中断源,它们的符号,名称以及各产生的条件分别如下: INT0 - 外部中断0,由P3.2端口线引入,低电平或下降沿引起 INT1 - 外部中断1,由P3.3端口线引入,低电平或下降沿引起 T0 - 定时器/计数器0...
  • BIOS中断调用

    千次阅读 2010-04-21 21:38:00
    BIOS和DOS中断大全DOS中断:1、字符功能调用类(Character-Oriented Function)01H、07H和08H —从标准输入设备输入字符02H —字符输出03H —辅助设备的输入04H —辅助设备的输出05H —打印输出06H —控制台输入/输出...
  • POWERPC中断

    千次阅读 2012-05-23 14:59:48
    从CPU的e500核的角度,中断源分为自己内核产生的异常和 PIC提供的中断。   异常,是e500核产生的,它是同步产生的(可以预知的),如非法指令,或访问存储器时出现TLB Miss等情况 中断,是e500核外部引脚产生的...
  • Linux 内核中断内幕

    千次阅读 2016-03-16 18:00:50
    本文对中断系统进行了全面的分析与探讨,主要包括中断控制器、中断分类、中断亲和力、中断线程化与 SMP 中的中断迁徙等。首先对中断工作原理进行了简要分析,接着详细探讨了中断亲和力的实现原理,最后对中断线程化...
  • 函数、符号及特殊字符 声调 语法 效果 语法 效果 语法 效果 \bar{x} \acute{\eta} \check{\alpha} \grave{\eta} \breve{a} \ddot{y} \dot{x} \hat{\alpha} \t...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 53,428
精华内容 21,371
关键字:

中断源符号