• 中断与异常(big hole)


    一些概念

    中断:一件可以改变处理器执行指令顺序的事件,对应于CPU内外部硬件电路产生的电信号。分为同步中断异步中断。

    • 同步中断是CPU控制单元产生,只有在一条指令中止执行后CPU才会发出中断。
    • 异步中断由其他硬件设备遵照CPU时钟信号随机产生。
      Intel微处理器将同步和异步中断分别称为异常和中断。中断由间隔计时器和I/O设备产生。异常由程序错误产生,或由内核必须处理的异常条件产生,第一种情况,内核发送信号;第二种情况,内核执行恢复异常所需的所有步骤。

    中断的作用

    • 内核的目标是尽快的处理完中断或尽可能把更多的处理向后推迟,内核响应中断进行的操作分为两部分:关键部分立即执行,其余部分随后执行。
    • 内核处理一个中断时,可能会有另一个中断发生,允许这种情况可以维持更多I/O设备处于忙状态。因此中断处理程序应当使内环控制路径能以嵌套方式执行。
      *由于内核代码存在一些临界区,在临界区内中断必须被禁止。临界区应当尽可能限制。因为内核应该大多数时候以允许中断方式运行。

    异常与中断

    • 中断
      *可屏蔽中断(maskable):I/O设备发出的所有中断请求(IRQ)都产生可屏蔽中断。又处于两种状态,屏蔽的,非屏蔽的。只要屏蔽的中断还是屏蔽的,控制单元就忽略它。
      *不可屏蔽中断:极少数的关键事件才能产生,总是由CPU识别。
      *异常
      *处理器探测异常:CPU执行指令时探测到反常条件产生的异常。可分为3组。
      *故障(fault):通常可以纠正,程序可以在不失连贯性的情况下重新开始。eip保存引发故障的指令地址。当异常处理程序终止时,那条指令会重新执行。
      *陷阱(trap):陷阱指令执行后立即报告,内核交还控制权后不是连续性继续执行。eip保存下条指令地址。主要用途是调试程序。
      *异常中止(abort):用于报告严重错误,eip不保存异常指令的确切位置。控制单元发送紧急中断信号,异常处理程序强制进程中止。
      *编程异常:在程序员发出请求时产生,也称软中断,用于执行系统调用以及向调试程序发送特定事件。。由int, int3指令触发。控制单元将编程异常作为陷阱处理。

    IRQ与中断

    能发出中断请求的硬件设备至少有一条IRQ输出线。所有IRQ线都与可编程中断控制器(PIC)输入引脚相连,执行下列操作

    1. 监视IRQ,如果两条以上线产生信号,选择IRQ编号小的线。
    2. IRQ线上的信号:
      a. 信号转化为对应的向量(0~255 8-bit标识符)
      b. 将向量存放在中断控制器I/O端口,允许CPU读取向量
      c. 信号发送到处理器INTR引脚,即产生中断
      d. 等待(PIC与设备控制器阻塞)CPU将中断信号写进PIC的I/O端口以确认;清INTR线
    3. IRQ从0开始,IRQn关联的向量是n+32
      可以禁止IRQ线,PIC停止对指定IRQ发布中断,但中断并未丢失。

    异常

    x86发布约20种异常,内核应为每种异常提供专门的异常处理程序。
    中断描述符表(IDT),与每个中断或异常向量相联系,存有相应的中断或异常处理程序入口地址。内核激活中断前,适当初始化IDT。idtr指定IDT线性机制及最大长度,Intel包含3种类型描述符。
    ![a](4.2 4-2)
    任务门:中断发生时,存放取代当前进程的新进程TSS选择符
    中断门:包含中断异常处理程序的段选择符与段内偏移。当控制转移到适当的段,处理器重置IF标志,关闭可屏蔽中断
    陷阱门:和中断门类似,但是不改变IF标志

    中断和异常处理程序嵌套执行

    每个终端或异常都会引发内和控制路径,或代表当前进程在内核态执行的单独指令序列。内和控制路径可以任意嵌套,即一个中断处理程序可以被另一个中断处理程序中断。处理中断时,第一部分指令是把寄存器内容保存到内核堆栈,后一部分是恢复寄存器内容并将CPU返回用户态。但是嵌套深度大于1时,最后一部分指令将执行上次被打断的内核控制路径。
    ![a](4.3 4-3)
    这就要求中断处理程序运行期间不能发生进程切换。一个中断处理程序可以抢占其他的中断处理程序,也可以抢占异常处理程序。异常处理程序从不抢占中断处理程序。唯一内核态异常是缺页异常,中断处理程序不执行导致缺页(进程切换)操作。处理缺页异常时,内核可以挂起当前进程,使用另一个进程代替,直到请求的页可以使用。被挂起的进程获得处理器,相应的内核控制路径可以恢复执行。
    交错执行内核控制路径可以:

    1. 提高中断控制器和设备控制器的吞吐量。内核正在处理一个中断也能发送应答,减少阻塞。
      2.实现无优先级的中断模型,简化代码,提高可移植性。
      多处理器系统中,几个内核控制路径可以并发执行。与异常相关的内核控制路径可以由于进程切换在不同的CPU上执行。

    初始化IDT

    Linux将中断描述符如下分类:

    • 中断门:用户态进程不能访问的一个Intel中断门(DPL = 0)。激活所有Linux中断处理程序,限制在内核态
    • 系统门:用户态进程可以访问的一个Intel陷阱门(DPL = 3)。激活3个Linux异常处理程序,分别是向量4,5,128,对应汇编指令的into,bound以及int $0x80
    • 系统中断门:用户态进程可以访问的Intel中断门(GPL = 3)。激活向量3的异常处理程序,对应int3指令
    • 陷阱门:用户态进程不能访问的一个Intel陷阱门(DPL = 0)。激活大部分Linux异常处理程序
    • 任务门:用户态进程不能访问的Intel任务门(DPL = 0)。激活Linux的Double Fault异常处理程序
      IDT存放在idt_table中,有256个表项。第一次初始化由内核将同一个中断门(指向ignore_int()中断处理程序)填充到256个表项。紧接着内核将进行第二次初始化,用有意义的程序替换。

    异常处理

    当异常发生时,内核向引发异常的进程发送一个信号,。由以下三部分组成:

    1. 在内核堆栈中保存大多数寄存器内容(汇编实现)
    2. 使用C函数处理异常
    3. 通过ret_from_exception()从异常处理函数退出

    中断处理

    向进程发送信号以通知中断的方法是无效的,因为进程可能被挂起。主要关注三种类型的中断:I/O中断,时钟中断,处理期间中断。

    I/O中断

    一般要求I/O中断处理程序具有足够的灵活性来同时给多个设备提供服务,实现方式有两种:

    1. IRQ共享:中断处理程序执行多个中断服务例程(ISR),每个ISR关联一个单独设备,设备间共享IRQ。无法知道那个设备产生IRQ,因此每个ISR都被执行以检验设备是否需要关注。
      2.IRQ动态分配:IRQ在最后时刻才与设备关联。
      中断发生时并不是所有操作具有相同的紧迫性。中断处理程序代表进程执行,因此总是处于TASK_RUNNING状态,因此不能有阻塞过程,必须由中断处理程序执行的操作称为top_half,其余的称为bottom-half。Linux把中断要执行的操作分为三类:
    • 紧急的:立即在中断处理程序内执行,在禁止可屏蔽中断情况下。
    • 非紧急的:立即被中断处理程序执行,在激活中断的情况下。
    • 非紧急可延迟的:可以延迟较长时间而不影响内和操作。

    I/O中断执行以下基础操作:

    1. 将IRQ值以及寄存器内容保存到内核栈
    2. 向对应的PIC应答,允许其进一步发生中断
    3. 检查共享IRQ设备的ISR
    4. 跳转到ret_from_intr(),终止
      ![a](4.6 4-4)

    IRQ数据结构

    每个中断向量有自己的irq_desc_t描述符,共同组成了irq_desc数组。
    ![a](4.6 4-5)
    意外中断,与某个IRQ相关的ISR不存在,或者所有ISR都都无法辨别是否是自身设备发出中断。办法是禁用IRQ;对于共享IRQ,当意外中断达到一定次数才禁用IRQ。

    软中断(softirq),tasklet和工作队列

    用于处理中断的bottom-half工作。软中断是一套静态定义的bottom-halves(机制),在编译期间静态注册,可以在不同处理器上同时运行(即使类型相同);tasklets是基于softirq动态创建的具有灵活性的bottom_halves,可以动态注册,只有不同种类的tasklets可以在不同的处理器上同时运行,是性能和易用性的折衷。大多数情况,tasklet就够用了。

    软中断

    softirq由softirq_action结构表示:

    struct softirq_action {
        void (*action)(struct softirq_action *);
    };
    
    static struct softirq_action softirq_vec[NR_SOFTIRQS];
    /*每个注册的softirq对应数组中的一项。在2.6内核中,一共有9个软中断*/
    

    softirq不会抢占其他的softirq,只用中断处理程序才可以抢占softirq。已经注册的softirq在执行前必须被标记,也称为触发软中断,标记后处于pengding状态。一般情况下,中断处理程序再返回前标记它的软中断,软中断会在合适的时间运行,一般有三种情况:

    1. 从中断处理程序代码路径返回时
    2. 在ksoftirq内核线程
    3. 任何显式检查与执行挂起软中断的代码处

    软中断的执行发生在__do_sofeirq()函数中,主体代码如下:

    pending = local_softirq_pending(); /*32-bit mask of pending irqs,if bit n is set,the n-th irq is pending */
    if (pending) {
        struct softirq_action *h;
        set_softirq_pending(0);/* reset the pending bitmask */
        h = softirq_vec;/*first entry of softirq_vec*/
        do {
            if (pending & 1)
                h->action(h); /*if bit is set,call action*/
            h++;
            pending >>= 1;
        } while (pending);
    }
    

    softirq 用于时间敏感的bottom-half操作。2.6版本中,网络和块设备直接使用了softirq,内核计时器以及tasklet建立在softirq之上。

    tasklets

    Tasklets主要基于两种softirq,HI_SOFTIRQ和TASKLET_SOFTIRQ,前者有更高优先级。

    struct tasklet_struct {
        struct tasklet_struct *next; /* next tasklet in the list*/
        unsigned long state; /* state TASKLET_STATE_SCHED(shed to run),TASKLET_STATE_RUN(running(only mprocessor))*/
        atomic_t count; /* reference counter,if nonzero disabled cannot run;else enabled can run if marked pending*/
        void (*func)(unsigned long); /* tasklet handler function */
        unsigned long data; /* argument to the tasklet function */
    };
    

    Tasklets 调度
    __tasklet_schedule() 和__tasklet_hi_schedule(),对应TASKLET_SOFTIRQ和HI_SOFTIRQ。

    ksoftirq内核线程
    软中断可以在运行时触发自身并重新运行。如果持续执行重复的软中断,其他用户态进程可能得不到处理;如果选择不执行重复的软中断,直到下次处理pending softirq时才处理,造成冲断处理不及时。最终采用了折衷的方案,重复的softirq不会立即执行。如果softirq数量太多,内核唤醒一族内核线程来处理负载,内核线程处于最低优先级,一个处理器对应一个线程:

    for (;;) {
        if (!softirq_pending(cpu))
            schedule();
        set_current_state(TASK_RUNNING);
        while (softirq_pending(cpu)) {
            do_softirq();
            if (need_resched())
                schedule();
        }
        set_current_state(TASK_INTERRUPTIBLE);
    }
    

    Work queues

    Work queues将工作推迟到内核线程,总是运行在进程上下文,因此可以被调度及挂起。workqueue处理函数无法访问用户态内存,因为没有为内核线程映射用户态内存。
    一般工作被推迟到缺省线程上,对性能要求较高的工作可以创建自己的线程。相关结构workqueue_struct,cpu_workqueue_struct, work_struct都在kernel/workqueue.c。缺省工作线程是events类型,每个工作线程对应一个cpu_workqueue_struct结构。每种工作线程对应一个workqueue_struct结构。

    从异常和中断返回

    返回阶段的主要目的是恢复某个程序的执行。但是需要使用一些标志来记录其他的请求,如挂起进程的切换请求,挂起的信号等等。存放在thread_info的flags字段中。完成返回任务的汇编代码不是一个函数,因为控制权不会返回给其调用者。有两个不同的入口,分别是ret_from_intr()和ret_from_exception()
    ![](4.9 4-6)

  • 相关阅读:
    堆得简单介绍以及堆排序
    从交换两个变量的值来看程序员的“奇技淫巧”
    两道有意思的题目
    mac上使用brew
    安全考虑,binlog_row_image建议尽量使用FULL
    Python3.6新特性
    自动化测试基础篇--Selenium浏览器操作
    自动化测试基础篇--Selenium Xpath定位
    自动化测试基础篇--Selenium元素定位
    自动化测试基础篇--Selenium Python环境搭建
  • 原文地址:https://www.cnblogs.com/zyfgs2012/p/4083842.html
Copyright © 2020-2023  润新知