Linux中断技术、异常控制技术总结归类

相关学习资料

《深入理解计算机系统(原书第2版)》.pdf
http://zh.wikipedia.org/zh/%E4%B8%AD%E6%96%B7
独辟蹊径品内核:Linux内核源代码导读 李云华著 中文 PDF版
https://www.kernel.org/
http://blog.csdn.net/orange_os/article/details/7485069
http://blog.csdn.net/sunnybeike/article/details/6958473

目录

1. 从异常控制流开始说起
2. 中断类型
3. 中断的初始化
4. 异常控制类型

1. 从异常控制流开始说起

0x1: 异常控制流简介

从给处理器加电开始,知道断电为止,程序计数器假设一个值的序列:
A0, A1, ...., An-1
其中,每个Ak是某个相应的指令Ik的"地址"。每次从Ak到Ak+1的过渡称为控制转移(control transfer)。这样的控制转移序列叫作处理器的控制流(flow of control或control flow)
控制流可以大致分为:

1. "平滑的"序列(即顺序执行)
其中每个Ik和Ik+1在存储器中都是相邻的,CPU按照计数器进行逐条指令的依次执行
2. 非平滑控制流
Ik+1与Ik不相邻,是由诸如跳转、调用、和返回这样一些程序指令造成的。这样一些指令都是必要的机制,使得程序能够对由程序变量表示的内部状态中的变化做出反应。 

但是系统也必须能够对系统状态变化做出反应,这些系统状态不是被内部程序变量捕获的,而且也不一定和程序的执行相关。比如:

但是系统也必须能够对系统状态变化做出反应,这些系统状态不是被内部程序变量捕获的,而且也不一定和程序的执行相关。比如:
1. 一个硬件定时器定期产生信息,这个事件必须得到处理。
2. 数据包到达网络适配器后,必须存放在存储器中。
3. 程序向磁盘请求数据,然后休眠,知道被通知数据已就绪。
4. 当子进程终止时,创造这些子进程的父进程必须得到通知。

现代系统通过使控制流发生突变来对这些情况做出反应(也就是异常控制流)。一般而言,我们把这些突变称为异常控制流(Exceptional Control Flow ECF)。异常控制流发生在计算机系统的各个层次。比如:

1. 在硬件层,硬件检测到事件时,会触发控制,使CPU突然转移到异常处理程序。
2. 在操作系统层,内核通过上下文切换将控制从一个用户进程转移到另一个用户进程(分时间片执行)。
3. 在应用层,一个进程可以发送信号到另一个进程,而接收者会将控制突然转移到它的一个信号处理程序。一个程序可以通过回避通常的栈规则,并执行到其他函数中任意位置的非本地跳转来对错误做出反应。

0x2: 异常控制流处理机制和中断技术的关系

在继续深入学习之前,我们必须先理清一个基本概念:

异常控制流是操作系统中的一种控制流处理机制,异常控制流处理机制被用于实现操作系统中的CPU处理流程切换的实现,而中断技术是实现这一技术的方法。即中断是一个技术,而异常控制流处理是一种机制

2. 中断类型

中断是现代操作系统的一项重要技术,利用中断技术可以极大地提高系统吞吐量。从本质上理解,中断是CPU提供的一项硬件机制,CPU可以根据中断号跳转到相应的中断处理例程上去

0x1: 中断硬件实现

在硬件实现上,中断可以是:

1. 包含控制线路的独立系统(本文重点学习的)
在IBM个人机上,广泛使用可编程中断控制器(Programmable Interrupt Controller,PIC)来负责中断响应和处理。PIC被连接在若干中断请求设备(各种外设)和处理器(CPU)的中断引脚之间,从而实现对处理器中断请求线路(多为一针或两针)的复用

2. 被整合进存储器子系统中
作为另一种中断实现的形式,即存储器子系统实现方式,可以将中断端口映射到存储器的地址空间,这样对特定存储器地址的访问实际上是中断请求

