Linux中断机制

1.中断概念

中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。Linux中通常分为外部中断(又叫硬件中断)和内部中断(又叫异常)。

在实地址模式中,CPU把内存中从0开始的1KB空间作为一个中断向量表。表中的每一项占4个字节。但是在保护模式中,有这4个字节的表项构成的中断向量表不满足实际需求,于是根据反映模式切换的信息和偏移量的足够使得中断向量表的表项由8个字节组成,而中断向量表也叫做了中断描述符表(IDT)。在CPU中增加了一个用来描述中断描述符表寄存器(IDTR),用来保存中断描述符表的起始地址。

2. Linux中断处理

  2.1 系统中断号

  由上述中断定义可知,系统中断向量表中共可保存256个中断向量入口,即IDT中包含的256个中断描述符(对应256个中断向量)。

  而0-31号中断向量被intel公司保留用来处理异常事件,不能另作它用。对这 0-31号中断向量,操作系统只需提供异常的处理程序,当产生一个异常时,处理机就会自动把控制转移到相应的处理程序的入口,运行相应的处理程序;而事实 上,对于这32个处理异常的中断向量,2.6版本的 Linux只提供了0-17号中断向量的处理程序,其对应处理程序参见下表、中断向量和异常事件对应表;也就是说,17-31号中断向量是空着未用的。

中断向量号 异常事件 Linux的处理程序
0 除法错误 Divide_error
1 调试异常 Debug
2 NMI中断 Nmi
3 单字节,int 3 Int3
4 溢出 Overflow
5 边界监测中断 Bounds
6 无效操作码 Invalid_op
7 设备不可用 Device_not_available
8 双重故障 Double_fault
9 协处理器段溢出 Coprocessor_segment_overrun
10 无效TSS Incalid_tss
11 缺段中断 Segment_not_present
12 堆栈异常 Stack_segment
13 一般保护异常 General_protection
14 页异常 Page_fault
15  (intel保留) Spurious_interrupt_bug
16 协处理器出错 Coprocessor_error
17 对齐检查中断 Alignment_check

  0-31号中断向量已被保留,那么剩下32-255共224个中断向量可用。 这224个中断向量又是怎么分配的呢?2.6版本的Linux中,除了0x80 (SYSCALL_VECTOR)用作系统调用总入口之外,其他都用在外部硬件中断源上,其中包括可编程中断控制器8259A的15个irq;事实上,当 没有定义CONFIG_X86_IO_APIC时,其他223(除0x80外)个中断向量,只利用了从32号开始的15个,其它208个空着未用。

 2.2 中断请求

  2.2.1 中断请求概述

  外部设备当需要操作系统做相关的事情的时候,会产生相应的中断。

  设备通过相应的中断线向中断控制器发送高电平以产生中断信号,而操作系统则会从中断控制器的状态位取得那根中断线上产生的中断。而且只有在设备在对某一条中断线拥有控制权,才可以向这条中断线上发送信号。也由于现在的外设越来越多,中断线又是很宝贵的资源不可能被一一对应。因此在使用中断线前,就得对相应的中断线进行申请。无论采用共享中断方式还是独占一个中断,申请过程都是先讲所有的中断线进行扫描,得出哪些没有别占用,从其中选择一个作为该设备的IRQ。其次,通过中断申请函数申请相应的IRQ。最后,根据申请结果查看中断是否能够被执行。

  2.2.2 中断相关结构

  中断中核心处理数据结构为irq_desc,它完整的描述了一条中断线,Linux 2.6。22.6中源码如下。

  irq_desc定义在include/linux/irq.h中

