中断和中断处理

中断和中断处理

任何操作系统内核的核心任务,都包含有对连接到计算机上的硬件设备进行有效管理,如硬盘、蓝光碟机、健盘、鼠标、3D处理器,以及无线电等。而想要管理这些设备,首先要能和它们互通音信才行。

两者的速率往往是不匹配的,如果依靠内核去询问会降低性能。所以提出一种让硬件有需要时向内核发出信号,让内核去做处理。该机制称为中断机制。

中断

中断使得硬件得以发出通知给处理器。

从物理学的角度看,中断是一种电信号,由硬件设备生成,并直接送入中断控制器的输入引脚中—中断控制器是个简单的电子芯片,其作用是将多路中断管线,采用复用技术只通过一个和处理器相连接的管线与处理器通信。当接收到一个中断后,中断控制器会给处理器发送一个电信号。处理器一经检测到此信号,便中断自己的当前工作转而处理中断。此后,处理器会通知操作系统已经产生中断,这样,操作系统就可以对这个中断进行适当地处理了。

不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标志。因此,来自键盘的中断就有别于来自硬盘的中断,从而使得操作系统能够对中断进行区分,并知道哪个硬件设备产生了哪个中断。这样,操作系统才能给不同的中断提供对应的中断处理程序。

中断值称为中断请求(IRQ)。每个IRQ线和数值相关联。如:IRQ 0是时钟中断。中断一般的动态分配的。

异常

基本的判别就是:

中断是硬件引起,CPU以外的事件引起。异常是软件代码引起。(除0异常 缺页异常 系统调用)。

中断处理程序

在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序interrupt handler或中断服务例程(interrupt server routine)。产生中断的每个设备都有一个相应的中断处理程序。例如,由一个函数专门处理来自系统时钟的中断,而另外一个函数专门处理由键盘产生的中断。一个设备的中断处理程序是它设备驱动程序(deriver)的一部分—设备驱动程序是用于对设备进行管理的内核代码。

中断处理程序与其他内核函数的真止区别在于:中断处理程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中中断上下文偶尔也称作原子上下文,因为正如我们看到的,该上下文中的执行代码不可阻塞。

中断随时可能发生,所以程序也随时可能运行。必须要保证中断是快速执行的,才能快速从中断中恢复。

上半部与下半部的对比

中断处理程序既要快速又要处理更多的数据。为了达到这个目的把中断拆分为了两部分。

  • 中断处理程序是上半部分(top half)-接收到中断,他就立即执行。

  • 能够稍微往后执行的会被推迟的到下半部(bottom half).

让我们考察一下上半部和下半部分割的例子,还是以我们的老朋友—网卡作为实例口当网卡接收来自网络的数据包时,需要通知内核数据包到了。网卡需要立即完成这件事,从而优化网络的吞吐量和传输周期,以避免超时。因此,网卜立即发出中断:晦,内核,我这里有最新数据包。内核通过执行网卡已注册的中断处理程序来做出应答。

内核负责把数据从网卡的buffer中拷贝到系统内存,中断处理程序完成,至于数据的处理则在下半部分。

注册中断处理程序

中断处理程序是管理硬件的驱动程序的组成部分。每一设备都有相关的驱动程序,如果设备使用中断(大部分设备如此),那么相应的驱动程序就注册一个中断处理程序。

使用 request_irq() 来注册:

/* request_irq: allocate a given interrupt line */
int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev)
  typedef irqreturn_t (*irq_handler_t)(int, void *);
  • irq:表示要分配的中断号。

  • handler:函数指针,中断处理程序的指针。

  • flags:中断处理标志位,可以为0,也可以为以下一个或者多个掩码。

    • IRQF_DISABLED—内核在处理该中断的时候禁止其他中断。

    • IRQF_SAMPLE_RANDOM——此标志表明这个设备产生的中断对内核墒池有贡献。(未明)

    • IRQF_TIMER——该标志是特别为系统定时器的中断处理而准备的。

    • IRQF_SHARED——·此标志表明可以在多个中断处理程序之间共享中断线。线上注册的每个处理程序必须指定这个标志;否则,在每条线上只能有一个处理程序。

  • name:中断设备的ASCII文本表示。

  • dev:用于共享中断线。当一个中断处理程序需要释放时(稍后讨论),dev将提供唯一的标志信息(} OO}1C,以便从共享中断线的诸多中断处理程序中删除指定的那一个。如果没有这个参数,那么内核不可能知道在给定的中断线上到底要删除哪一个处理程序。如果无须共享中断线,那么将该参数斌为空值(NULL)就可以了,但是,如果中断线是被共享的,那么就必须传递唯一的信息(除非设备又旧又破且位于ISA总线上,那么就必须支持共享中断)。

request_irq()可能会睡眠。所以不能在中断上下文和不允许阻塞的代码中调用该函数。(因为该函数会调用kmalloc(),而其会睡眠)。

例子

request_irq():
if (request_irq(irqn, my_interrupt, IRQF_SHARED, "my_device", my_dev)) {
    printk(KERN_ERR "my_device: cannot register IRQ %d\n", irqn);
    return -EIO;
}

void free_irq(unsigned int irq, void *dev)

编写中断处理程序


static irqreturn_t intr_handler(int irq, void *dev)