0x2: 中断分类

从实现机制上来分,中断可以分为以下2类:

1. 外部中断(包括可屏蔽、不可屏蔽中断)
外部中断是由外部设备引发的中断,而引发中断的设备被称为中断源,中断源大致可以分为以下几种
    1) 定时器、计时器
    2) 键盘
    3) 内部实时时钟
    4) 通用接口
    5) PS/2鼠标
    6) 协处理器
    7) IDE/SATE硬盘
    8) 串口
    9) 并口
    10) 软盘
    ...
外部设备通过"可编程中断控制器(Programmable Interrupt Controller,PIC)"向CPU报告的中断,大致流程如下:
    1) 外部设备通过中断请求线(IRQ)连接到一个"中断控制器"上
    2) 当一个外部设备需要发出中断时,会驱动对应的中断请求线进入有信号状态
    3) 中断控制器检测这个中断是否被屏蔽了(CPU的IF位被置1,则不屏蔽任何外部中断;CPU的IF位被置0,则屏蔽所有外部中断),如果没有被屏蔽就驱动CPU的"INTR中断请求线"进入信号状态
    4) CPU随后就能检测到这个中断了(在每次CPU周期的下降沿检测一次中断)
    5) 如果该中断被屏蔽(CPU中的中断屏蔽寄存器被选中),中断控制器中的寄存器中的某一位位将记录这一请求,等到中断被开启时再驱动CPU的"INTR中断请求线"进入信号状态
    6) 之后CPU通过"中断应答"从中断控制器的数据线上读取中断号,并通过中断号获取中断向量
    7) 如果多个设备(外设中断源)在同一时刻通过不同的中断请求线发出中断请求,中断控制器也会将这些请求记录在不同的位中
    8) 如果这些中断都没有被屏蔽,则中断控制器根据优先级,依次执行优先级高的中断(IRQn的数字n越小优先级越大)
/*
关于中断屏蔽,这里需要补充几点
关闭外部中断的方式有:
1. 通过cli指令把标志寄存器中的IF位清零,这样就关闭了"所有的"外部中断
2. 通过中断控制器中的中断屏蔽寄存器,屏蔽某一特定的IRQn,从而屏蔽该中断(只是屏蔽某个中断,不影响其他中断)
3. 在很多外设上,也设有控制寄存器,可以通过外设的中断控制器从数据源上关闭外设上的某个IRQn,从而屏蔽某个中断
*/

2. 内部中断
和外部中断相对的就是内部中断。从CPU的角度看,外部中断是一个异步事件,它可能在任何时候发送,而内部中断是一个同步事件,它是执行某条指令时产生的。
内部中断可以大致分为以下几种
    1) 异常(faults)
    CPU在指令执行时产生的,异常是可以修复的。当异常发生时,压入堆栈的是产生异常的"那条指令",当CPU执行异常处理程序结束后,将"重新执行那一条指令"。
        1.1) 缺页异常: 14: #PF
        1.2) 保护错误(内存或其他保护检查): 13: #GP
        1.3) 堆栈段错误(堆栈操作或者加载SS): 12: #SS
        1.4) 段不存在(加载段寄存器后访问段): 11: #NP
        1.5) 除法错误(DIV/IDIV指令): 0: #DE
        1.6) 越界: 5: #BR
        1.7) 无效操作码(无效操作指令): 6: #UD
        1.8) 对齐校验(内存访问): 17: #AC
    2) 陷阱(traps)
    在CPU执行陷阱指令后,立刻通过中断描述表执行预定的陷阱处理例程。陷阱处理例程执行结束后,将返回陷阱指令的"下一条指令"继续执行。
        2.1) 系统调用(system call)
        系统调用是一种软中断,软中断是一条CPU指令,用以自陷一个中断。由于软中断指令通常要运行一个切换CPU至内核态(Kernel Mode/Ring 0)的子例程,它常被用作实现系统调用(System call)     这是最长使用到的中断,我们在编程中使用到的API最终都会通过系统调用这种内部中断来进行ring3到ring0的切换
        2.2) 单步异常(调试异常): 1: #DB
        用于单步执行、内存断点
        2.3) INT3(断点异常): 3: #BP
        2.4) 溢出(指令INT0): 4: #OF
    3) 终止(aborts)
        3.1) 双重错误(所有能产生异常、NMI、或者INTR的指令): 8: #DF
