第7章中断和中断处理

7.6 中断上下文

当执行一个中断处理程序时,内核处于中断上下文中。进程上下文是一种内核所处的操作模式,此时内核代表进程执行——例如,执行系统调用或允许内核线程。在进程上下文中,可以通过current宏关联当前进程。此外,因为进程是以进程上下文的形式连接到内核中的,因此,进程上下文可以睡眠,也可以调用调度程序。

中断上下文和进程没有瓜葛。与current宏不相干。因为没有后备进程,所以中断上下文不可以睡眠,否则又怎能再对它重新调度呢?因此,不能从中断上下文中调用某些函数。如果一个函数睡眠,就不能在中断处理程序中使用它——这是对什么样的函数可以在中断处理程序中使用的限制。

中断上下文具有较为严格的时间限制,因为它打断了其他代码。中断上下文中的代码应当迅速、简洁,尽量不要使用循环去处理繁重的工作。中断处理程序打断了其他的代码。正是因为这种异步执行的特性,所以所有的中断处理程序必须尽可能的迅速、简洁。尽量把工作从中断处理程序中分离出来,放在下半部执行,因为下半部可以在更合适的时间运行。

中断处理程序栈的设置是一个配置选项。曾经,中断处理程序并不具有自己的栈。相反,它们共享所中断进程的内核栈。内核栈的大小是两页,在32位体系结构上是8KB,在64位体系结构上是16KB。因为在这种设置中,中断处理程序共享别人的堆栈,所以它们在栈中获取空间时必须非常节约。当然,内核栈本来就很有限,因此,所有的内核代码都应该谨慎利用它。

在2.6版本早期的内核中,增加了一个选项,把栈的大小从两页减到一页,也就是在32位的系统上只提供4KB的栈。这就减轻了内存的压力,因为系统中每个进程原先都需要两页连续,且不可换出的内核内存。为了应对栈大小的减少,中断处理程序拥有了自己的栈,每个处理器一个,大小为一页。这个栈称为中断栈,尽管中断栈的大小是原先共享栈的一半,但平均可用栈空间大得多,因为中断处理程序把这一整页占为己有。

中断处理程序不必关心栈如何设置,或者内核栈的大小是多少。总之,尽量节约内核栈空间。

7.7 中断处理机制的实现

中断处理系统在Linux中的实现依赖于体系结构的。实现依赖于处理器、所使用的中断控制器的类型、体系结构的设计及机器本身。

图7-1是中断从硬件到内核的路由。

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

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

do_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/handle.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:
            /*
             * 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)) {
                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;
}

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

ret_from_intr()例程类似于初始入口代码,以汇编语言编写。这个例程检查重新调度是否正在挂起。如果重新调度正在挂起,而且内核正在返回用户空间,那么,schedule()被调用。如果内核正在返回内核空间,只有在preempt_count为0时,schedule()才会被调用,否则,抢占内核是不安全的。在schedule()返回之后,或者如果没有挂起的工作,那么,原来的寄存器被恢复,内核恢复到曾经中断的点。

在x86上,初始的汇编例程位于arch/x86/kernel/entry_64.S(文件entry_32.S 对应32位的x86体系架构),C方法位于arch/x86/kernel/irq.c。

7.8 /proc/interrupts

procfs是一个虚拟文件系统,存在于内核内存,一般安装在/proc目录。在procfs中读写文件都要调用内核函数,这些函数模拟从真实文件中读写。与此相关的例子是/proc/interrupts文件,该文件存放的是系统中与中断相关的统计信息。如下是从单处理器PC上输出的信息:

第1列是中断号。在这个系统中,现有的中断号为0~2、4、5、12及15。第2列是一个接收中断数目的计数器。系统中的每个处理器都存在这样的列,但是,这个机器只有一个处理器。看到,时钟中断已接收3602371次中断,这里,声卡(EMU10K1)没有接收一次中断。第3列是处理这个中断的中断控制器。XT-PIC对应于标准的PC可编程中断控制器。最后一列是与这个中断相关的设备名字。这个名字是通过参数devname提供给函数request_irq()的。如果中断是共享的,则这条中断线上注册的所有设备都会列出来。

procfs代码位于fs/proc中。提供/proc/interrupts的函数是与体系结构相关的,叫做show_interrupts()。

在x86体系结构下:

int show_interrupts(struct seq_file *p, void *v)
{
        unsigned long flags, any_count = 0;
        int i = *(loff_t *) v, j, prec;
        struct irqaction *action;
        struct irq_desc *desc;

        if (i > nr_irqs)
                return 0;

        for (prec = 3, j = 1000; prec < 10 && j <= nr_irqs; ++prec)
                j *= 10;

        if (i == nr_irqs)
                return show_other_interrupts(p, prec);

        /* print header */
        if (i == 0) {
                seq_printf(p, "%*s", prec + 8, "");
                for_each_online_cpu(j)
                        seq_printf(p, "CPU%-8d", j);
                seq_putc(p, '\n');
        }

        desc = irq_to_desc(i);
        if (!desc)
                return 0;

        raw_spin_lock_irqsave(&desc->lock, flags);
        for_each_online_cpu(j)
                any_count |= kstat_irqs_cpu(i, j);
        action = desc->action;
        if (!action && !any_count)
                goto out;

        seq_printf(p, "%*d: ", prec, i);
        for_each_online_cpu(j)
                seq_printf(p, "%10u ", kstat_irqs_cpu(i, j));
        seq_printf(p, " %8s", desc->chip->name);
        seq_printf(p, "-%-8s", desc->name);

        if (action) {
                seq_printf(p, "  %s", action->name);
                while ((action = action->next) != NULL)
                        seq_printf(p, ", %s", action->name);
        }

        seq_putc(p, '\n');
out:
        raw_spin_unlock_irqrestore(&desc->lock, flags);
        return 0;
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值