中断与中断处理(二)

**

书接上回

**

(六):中断上下文

当执行一个中断处理程序的时候,内核处于中断上下文中.中断上下文由于没有后备进程,所以不可以睡眠,同时中断上下文具有严格的时间限制,因为他打断了其他代码.

中断处理程序栈的设置是一个配置选项.原来的时候,中断处理程序共享所中断进程的内核栈,大小是两页的大小,即在32位系统上是8KB,在64位系统上是16KB.现在每一个中断处理程序都有自己的一个中断栈,大小是原来的一半,即在32位机器上是4KB.

(七):中断处理机制的实现

首先我们看一个中断从硬件到内核的路由:

这里写图片描述

在内核中,中断的旅程开始于预定义入口点,这类似于系统调用通过预定义的异常句柄进入内核.对于每条中断线,处理器都会跳到对应的一个唯一的位置.这样,内核就可以知道所接收中断的IRQ号了 .初始入口点只是在栈中保存这个号,并存放当前寄存器的值(这些值属于被中断的任务);然后,内核调用函数do_IRQ().从这里开始,大多数中断处理代码使用C编写的.

下面是fo_IRQ()的声明:

unsigned int do_IRQ(struct pt_regs regs);

因为C的调用惯例是要把函数参数放在栈的顶部,因此,pt_regs结构包含原始寄存器的值.这些值是以前在汇编入口例程中保存到栈中的.中断的值也得以保存.所以,do_IRQ()以及将它提取出来.

计算出中断号之后,do_IRQ()对所接收的中断进行应答,禁止这条中断线上的中断传递.在普通的PC上,这些操作是由mask_and_ack_8259A()来完成的.

接下来,do_IRQ()需要确保在这条中断线上有一个有效的处理程序,而且这个程序已经启动,但是当前并没有执行.如果是这样的话,do_IRQ()就会调用handle_IRQ_event()来运行为这条中断线所安装的中断处理程序.下面我们来看一下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
 *
 * handle_IRQ_event - irq操作链处理程序
 * @irq:    中断号
 * @action: 该中断所在的中断操作链
 *
 * 处理一个irq事件的操作链
 */
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
             *
             * 捕获返回值是WAKE_THREAD的驱动程序,但是
             * 并不创建一个线程函数
             */
            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.
             *
             * 为这次中断唤醒处理线程.万一线程崩溃并且被杀死,
             * 我们仅仅假装我们已经处理了该中断.上述的硬中断
             * 已经禁止了设备中断,因此杜绝了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;
}

首先,因为处理器禁止中断,这里要把他们打开,就必须在处理程序注册期间指定IRQF_DISABLED标志.回想一下,IRQF_DISABLED表示处理程序必须在中断禁止的情况下运行.接下来,每个潜在在处理程序在循环中依次执行.如果这条线不是共享的,第一次执行后就退出循环.否则,所有的处理程序都要被执行.之后,如果在注册期间指定了IRQF_SAMPLE_RANDOM标志,则还要调用函数add_interrupt_randomness().这个函数使用中断间隔时间为随机数产生器产生熵.最后,再将中断禁止(do_IRQ()期望中断一直是禁止的),函数返回.回到do_IRQ(),该函数做清理工作并返回到初始入口点,然后再从入口点跳到ret_from_intr()函数.

ret_from_intr()例程类似与初始入口代码,以汇编语言编写.这个例程检查重新调度是否在挂起.如果重新调度正在挂起,而且内核正在返回用户空间(即中断了用户进程),那么,schedule()被调用.如果内核正在返回内核空间(即中断了内核进程),只有在preempt_count为0的候,schedule才会被调用.

(八): /proc/interrupts

orocfs是一个虚拟文件系统,他只存在与内核内存,一把安装于/proc目录.在procfs中读写文件都要调用内核函数,这些函数模拟从真实文件中读或写.于此相关的例子是/proc/interrupts文件,该文件存放的是系统中与中断相关的统计信息.下面我们在终端上输入命令

cat /proc/interrupts

我们看一下输出结果:

这里写图片描述

对于proc文件系统,我们在我的博客操作系统中已经有了更加详细的介绍.

(九):中断控制

Linux提供了一组接口用于操作机器上的中断状态.这些接口为我们提供了能够禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力.这些例程都是和体系结构相关的,可以在

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

这两个函数通常以单个汇编指令来实现.实际上,在X86中,local_irq_disable()仅仅是cli指令,而local_irq_enable()只不过是sti指令.cli和sti分别是clear和set允许中断标志的汇编调用.

如果在调用local_irq_disable()例程之前禁止了中断,那么该例程会带来潜在的危险;同样,相应的local_irq_enable()例程也存在潜在危险,因为他将无条件的激活中断,尽管这些中断在开始时就是关闭的.所以我们需要一种就机制把中断恢复到以前的状态而不是简单的禁止或激活.内核普遍关心这点,是因为,内核中一个给定的代码路径既可以在中断激活的情况下达到,也可以在中断禁止的情况下达到,这取决于具体的调用链.在禁止中断之前保存中断系统的状态会更加安全一些.相反,在准备激活中断时,只需把中断回复到他们原来的状态.

unsigned long flags;
local_irq_save(flags); /* 禁止中断 */
local_irq_restore(flags) ;  /* 中断被恢复到他们原来的状态 */

这些方法至少部分要以宏的形式实现,因此表面上flags参数(这些参数必须定义为 unsigned long类型)是以值传递的.该参数包含具体体系结构的数据,也就是包含中断系统的状态.至少有一种体系结构把栈信息与值结合(SPARC),因此,flags不能传递给另一个函数(特别是它必须驻留在同一栈帧中).基于这个原因,对local_irq_save()和local_irq_restore()的调用必须在同一个函数中进行.

2:禁止指定中断线

在某些情况下,之禁止整个系统中一条特定的中断线就够了.这就是所谓的屏蔽掉(masking out)一条中断线.为此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);

前两个禁止中断控制器上指定的中断线,即禁止跟定中断向系统中所有处理器的传递.另外,函数只有在当前正在执行的所有处理程序完成之后,disable_irq()才能返回.因此,调用者不仅要确保不在指定线上传递新的中断,同时还要确保已经开始执行的处理程序已全部退出.函数diable_irq_nosygn()不会等待当前中断处理程序执行完毕.

函数sygnchronize_irq()等待一个特定的中断处理程序的退出.如果该处理程序正在执行,那么该函数必须退出后才能返回.

对这些函数的调用可以嵌套.但要记住在一条指定的中断线上,对disable_irq()或disable_irq_nosygn()的每次调用,都需要相应的调用一个enable_irq().只要在对enable_irq()完成最后一次调用后,才真正重新激活了中断线.例如,函数disable_irq()被调用了2次,那么直到第二次调用enable_irq()后,才能真正的激活中断线.

3:中断系统的状态

宏irqs_disable()定义在

in_interrupt()
in_irq()

第一个宏最有用:如果内核处于任何类型的中断处理中,他返回非0,说明内核此刻正在执行中断处理程序,或者正在执行下半部处理程序.

宏in_irq()只有在内核确实正在执行中断处理程序时才返回非0.

下面是一个中断控制方法的列表.

这里写图片描述

posted @ 2015-07-13 09:22  陈洪波  阅读(293)  评论(0编辑  收藏  举报