• Linux内核设计与实现 总结笔记(第七章)中断和中断处理


    中断和中断处理

    处理器的速度跟外围硬件设备的速度往往不再一个数量级上,因此,如果内核采取让处理器向硬件发出一个请求。

    然后专门等待回应的办法,如果专门等待回应,明显太慢。所以等待期间可以处理其他事务,等待完成了请求操作后,再回来进行处理。

    所以内核提供了一种机制,让内核在需要的时候再向内核发出信号。这就是中断机制。

    一、中断

    硬件中断可以随时产生,因此,内核随时可能因为新到来的中断而被打断。

    不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标志。

    这些中断值通常被称为中断请求(IRQ)线。每个IRQ线都会被关联一个数值量。

    异常:它产生时必须考虑处理器时钟同步,常常也被称为同步中断。

    在处理器执行到由于编程失误而导致的错误指令的时候,或者实在执行期间出现特殊情况,必须靠内核来处理的时候,处理器就会产生一个异常。

    二、中断处理程序

    在响应要给特定中断的时候,内核会执行要给一个函数,该函数叫做中断处理程序中断服务例程(ISR)。

    linux中断程序是普通的C函数,不过必须按照特定的方式声明,以便内核识别。

    真正的区别在于:中断处理程序是被内核调用来响应中断的,而它们运行于中断上下文的特殊上下文中。

    中断上下文偶尔也称为原子上下文,该上下文中的执行代码不可阻塞。

    中断程序最好尽快反应,并且执行时间尽可能短。

    三、上半部与下半部的对比

    一般把中断程序分成两个部分,中断处理程序是上半部(接收到一个中断,就立即执行,但只做有限的工作) 。

    稍后完成的工作会推迟到下半部去,在适合的时机,下半部会被开中断执行。

    四、注册中断处理程序

    驱动程序可以通过request_irq()函数注册终端处理程序,它被声明再文件<linux/interrupt.h>中

    /* request_irq:分配一条给定的中断线 */
    int request_irq(unsigned int irq,
                        irq_handler_t handler,
                        unsigned long flags,
                        const char *name,
                        void *dev)
    /* 第一个参数irq表示要分配的中断号 */
    /* 第二个参数handler时一个指针,指向处理这个中断的实际中断处理程序 */
    
    /* handler原型 */
    typedef irqreturn_t (*irq_handler_t)(int, void *);
    request_irq原型

    4.1 中断处理程序标志

    第三个参数flags可以为0,再文件<linux/interrupt.h>中定义了掩码

    IRQF_DISABLED:意味着内核在处理中断程序本身期间,要禁止所有的其他中断

    IRQF_SAMPLE_RANDOM:此标志表明这个设备产生的中断对内核熵池有贡献

    IRQF_TIMER:特别为系统定时器的中断处理而准备的

    IRQF_SHARED:可以在多个中断的处理程序之间的贡献中断线

    第四个参数name是与中断相关的设备的ASCII文本表示,在/proc/irq和/proc/interrupts

    第五个参数dev用于共享中断线,当一个中断处理程序释放时,dev将提供标志信息。如果不需要共享中断线,设空(NULL)就行。

    4.2 一个中断例程

    request_irq();
    if(request_irq(irqm, my_interrupt, IRQF_SHARED, "my_device", my_dev)) {
        printk(KERN_ERR "my_device: cannot register IRQ %d
    ", irqn);
        return -EIO;
    }
    request_irq()
    • irqn是请求中断线
    • my_interrupt是中断处理程序
    • 设置IRQF_SHARED中断可以共享
    • 设备名为"my_device"
    • 传递my_dev给dev形参

    如果调用返回0,则说明成功。必须先初始化程序,然后再注册中断

    4.3 释放中断处理程序

    调用free_irq注销相应的中断处理程序。

    void free_irq(unsigned int irq, void *dev)
    free_irq

    如果指定的中断线不是共享的,那么函数删除动作的同时会禁用此中断线

    如果设置了共享标志,那么函数删除仅仅对dev对应的处理程序,除非删除了最后一个处理程序后,中断线才会被禁用。

    五、编写中断处理程序

    /* irq:参数已经没有太大意义了 */
    /* dev:一个通用指针,它与中断处理程序注册时传递的必须一致 */
    static irqreturn_t intr_handler(int irq, void *dev)
    irqreturn_t intr_handler

    返回值是一个特殊类型:irqreturn_t,可能返回两个特殊的值:IRQ_NONE和IRQ_HANDLED

    重入和中断处理程序:中断程序是不需要重入的,程序中断时,其他中断会被屏蔽,而且也不会重复嵌套。

    5.1 共享中断处理程序

    共享和非共享处理程序比较相似,但是差异主要有三处:

    • request_irq()参数flags必须设置IRQF_SHARED标志
    • 对于每个注册的中断程序,dev参数必须唯一,中断处理程序会用到这个值
    • 中断处理程序必须能够区分它的设备是否真的产生了中断。否则无法判断哪个设备发出的中断请求。

    任何一个设备没有按规则进行共享,那么中断就无法共享了。

    5.2 中带能处理程序实例

    在driver/char/rtc.c中有RTC相关的例子。中断发生时,报警器或定时器就会启动。

    当RTC驱动程序装载时,rtc_init()会被调用。

    /* 对rtc_irq注册rtc_interrupt */
    if(request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc", (void *)&rtc_port)) {
        printk(KERN_ERR "rtc: cannot register IRQ %d
    ", rtc_irq);
        return -EIO;
    }
    注册rtc_interrupt

    第一个参数说明RTC位于IRQ8,中断程序为rtc_interrupt,并且设置了共享中断线,程序名为"rtc"

    static irqreturn_t rtc_interrupt(int irq, void *dev)
    {
        /*
         * 可以是报警中孤单、更新完成的中断或周期性中断
         * 我们把装填保存在rtc_irq_data的低字节中,
         * 而把从最后一次读取之后所接收的中断号保存在其余字节中
         */
        spin_lock(&rtc_lick);
    
        rtc_irq_data += 0x100;
        rtc_irq_data &= ~0xff;
        rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
    
        if(rtc_status & RTC_TIMER_ON)
            mod_timer(&rtc_irq_timer, jiffies + HZ/rtq_freq + 2*HZ/100);
    
        spin_unlock(&rtc_lock);
    
        /*
         * 现在执行其余的操作
         */
        spin_lock(&rtc_task_lock);
        if(rtc_callback)
            rtc_callback->func(rtc_callback->private_data);
        spin_unlock(&rtc_task_lock);
        wake_up_interruptible(&rtc_wait);
    
        kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
    
        return IRQ_HANDLED;
    }
    中断处理程序

     使用自旋锁,保证不会被其他处理器同时访问。

    rtc_irq_data存放RTC有关的信息。

    后面部分,则是可能运行预先设置好的回调函数。每个RTC驱动程序允许注册要给回调函数,在中断到来时执行。

    最后返回IRQ_HANDLED,表明已经正确完成对此设备的操作。

    六、中断上下文

    当执行一个中断处理程序时,内核处于中断上下文中。

    中断上下问和进程没有什么瓜葛。

    中断上下文具有较为严格的时间限制,因为它打断了其他代码。

    中断处理程序栈的设置是要给配置选项,32位8KB,64位16KB 。

    总而言之,尽量节约内核栈空间。

    七、中断处理机制的是实现

    中断发生,通过电信号传递给处理器,除非屏蔽了此中断,否则处理器会立即执行。

    中断旅程开始于预定义入口,每条中断线,处理器都会跳到对应的一个唯一的位置,这样内核就知道接收的IRQ中断号。

    unsigned int do_IRQ(struct pt_regs regs)
    do_IRQ()声明

    如果do_IRQ确保在中断线上有一个有效的处理程序,而且程序已经启动。do_IRQ()就调用handle_IRQ_event()来运行为这条

    中断线所安装的中断处理程序。在文件kernel/irq/handler.c中

    /**
     * handle_IRQ_event - irq action chain handler
     * @irq:             the interrupt number
     * @action:        the interrupt action chain for this irq
     *
     * Handles the action chain of an irq event
     */
    irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
    {
        irqreturn_t ret, retval = IRQ_NONE;
        unsigned int status = 0;
    
        if(!(action->flags & IRQF_DISABLED))
            local_irq_enable_in_hardirq();
    
        do {
            trace_irq_handler_entry(irq, action);
            ret = action->handler(irq, action->dev_id);
            trace_irq_handler_exit(irq, action, ret);
    
            switch(ret) {
            case IRQ_WAKE_THREAD:
                    /*
                     * 把返回值设置为已处理,以便可疑的检查不再触发
                     */
                    ret = IRQ_HANDLED;
    
                    /*
                     * 捕获返回值为WAKE_THREAD的驱动程序,但是并不创建一个线程函数
                     */
                    if(unlikely(!action->thread_fn)) {
                        warn_no_thread(irq, action);
                        break;
                    }
    
                    /*
                     * 为这次中断唤醒处理线程。万一线程崩溃且被杀死,我们仅仅假装已经处理了该中断。上述的硬件中断(hardirq)处理程序已经进制设备中断,因此杜绝irq产生
                     */
                    if(likely(!test_bit(IRQTF_DIED, &action->thread_flags))) {
                        set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
                        wake_up_process(action->thread);
                    }
    
                    /* Fall through to add to randomness */
                case IRQ_HANDLED:
                    status |= action->flags;
                    break;
    
                default:
                    break;
                }
    
                retval |= ret;
                action = action->next;
        } while (action);
        
        if(status & IRQF_SAMPLE_RANDOM)
            add_interrupt_randomness(irq);
        local_irq_disable();
    
        return retval;
    }
    handle_IRQ_event()

    八、/proc/interrupts

    chen@chen-K42F:~/Downloads/4412/linux-4.14.12$ cat /proc/interrupts 
               CPU0       CPU1       
      0:         18          0   IO-APIC   2-edge      timer
      1:      25244          0   IO-APIC   1-edge      i8042
      9:         11         12   IO-APIC   9-fasteoi   acpi
     12:     207590      21487   IO-APIC  12-edge      i8042
     16:     197581     103440   IO-APIC  16-fasteoi   ehci_hcd:usb1
     17:    1087739     322896   IO-APIC  17-fasteoi   ath9k
     19:          0          0   IO-APIC  19-fasteoi   i801_smbus
     23:         15         26   IO-APIC  23-fasteoi   ehci_hcd:usb2
     24:      79350      28510   PCI-MSI 327680-edge      xhci_hcd
    cat /proc/interrupt部分结果

    第一列时中断线,第二列时一个接收中断的计数器,第三列时处理这个中断的中断控制器

    九、中断控制

    相关文件在<asm/system.h> <asm/irq.h>中找到

    9.1 禁止和激活中断

    用于禁止当前处理器上的本地中断,随后再就激活他们的语句。

    local_irq_disable();
    /* 禁止中断 */
    local_irq_enable();

    9.2 进制指定中断线

    对中断的状态操作之前禁止设备中断的传递,linux提供了四个接口

    void disable_irq(unsigned int irq);
    void disable_irq_nosync(unsigned int irq);
    void enable_irq(unsigned int irq);
    void synchronize_irq(unsigned int irq);
    四种方法

    9.3 中断系统状态

    通常有必要了解中断系统的状态,或者你当前是否正处于中断上下文的执行状态中。

    宏irqs_disable()定义在<asm/system.h>

    如果本地处理器上的中断系统被禁止,则返回非0,否则返回0

    在<linux/hardirq.h>

    local_irq_disable()  禁止本地中断传递

    local_irq_enable()  激活本地中断传递

    local_irq_save()  保存本地中断传递的当前状态,然后禁止本地中断传递

    local_irq_restore()  恢复本地中断传递到给定的状态

    disable_irq()    禁止给定中断线,并确保该函数返回之前在该中断线上没有处理程序再运行

    disable_irq_nosync()  禁止给定中断线

    enable_irq()    激活给定中断线

    irqs_disabled()    如果本地中断传递被禁止,则返回非0,否则返回0

    in_interrupt()    如果在中断上下文中,则返回非0,如果在进程上下文中,则返回0

    in_irq()    如果当前正在执行中断处理程序,则返回非0,否则返回0

  • 相关阅读:
    商城项目开发(一)
    java基础
    SpringBoot笔记
    设计模式-创建型模式
    讲真的 曾惜
    Linux常用精简命令实训练习
    使用ZeroClipboard.js复制内容到剪贴板上
    浅谈Jquery中的bind(),live(),delegate(),on()绑定事件方式
    jQuery mouseover与mouseenter,mouseout与mouseleave的区别
    div中的img垂直居中
  • 原文地址:https://www.cnblogs.com/ch122633/p/10015756.html
Copyright © 2020-2023  润新知