/*
关于中断号(向量号)、中断向量表、中断描述符表的区别
1. 中断号(向量号)
中断号(向量号)是用来在中断描述符表中定位中断描述符的

2. 中断描述符表
保存中断描述符的一段连续内存(可以理解为一张表)。中断描述符是用来获取中断向量用的(知道了中断向量就知道中断服务程序的入口地址)

3. 中断向量表
保存中断向量的一段连续内存(可以理解为一张表)。中断向量代表着中断服务程序的入口地址

例如:
INT 21H
21就是中断号
21H就就是一个中断描述符
21H*4 =84H 得到的就是中断向量
以84H为首地址(84H 85H 86H 87H)其中存放的就是中断服务程序的地址
*/

3. 中断的初始化

我们知道,中断向量号是8位的,那么它一共有256项(0-255),也就意味着中断描述符表也是256项(其中记录着中断向量表的表项索引),同时中断向量表也是256项(其中记录着中断处理例程的入口地址)

对于不同的中断,在中断初始化和中断处理过程中,其处理方式是不一样的

1. 内部中断(0~31号、0x80作为中断号)
只要初始化:
    1) 相关的中断向量表
2. 外部中断(0~255中的除了0~31号、0x80的其他中断号)
需要初始化:
    1) 相关的中断向量表
    2) 以及中断控制器(控制器负责优先级排队、屏蔽等工作)

0x1: 内部中断初始化

内部中断的初始化需要对0~31号和0x80号系统保留中断向量的初始化,这部分草走在trap_init()中完成

\linux-3.15.5\arch\x86\kernel\traps

void __init trap_init(void)
{
    int i;

#ifdef CONFIG_EISA
    void __iomem *p = early_ioremap(0x0FFFD9, 4);

    if (readl(p) == ‘E‘ + (‘I‘<<8) + (‘S‘<<16) + (‘A‘<<24))
        EISA_bus = 1;
    early_iounmap(p, 4);
#endif
/*
trap_init()主要是调用set_xxx_gate(中断向量, 中断处理函数)
set_xxx_gate()就是按照中断门的格式填写中断向量表的

Intel x86支持4种"门描述符":
1) 调用门(call gate)
2) 陷阱门(trap gate)
3) 中断门(iinterrupt gate)
4) 任务门(task gate)
*/
    set_intr_gate(X86_TRAP_DE, divide_error);
    set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
    /* int4 can be called from all */
    set_system_intr_gate(X86_TRAP_OF, &overflow);
    set_intr_gate(X86_TRAP_BR, bounds);
    set_intr_gate(X86_TRAP_UD, invalid_op);
    set_intr_gate(X86_TRAP_NM, device_not_available);
#ifdef CONFIG_X86_32
    set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
#else
    set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
#endif
    set_intr_gate(X86_TRAP_OLD_MF, coprocessor_segment_overrun);
    set_intr_gate(X86_TRAP_TS, invalid_TSS);
    set_intr_gate(X86_TRAP_NP, segment_not_present);
    set_intr_gate_ist(X86_TRAP_SS, &stack_segment, STACKFAULT_STACK);
    set_intr_gate(X86_TRAP_GP, general_protection);
    set_intr_gate(X86_TRAP_SPURIOUS, spurious_interrupt_bug);
    set_intr_gate(X86_TRAP_MF, coprocessor_error);
    set_intr_gate(X86_TRAP_AC, alignment_check);
#ifdef CONFIG_X86_MCE
    set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK);