第一个参数是中断号,目前没有多少作用,因为dev出现了。

第二个参数dev是一个通用指针,它与在中断处理程序注册时传递给request_irq()的参数dev必须一致。如果该值有唯一确定性(这样做是为了能支持共享),那么它就相当于一个cookie,可以用来区分共享同一中断处理程序的多个设备。另外}}Y也可能指向中断处理程序使用的一个数据结构。因为对每个设备而言,设备结构都是唯一的,而且可能在中断处理程序中也用得到,因此,它也通常被看做dev。

重入和中断处理程序

中断处理程序不可能重入。因为在处理该程序的时候该中断线上的信号会被屏蔽掉。

共享的中断处理程序

共享的处理程序与非共享的处理程序在注册和运行方式上比较相似,但差异主要有以下三处:

  1. request_irq()的flag必须设置为IRQF_SHARED

  2. dev参数必须唯一。

  3. 中断处理程序必须能够知道设备是否真的出现中断。如果硬件不支持这一功能,那中断处理程序肯定会束手无策,它根本没法知道到底是与它对应的设备发出了这个中断,还是共享这条中断线的其他设备发出了这个中断。

内核接收一个中断后,它将依次调用在该中断线上注册的每一个处理程序。因此,一个处理程序必须知道它是否应该为这个中断负责,如果与它相关的设备并没有产生中断,那么处理程序应该立即退出。这需要硬件设备提供状态寄存器(或类似机制),以便中断处理程序进行检查。毫无疑问,大多数硬件都提供这种功能。

eg:

下面是一个实例:real-time clock(RTC)驱动程序。


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


static irqreturn_t rtc_interrupt(int irq, void *dev)
{
    /*
    * Can be an alarm interrupt, update complete interrupt,
    * or a periodic interrupt. We store the status in the
    * low byte and the number of interrupts received since
    * the last read in the remainder of rtc_irq_data.
    */
    spin_lock(&rtc_lock);
    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/rtc_freq + 2*HZ/100);
  
    spin_unlock(&rtc_lock);
    /*
    * Now do the rest of the actions
    */
    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;
}

中断上下文

在执行一个中断处理程序的时候,内核处于中断上下文中。因为没有后备进程,所以中断上下文不可以睡眠。——不能在一个中断上下文中调用一个会睡眠的函数。

以前和进程共用内核栈,现在独立出来。栈的大小为一页。

中断处理机制的实现

中断从硬件到内核的路由。设备产生中断,通过总线把电信号发送给中断控制器.如果中断线是激活的(‘白们是允许被屏蔽的),那么中断控制器就会把中断发往处理器。在大多数体系结构中,这个工作就是通过电信号给处理器的特定管脚发送一个信号。除非在处理器上禁止该中断,否则,处理器会立即停止它正在做的事,关闭中断系统,然后跳到内存中预定义的位置开始执行那里的代码口这个预定义的位置是由内核设置的,是中断处理程序的入口点。

对于每条中断线,处理器都会跳到唯一的入口。


unsigned int do_IRQ(struct pt_regs regs)

计算出中断后,禁止该中断线。

do_IRQ()需要确保中断线上有一个有效的处理程序。


/**
* 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:
      /*
      * Set result to handled so the spurious check
      * does not trigger.
      */
      ret = IRQ_HANDLED;
      /*
      * Catch drivers which return WAKE_THREAD but
      * did not set up a thread function
      */
      if (unlikely(!action->thread_fn)) {
      /*
      * Catch drivers which return WAKE_THREAD but
      * did not set up a thread function
      */
      if (unlikely(!action->thread_fn)) {
      warn_no_thread(irq, action);
      break;
    }
    /*
    * Wake up the handler thread for this
    * action. In case the thread crashed and was
    * killed we just pretend that we handled the
    * interrupt. The hardirq handler above has
    * disabled the device interrupt, so no irq
    * storm is lurking.
    */
    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;
}

First, because the processor disabled interrupts, they are turned back on unlessIRQF_DISABLED was specified during the handler’s registration. Recall that IRQF_DISABLED specifies that the handler must be run with interrupts disabled. Next, each potential handler is executed in a loop. If this line is not shared, the loop terminates after the first iteration. Otherwise, all handlers are executed.After that,add_interrupt_randomness() is called if IRQF_SAMPLE_RANDOM was specified during registration.This function uses the timing of the interrupt to generate entropy for the random number generator. Finally, interrupts are again disabled (do_IRQ() expects them still to be off) and the function returns. Back in do_IRQ(), the function cleans up and returnsto the initial entry point, which then jumps to ret_from_intr().

/proc/interrupts

procfs是个虚拟文件系统,只存在于内核中。存放相关interrupt信息。

  1. 中断线。

  2. 中断计数。

  3. 中断控制器。

  4. devname。

中断控制

以下一种是中断开关。一种可以存储状态,恢复操作。这是禁止所有的中断。


local_irq_disable();
/* interrupts are disabled .. */
local_irq_enable();

unsigned long flags;
local_irq_save(flags); /* interrupts are now disabled */
/* ... */
local_irq_restore(flags); /* interrupts are restored to their previous state */

控制中断线:


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);/*如果正在执行会等待退出*/


你可能感兴趣的:(Linux内核设计与实现小结)