/**
 * struct irq_desc - interrupt descriptor
 *
 * @handle_irq:        highlevel irq-events handler [if NULL, __do_IRQ()]
 * @chip:        low level interrupt hardware access
 * @msi_desc:        MSI descriptor
 * @handler_data:    per-IRQ data for the irq_chip methods
 * @chip_data:        platform-specific per-chip private data for the chip
 *            methods, to allow shared chip implementations
 * @action:        the irq action chain
 * @status:        status information
 * @depth:        disable-depth, for nested irq_disable() calls
 * @wake_depth:        enable depth, for multiple set_irq_wake() callers
 * @irq_count:        stats field to detect stalled irqs
 * @irqs_unhandled:    stats field for spurious unhandled interrupts
 * @lock:        locking for SMP
 * @affinity:        IRQ affinity on SMP
 * @cpu:        cpu index useful for balancing
 * @pending_mask:    pending rebalanced interrupts
 * @dir:        /proc/irq/ procfs entry
 * @affinity_entry:    /proc/irq/smp_affinity procfs entry on SMP
 * @name:        flow handler name for /proc/interrupts output
 */
struct irq_desc {
    irq_flow_handler_t    handle_irq;
    struct irq_chip        *chip;
    struct msi_desc        *msi_desc;
    void            *handler_data;
    void            *chip_data;
    struct irqaction    *action;    /* IRQ action list */
    unsigned int        status;        /* IRQ status */

    unsigned int        depth;        /* nested irq disables */
    unsigned int        wake_depth;    /* nested wake enables */
    unsigned int        irq_count;    /* For detecting broken IRQs */
    unsigned int        irqs_unhandled;
    spinlock_t        lock;
#ifdef CONFIG_SMP
    cpumask_t        affinity;
    unsigned int        cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
    cpumask_t        pending_mask;
#endif
#ifdef CONFIG_PROC_FS
    struct proc_dir_entry    *dir;
#endif
    const char        *name;
} ____cacheline_internodealigned_in_smp;

irq_desc

  其相关联的几个结构体如下:

  定义在include/linux/ interrupt.h中的中断行动结构体:struct irqaction

struct irqaction {
    irq_handler_t handler;
    unsigned long flags;
    cpumask_t mask;
    const char *name;
    void *dev_id;
    struct irqaction *next;
    int irq;
    struct proc_dir_entry *dir;
};

  定义在include/linux 中的:irq_chip 芯片相关的处理函数集合

/**
 * struct irq_chip - hardware interrupt chip descriptor
 *
 * @name:        name for /proc/interrupts
 * @startup:        start up the interrupt (defaults to ->enable if NULL)
 * @shutdown:        shut down the interrupt (defaults to ->disable if NULL)
 * @enable:        enable the interrupt (defaults to chip->unmask if NULL)
 * @disable:        disable the interrupt (defaults to chip->mask if NULL)
 * @ack:        start of a new interrupt
 * @mask:        mask an interrupt source
 * @mask_ack:        ack and mask an interrupt source
 * @unmask:        unmask an interrupt source
 * @eoi:        end of interrupt - chip level
 * @end:        end of interrupt - flow level
 * @set_affinity:    set the CPU affinity on SMP machines
 * @retrigger:        resend an IRQ to the CPU
 * @set_type:        set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
 * @set_wake:        enable/disable power-management wake-on of an IRQ
 *
 * @release:        release function solely used by UML
 * @typename:        obsoleted by name, kept as migration helper
 */
struct irq_chip {
    const char    *name;
    unsigned int    (*startup)(unsigned int irq);  //中断开始
    void        (*shutdown)(unsigned int irq);    //中断关闭
    void        (*enable)(unsigned int irq);      //中断使能
    void        (*disable)(unsigned int irq);    //中断禁用

    void        (*ack)(unsigned int irq);
    void        (*mask)(unsigned int irq);
    void        (*mask_ack)(unsigned int irq);
    void        (*unmask)(unsigned int irq);
    void        (*eoi)(unsigned int irq);

    void        (*end)(unsigned int irq);
    void        (*set_affinity)(unsigned int irq, cpumask_t dest);
    int        (*retrigger)(unsigned int irq);
    int        (*set_type)(unsigned int irq, unsigned int flow_type);
    int        (*set_wake)(unsigned int irq, unsigned int on);

    /* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
    void        (*release)(unsigned int irq, void *dev_id);
#endif
    /*
     * For compatibility, ->typename is copied into ->name.
     * Will disappear.
     */
    const char    *typename;
};

  2.2.3 中断请求实现