#endif
    set_intr_gate(X86_TRAP_XF, simd_coprocessor_error);

    /* Reserve all the builtin and the syscall vector: */
    for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
        set_bit(i, used_vectors);

#ifdef CONFIG_IA32_EMULATION
    set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
    set_bit(IA32_SYSCALL_VECTOR, used_vectors);
#endif
//设置系统调用中断
#ifdef CONFIG_X86_32
    set_system_trap_gate(SYSCALL_VECTOR, &system_call);
    set_bit(SYSCALL_VECTOR, used_vectors);
#endif

    /*
     * Set the IDT descriptor to a fixed read-only location, so that the
     * "sidt" instruction will not leak the location of the kernel, and
     * to defend the IDT against arbitrary memory write vulnerabilities.
     * It will be reloaded in cpu_init() */
    __set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO);
    idt_descr.address = fix_to_virt(FIX_RO_IDT);

    /*
     * Should be a barrier for any external CPU state:
     */
    cpu_init();

    x86_init.irqs.trap_init();

#ifdef CONFIG_X86_64
    memcpy(&debug_idt_table, &idt_table, IDT_ENTRIES * 16);
    set_nmi_gate(X86_TRAP_DB, &debug);
    set_nmi_gate(X86_TRAP_BP, &int3);
#endif
}

0x2: 外部中断初始化

外部中断的初始化需要:

1. 对除了0~31、0x80中断号之外的其它中断向量
2. 中断控制器的初始化(相比内部中断初始化多了这一步)这两步操作都在在init_IRQ()中完成

\linux-3.15.5\arch\x86\kernel\i8259.c

void __init init_IRQ(void)
{
    int i;

    /*
     * We probably need a better place for this, but it works for
     * now ...
     */
    x86_add_irq_domains();

    /*
     * On cpu 0, Assign IRQ0_VECTOR..IRQ15_VECTOR‘s to IRQ 0..15.
     * If these IRQ‘s are handled by legacy interrupt-controllers like PIC,
     * then this configuration will likely be static after the boot. If
     * these IRQ‘s are handled by more mordern controllers like IO-APIC,
     * then this vector space can be freed and re-used dynamically as the
     * irq‘s migrate etc.
     */
    for (i = 0; i < legacy_pic->nr_legacy_irqs; i++) //对于单CPU结构,
        per_cpu(vector_irq, 0)[IRQ0_VECTOR + i] = i;

    //x86_init.irqs.intr_init()等价于调用:native_init_IRQ()
    x86_init.irqs.intr_init();
}

void __init native_init_IRQ(void)
{
    int i;

    /* Execute any quirks before the call gates are initialised: */
    x86_init.irqs.pre_vector_init(); //调用 init_ISA_irqs 

    apic_intr_init();  

    /*
     * Cover the whole vector space, no vector can escape
     * us. (some of these will be overridden and become
     * ‘special‘ SMP interrupts)
     */
     /*
     interrupt数组,它保存的是每个中断服务程序的入口地址,它的定义是在\linux-3.15.5\arch\x86\kernel\entry_32.S中
     */
    for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++)
    {
    //设置32~255号中断
        /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
        if (!test_bit(i, used_vectors))
    {
        //要除去0x80中断
        set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);
    }
    }

    if (!acpi_ioapic && !of_ioapic)
        setup_irq(2, &irq2);

#ifdef CONFIG_X86_32
    /*
     * External FPU? Set up irq13 if so, for
     * original braindamaged IBM FERR coupling.
     */
    if (boot_cpu_data.hard_math && !cpu_has_fpu)
        setup_irq(FPU_IRQ, &fpu_irq);

    irq_ctx_init(smp_processor_id());
#endif
}

4. 异常控制类型

