zoukankan      html  css  js  c++  java
  • Linux Kernel Development——中断

    中断的上半部和下半部

    中断是系统硬件与处理器通信的一种机制。当硬件设备发生中断的时候,内核会被打断,并执行中断对应的处理函数。在执行中断服务程序的时候,内核处于中断上下文。此时,如果不禁止中断,该中断处理程序仍有可能被其他中断事件所打断。因此,我们希望中断服务程序执行的越快越好。而通常一个中断服务程序要做很多的事情,比如网卡中断发生时,不仅要对网卡作应答,还要将网络数据包拷贝到系统内存,并作相应处理后交给合适的协议栈。这样,既想速度快,又要完成大量的工作,两者必须作出取舍。

    内核对这个问题的处理比较巧妙:内核将整个的中断处理流程分为了上半部和下半部。上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部执行的速度就会很快,可以服务更多的中断请求。但是,仅有"登记中断"是远远不够的,因为中断的事件可能很复杂。因此,Linux引入了一个下半部,来完成中断事件的绝大多数使命。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。 内核提供了很多下半部执行的机制,如软中断、tasklet和等待队列等。

    拿网卡来举例,在linux内核中,当网卡一旦接受到数据,网卡会通过中断告诉内核处理数据,内核会在网卡的中断处理函数(也就是上半部)执行一些网卡硬件的必要设置,因为这是在中断响应后急切要干的事情。接着,内核调用对应的下半部函数来处理网卡接收到的数据,因为数据处理没必要在中断处理函数里面马上执行,可以将中断让出来做更紧迫的事情。

    中断处理函数

    中断处理程序使用硬件设备驱动程序的组成部分,驱动程序通过request_irq()注册并激活一个中断处理函数。而在驱动程序卸载时,通过free_irq()注销中断处理函数,并释放中断线。需要注意的是,request_irq()可能会睡眠,因此不能在中断上下文或者不允许阻塞的代码中使用;必须在进程上下文中调用free_irq()。

    中断处理函数的原型为:

       1:  /**
       2:   * enum irqreturn
       3:   * @IRQ_NONE            interrupt was not from this device
       4:   * @IRQ_HANDLED         interrupt was handled by this device
       5:   * @IRQ_WAKE_THREAD     handler requests to wake the handler thread
       6:   */
       7:  enum irqreturn {
       8:          IRQ_NONE                = (0 << 0),
       9:          IRQ_HANDLED             = (1 << 0),
      10:          IRQ_WAKE_THREAD         = (1 << 1),
      11:  };
      12:   
      13:  typedef enum irqreturn irqreturn_t;
      14:   
      15:  typedef irqreturn_t (*irq_handler_t)(int, void *);

    中断处理函数是无需重入的,因为当一个中断处理函数在执行时,该中断线上的其他中断都会被屏蔽掉,以防止在同一个中断线上接收另一个中断。

    一个典型的中断处理函数实例:RTC中断处理函数

       1:  /*
       2:   *    A very tiny interrupt handler. It runs with IRQF_DISABLED set,
       3:   *    but there is possibility of conflicting with the set_rtc_mmss()
       4:   *    call (the rtc irq and the timer irq can easily run at the same
       5:   *    time in two different CPUs). So we need to serialize
       6:   *    accesses to the chip with the rtc_lock spinlock that each
       7:   *    architecture should implement in the timer code.
       8:   *    (See ./arch/XXXX/kernel/time.c for the set_rtc_mmss() function.)
       9:   */
      10:   
      11:  static irqreturn_t rtc_interrupt(int irq, void *dev_id)
      12:  {
      13:      /*
      14:       *    Can be an alarm interrupt, update complete interrupt,
      15:       *    or a periodic interrupt. We store the status in the
      16:       *    low byte and the number of interrupts received since
      17:       *    the last read in the remainder of rtc_irq_data.
      18:       */
      19:   
      20:      spin_lock(&rtc_lock);
      21:      rtc_irq_data += 0x100;
      22:      rtc_irq_data &= ~0xff;
      23:      if (is_hpet_enabled()) {
      24:          /*
      25:           * In this case it is HPET RTC interrupt handler
      26:           * calling us, with the interrupt information
      27:           * passed as arg1, instead of irq.
      28:           */
      29:          rtc_irq_data |= (unsigned long)irq & 0xF0;
      30:      } else {
      31:          rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
      32:      }
      33:   
      34:      if (rtc_status & RTC_TIMER_ON)
      35:          mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
      36:   
      37:      spin_unlock(&rtc_lock);
      38:   
      39:      /* Now do the rest of the actions */
      40:      spin_lock(&rtc_task_lock);
      41:      if (rtc_callback)
      42:          rtc_callback->func(rtc_callback->private_data);
      43:      spin_unlock(&rtc_task_lock);
      44:      wake_up_interruptible(&rtc_wait);
      45:   
      46:      kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
      47:   
      48:      return IRQ_HANDLED;
      49:  }
      50:  #endif

    中断上下文

    与进程上下文不同(内核代表进程执行操作,通过current宏关联当前进程,可以睡眠,也可以调用调度程序),当执行中断处理程序或下半部时,内核处于中断上下文中。中断上下文与进程没有什么瓜葛,因此中断上下文不可睡眠。中断处理函数具有较严格的时间限制,因为它打断了其他的代码。应当尽量将工作从中断处理程序中分离开来,放到下半部中执行,因为下半部可以在更合适的时间运行。

    中断处理机制

    1

    图 中断从硬件到内核的路由

    • 中断发生时根据中断控制器找到中断号
    • 中断上半部执行:调用do_IRQ(),保存当前状态old_regs = set_irq_regs(regs),irq_enter(),调用中断处理函数,最后irq_exit(),恢复原来状态set_irq_regs(old_regs)
    • 中断下半部执行:软中断和tasklet
  • 相关阅读:
    c# TCP高性能通信
    c#实现的HTTP服务端
    c#的二进制序列化组件MessagePack介绍
    c# 任务超时执行
    c#项目总结
    etcd客户端c#
    开发的服务集群部署方案,以etcd为基础(java)
    udt的java版本judt项目持续升级1.2版本
    udt通信java再次升级1.1版
    (转)Spring Boot(二) & lombok
  • 原文地址:https://www.cnblogs.com/feisky/p/2954862.html
Copyright © 2011-2022 走看看