上下半部机制

  我们期望让中断处理程序运行得快,并想让它完成的工作量多,这两个目标相互制约,如何解决——上下半部机制。

  我们把中断处理切为两半。中断处理程序是上半部——接受中断,他就立即开始执行,但只有做严格时限的工作。能够被允许稍后完成的工作会推迟到下半部去,此后,在合适的时机,下半部会被开终端执行。上半部简单快速,执行时禁止一些或者全部中断。

  下半部稍后执行,而且执行期间可以响应所有的中断。这种设计可以使系统处于中断屏蔽状态的时间尽可能的短,以此来提高系统的响应能力。上半部只有中断处理程序机制,而下半部的实现有软中断实现,tasklet实现和工作队列实现。

  我们用网卡来解释一下这两半。当网卡接受到数据包时,通知内核,触发中断,所谓的上半部就是,及时读取数据包到内存,防止因为延迟导致丢失,这是很急迫的工作。读到内存后,对这些数据的处理不再紧迫,此时内核可以去执行中断前运行的程序,而对网络数据包的处理则交给下半部处理。

上下半部划分原则

  1) 如果一个任务对时间非常敏感,将其放在中断处理程序中执行;

  2) 如果一个任务和硬件有关,将其放在中断处理程序中执行;

  3) 如果一个任务要保证不被其他中断打断,将其放在中断处理程序中执行;

  4) 其他所有任务,考虑放置在下半部执行。

下半部实现机制之软中断

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

  a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。

  b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。

下半部实现机制之tasklet

  tasklet是通过软中断实现的,所以它本身也是软中断。

  软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。

  为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。

  Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet作为一种新机制,显然可以承担更多的优点。正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只能在一个cpu上执行。在软中断时代,显然没有这种考虑。因此同一种软中断可以在两个cpu上同时执行,很可能造成冲突。

  总结下tasklet的优点:

  (1)无类型数量限制;

  (2)效率高,无需循环查表;

  (3)支持SMP机制;

  它的特性如下:

  1)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。

  2)多个不同类型的tasklet可以并行在多个CPU上。

  3)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。

下半部实现机制之工作队列(work queue)

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

  上面我们介绍的可延迟函数运行在中断上下文中,于是导致了一些问题,说明它们不可挂起,也就是说软中断不能睡眠、不能阻塞,原因是由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列,它能够在不同的进程间切换,以完成不同的工作。

  如果推后执行的任务需要睡眠,那么就选择工作队列,如果不需要睡眠,那么就选择软中断或tasklet。工作队列能运行在进程上下文,它将工作托付给一个内核线程。工作队列说白了就是一组内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。我们用结构体workqueue_struct表示工作者线程,工作者线程是用内核线程实现的。而工作者线程是如何执行被推后的工作——有这样一个链表,它由结构体work_struct组成,而这个work_struct则描述了一个工作,一旦这个工作被执行完,相应的work_struct对象就从链表上移去,当链表上不再有对象时,工作者线程就会继续休眠。因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。

Linux软中断和工作队列的作用是什么

  Linux中的软中断和工作队列是中断上下部机制中的下半部实现机制。

  1.软中断一般是“可延迟函数”的总称,它不能睡眠,不能阻塞,它处于中断上下文,不能进城切换,软中断不能被自己打断,只能被硬件中断打断(上半部),可以并发的运行在多个CPU上。所以软中断必须设计成可重入的函数,因此也需要自旋锁来保护其数据结构。

  2.工作队列中的函数处在进程上下文中,它可以睡眠,也能被阻塞,能够在不同的进程间切换,以完成不同的工作。

可延迟函数和工作队列都不能访问用户的进程空间,可延时函数在执行时不可能有任何正在运行的进程,工作队列的函数有内核进程执行,他不能访问用户空间地址。

时间: 2024-10-06 17:26:03

Linux中断机制的相关文章

linux 中断机制的处理过程