我们已经学习了中断有内部和外部中断,外部中断来自于硬件外设,内部中断又可以分为异常(faults)、陷阱(traps)、终止(aborts)。我们需要牢记的是,操作系统是不能执行命令的,整个机器中可以执行命令的只有是CPU,CPU通过硬件的方式提供中断机制,中断是所有异常处理的根本技术。不管是进程切换、硬件外设、除零异常、虚拟内存中的发生的缺页异常处理、SEH,归根结底,全部都要通过CPU的中断机制来实现。

异常控制流处理就是我们在中断的基础上,抽象出的一个理论性概念

异常(exception)就是控制流中的突变,用来响应处理器状态中的某些变化。在处理器中,状态被编码为不同的位和信号。状态变化称为事件(event)。

我们在学习异常处理流程的时候,要注意不要把异常和中断割裂成2个独立的概念去学习,相反,异常和中断说的都是一回事。只是一个从操作系统概念层面去阐述,一个从硬件技术原理角度去阐述

在任何情况下,当处理器检测到有事件(内部异常、外部异常)发生时,它就会通过一张叫做异常表(exception table)的跳转表(中断描述符表),进行一个间接过程调用(异常处理例程的调用)(通过中断向量表),到一个专门用来处理这类事件的操作系统子程序(异常处理程序 exception handler)。进行相应的异常处理

0x1: 异常的分类

异常可以分为四类:

1. 中断(interrupt)
来自I/O设备的信号,异步,总是返回到下一条指令

2. 陷阱(trap)
有意的异常,是执行一条指令的结果。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫系统调用
同步, 总是返回到下一条指令

3. 故障(fault)
潜在可恢复的错误。如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。斗则,处理程序返回到内核中的abort例程,abort例程会终止引起故障的应用程序。
同步,可能返回到当前指令

4. 终止(abort)
不可恢复的错误,同步,不会返回

Copyright (c) 2014 LittleHann All rights reserved

Linux中断技术、异常控制技术总结归类

时间: 2024-10-29 10:47:40

Linux中断技术、异常控制技术总结归类的相关文章

深入理解Linux内核-中断和异常

Linux内核代码查看 http://androidxref.com/ 中断:被定义位一个事件,它能改变处理器执行指令的顺序.它对应硬件(CPU.其他硬件设备)电路产生的电信号. 同步中断:指令执行时CPU控制单元产生:称为同步,是因为只有在一条指令终止执行后CPU才回发出中断.也被称为异常 异步中断:其他硬件设备按照CPU时钟信号随机产生的.也被简称中断 中断的约束:1.中断必须尽快处理完成:中断一般被分两部分执行:关键而且紧急的部分,内核立即执行:其余部分内核稍后执行: 2.中断的处理必须能

Linux 2.6 内核阅读笔记 中断和异常

2014年7月24日 中断门.陷阱门及中断门 中断是可以禁止的,可以通过告诉PIC停止对某个中断的发布.被禁止的中断是不会丢失的,在解除禁止后又会发送到CPU上. 禁止中断和屏蔽(mask)中断的不同之处是屏蔽是忽略掉某个中断,而禁止相当于延迟发送. Intel提供了三种类型的中断描述符:任务门.中断门及陷阱门描述.linux使用与inten稍有不同的细分分类和术语,把他们进行如下分类: 中断门:用户态进程不能访问的一个intel中断门(DPL为0),所有的linux中断处理程序都通过中断门在内

linux总结-第一讲-中断和异常

中断与异常 一.中断(广义):会改变处理器执行指令的顺序,通常与CPU芯片内部或外部硬件电路产生的电信号相对应 中断--异步的:由硬件随机产生,在程序执行的任何时候可能出现 异常--同步的:在(特殊的或出错的)指令执行时由CPU控制单元产生 我们用"中断信号"来通称这两种类型的中断 二.中断信号的作用 中断信号提供了一种特殊的方式,使得CPU转去运行正常程序之外的代码 当一个中断信号到达时,CPU必须停止它当前正在做的事,并且切换到一个新的活动 在进程的内核态堆栈保存程序计数器的当前值

红帽Linux故障定位技术详解与实例(2)

红帽Linux故障定位技术详解与实例(2) 2011-09-28 14:26 圈儿 BEAREYES.COM 我要评论(0) 字号:T | T 在线故障定位就是在故障发生时, 故障所处的操作系统环境仍然可以访问,故障处理人员可通过console, ssh等方式登录到操作系统上,在shell上执行各种操作命令或测试程序的方式对故障环境进行观察,分析,测试,以定位出故障发生的原因. AD:2014WOT全球软件技术峰会北京站 课程视频发布 3.内核故障情形及处理 (1)内核panic panic是内

红帽Linux故障定位技术详解与实例(1)

红帽Linux故障定位技术详解与实例(1) 2011-09-28 14:26 圈儿 BEAREYES.COM 我要评论(0) 字号:T | T 在线故障定位就是在故障发生时, 故障所处的操作系统环境仍然可以访问,故障处理人员可通过console, ssh等方式登录到操作系统上,在shell上执行各种操作命令或测试程序的方式对故障环境进行观察,分析,测试,以定位出故障发生的原因. AD:2014WOT全球软件技术峰会北京站 课程视频发布 红帽Linux故障定位技术详解与实例是本文要介绍的内容,主要

Linux中断完全分析

学习本文可以对linux中断有全面而深刻的认识.本文对Linux中断所涉及的需求.管理机制.中断实现.中断接口(上半部和下半部).驱动使用进行完全分析. 一.中断需求 软件是需求驱动的,软件的出现是为了解决需求和问题的.先知道需求,那理解代码就是为了验证已知的问题:不知道需求,那理解代码就是揣摩设计者的目的.两者相比,前者自然效率跟高. 中断是为了解决异步的需求.紧急的事情或者更高优先级的事情需要先做,就代表异步.例如,手机需要时刻优先响应用户按键或者触摸这个事件,否则用户体验就变差.信号是软件

红帽Linux故障定位技术详解与实例(3)

红帽Linux故障定位技术详解与实例(3) 在线故障定位就是在故障发生时, 故障所处的操作系统环境仍然可以访问,故障处理人员可通过console, ssh等方式登录到操作系统上,在shell上执行各种操作命令或测试程序的方式对故障环境进行观察,分析,测试,以定位出故障发生的原因. AD:2014WOT全球软件技术峰会北京站 课程视频发布 5.用kdump工具内核故障定位实例 A) 部署Kdump 部署 kdump 收集故障信息的步骤如下: (1)设置好相关的内核启动参数 在 /boot/grub

Linux 中断详解 【转】

转自:http://blog.csdn.net/tiangwan2011/article/details/7891818 原文地址 http://www.yesky.com/20010813/192117.shtml 方法之三:以数据结构为基点,触类旁通 结构化程序设计思想认为:程序 =数据结构 +算法.数据结构体现了整个系统的构架,所以数据结构通常都是代码分析的很好的着手点,对Linux内核分析尤其如此.比如,把进程控制块结构分析清楚 了,就对进程有了基本的把握:再比如,把页目录结构和页表结构

掌握 Linux 调试技术

掌握 Linux 调试技术 在 Linux 上找出并解决程序错误的主要方法 Steve Best ([email protected])JFS 核心小组成员,IBM 简介: 您可以用各种方法来监控运行着的用户空间程序:可以为其运行调试器并单步调试该程序,添加打印语句,或者添加工具来分析程序.本文描述了几种可以用来调试在 Linux 上运行的程序的方法.我们将回顾四种调试问题的情况,这些问题包括段错误,内存溢出和泄漏,还有挂起. 本文讨论了四种调试 Linux 程序的情况.在第 1 种情况中,我们