一.中断的概念 中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序.Linux中通常分为外部中断(又叫硬件中断)和内部中断(又叫异常). 在实地址模式中,CPU把内存中从0开始的1KB空间作为一个中断向量表.表中的每一项占4个字节.但是在保护模式中,有这4个字节的表项构成的中断向量表不满足实际需求,于是根据反映模式切换的信息和偏移量的足够使得中断向量

linux 中断机制浅析

一.中断相关结构体 1.irq_desc中断描述符 [cpp] view plain copy struct irq_desc { #ifdef CONFIG_GENERIC_HARDIRQS_NO_DEPRECATED struct irq_data irq_data; #else union { struct irq_data irq_data;   //中断数据 struct { unsigned int    irq;    //中断号 unsigned int    node;   /

非常好!!!Linux源代码阅读——中断【转】

Linux源代码阅读——中断 转自:http://home.ustc.edu.cn/~boj/courses/linux_kernel/2_int.html 目录 为什么要有中断 中断的作用 中断的处理原则 Linux 中断机制 中断控制器 中断描述符 中断数据结构 中断的初始化 内核接口 中断处理过程 CPU 的中断处理流程 保存中断信息 处理中断 从中断中返回 编写中断处理程序 软中断.tasklet与工作队列 上半部与下半部 软中断 tasklet 工作队列 1 为什么要有中断 1.1 中

关于linux硬中断、软中断、软中断指令、信号的简明解释

硬中断: 1.同步中断(内部中断/异常) core内部触发的中断,当中断发生时.会等待流水线完成后,再去执行中断.包含软中断指令.中断的标志由软件或者硬件设置. 2.异步中断(外部中断/中断) core外部触发的中断,当中断发生时.不会等待流水线完成,就会立即执行.此时流水线作废.中断的标志由硬件设置. 软中断: linux中断机制的下半部分(下半部:软中断,普通进程处理方式).软件上模拟硬件中断,达到异步调用下半部服务函数的功能. 软中断指令: 由程序设置中断标志后,硬件接着执行中断的相关操作

很有深意的《白雪公主杀人事件》

很久没有完整的看过一部有深意的电影了,昨天下了一部<白雪公主杀人事件>,听名字好像是一部恐怖电影,今天看Linux中断机制实在烦了,打开看,结果发现这部不是那么简单的破案与恐怖片,很有深意 所有人讲述逻辑都是向着自己有利的描述 看最近期的<罗辑思维>--关于租界,也是这样的结论,我们从中学开始学的历史只是一味的讲我们怎么怎么被帝国主义迫害,看完它后,感觉我们也被历史给选择性失忆了.

Linux内核——中断机制

中断机制 为什么需要中断? 如果让内核定期对设备进行轮询,以便处理设备,那会做很多无用功,因为外设的处理速度一般慢于CPU,而CPU不能一直等待外部事件.所以能让设备在需要内核时主动通知内核,会是一个聪明的方式,这便是中断. 中断处理程序 在响应一个特定中断时,内核会执行一个函数--中断处理程序.中断处理程序与其他内核函数的区别在于,中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中. 中断处理程序就是普通的C代码.特别之处在于中断处理程序是在中断上下文中运行的

Linux进程间通信总结

Linux进程间通信总结 1. 管道 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: (1)管道是半双工的,数据只能向一个方向流动:需要双方通信时,需要建立起两个管道: (2)只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程): (3)单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中. (4)数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出

Linux系统理解以及Linux系统学习心得

原创作品转载请注明出处  <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 作者:严哲璟 说一下我对Linux系统的理解 1.加载Linux内核准备:在加载基本输入输出模块(BIOS)之后,从磁盘的引导扇区读入操作系统的代码文件块到内存中,之后开始整个系统的初始化. 2.main.c的start_kernel函数是整个操作系统的入口,这也与Linux是基于C语言的特性相符,start_kernel具体做的动作很多

Process Kill Technology &amp;&amp; Process Protection Against In Linux

目录 0. 引言 1. Kill Process By Kill Command 2. Kill Process By Resource Limits 3. Kill Process By Code Injection Into Running Process Via GDB 4. Kill Process By Using Cross Process Virtual Memory Modify 5. Kill Process By Using ptrace To Inject .so 6. P