三、中断和异常

1.中断

  同步中断:由当前CPU执行完一条指令之后产生,intel处理器手册也将同步中断称为异常。

  异步中断:由其他硬件设备依照CPU时钟信号随机产生,intel处理器手册将异步中断称为中断。

  中断处理需要满足的约束:

      1.中断处理必须尽可能的快和短,关键紧急和跟硬件相关的操作内核应立即执行,其余推迟的部分由内核稍后执行。

      2.中断处理程序必须能够嵌套执行(针对不同类型的中断)。

      3.内核代码的临界区中应该禁止中断。但是必须尽可能的限制这样的临界区,否则会大大降低内核的运行效率。

2.中断和异常

  Intel文档对中断和异常的分类:

    中断:

      可屏蔽中断(IRQ)、非屏蔽中断 

    异常:

      处理器探测异常:当CPU执行指令时探测到的一个反常条件所产生的异常,可以进一步分为三组,取决于产生异常时eip寄存器的值。

        故障,陷阱,异常中止,编程异常

3.IRQ和中断

  每个能发出中断请求的硬件设备控制器都有一条名为IRQ的输出线。所有的IRQ线都与一个名为可编程中断控制器(PIC)的硬件电路的输入引脚相连。

  IRQ线从0开始顺序编号。

  可以有选择的禁止每条IRQ线,通过对PIC编程禁止和激活IRQ。

  禁止的中断是丢失不了的,一旦中断被激活,PIC会将之前的中断信号发送到CPU,这样中断处理程序可以逐次地处理同一类型的IRQ。

  高级可编程中断控制器:

    I/O高级可编程控制器(APIC),每个CPU都有一个本地APIC,每个APIC都有32位寄存器,所有本地APIC都连接到一个外部I/O APIC,形成一个多APIC的系统。

    I/O APIC的组成:一组24条IRQ线、一张24项的中断重定向表。中断优先级不与引脚号相关联,中断重定向表中的每一项都可以被单独编程以指明中断向量和优先级、目标处理器和选择处理器的方式。

    来自外部硬件设备的中断请求以两种方式在可用CPU之间分发:

      静态分发: IRQ信号传递给重定向表相应项中所列出的本地APIC。中断立即传递给一个特定的CPU或一组CPU或所有CPU。

      动态分发:如果CPU正在执行最低优先级的进程,IRQ信号就传递给这种处理器的本地APIC。

  异常

    内核为每种异常提供一个专门的异常处理程序,异常处理程序会发送信号到引起异常的进程。

  中断描述符表(IDT):表中的每个向量存放中断或异常处理程序的入口地址。IDT包括三种描述符类型:任务门、中断门、陷阱门

  中断和异常的硬件处理过程(当发生了一个中断或异常时):

    1.确定与中断或异常关联的向量i

    2.读取idtr寄存器中指向IDT表中的第i项

    3.从gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中选择符所标识的段描述符,该段描述符指定中断或异常处理程序所在段的基地址。

    4.确信中断是由授权了的中断源发出的。比较中断处理程序的特权和引起中断的程序的特权。中断处理程序的特权必须大于引起中断的程序的特权。

    5.检查是否发生特权级的变化

    6.如果故障已发生,用引起异常的指令地址装载cs和eip寄存器

    7.在栈中保存eflags,cs,eip寄存器的内容

    8.如果异常产生硬件出错码,则保存在栈中

    9.装载cs和eip寄存器,执行中断或异常处理程序的第一条指令。

4.中断和异常处理程序的嵌套执行

  每个中断或异常都会引起一个内核控制路径,或者说当前进程在内核态执行单独的指令序列。

  内核控制路径可以任意嵌套,一个中断处理程序可以被另外一个中断处理程序"中断",引起内核控制路径的嵌套执行。

  允许内核中断嵌套,需要付出一定的代价,就是中断处理程序永远都不能阻塞,换句话说,中断处理程序运行期间不能发生进程切换。

  内核启用中断以前,必须初始化IDT表。

  中断描述符分类:

    中断门:用户态的进程不能访问中断门(DPL=0),所有的Linux中断处理程序都通过中断门激活,并全部限制在内核态。

    系统门:用户态的进程可以访问的一个Intel陷阱门。通过系统门来激活三个Linux异常处理程序,它们的向量是4,5,128

    系统中断门:能够被用户态进程访问的Intel中断门。

    陷阱门:用户态进程不能访问到的一个Intel陷阱门。大部分Linux异常处理程序都通过陷阱门来激活。

    任务门:不能被用户态进程访问的Intel任务门。

5.异常处理

   异常处理程序的标准结构:

    1.在内核堆栈中保存大多数寄存器的内容

    2.用高级的C函数处理异常

    3.通过ret_from_exception()函数从异常处理程序退出

  进入和离开异常处理程序:

    执行异常处理程序的C函数名由do_前缀和处理程序名组成。其中大部分函数把硬件出错码和异常向量保存在当前进程描述符中,然后向当前进程发送一个适当的信号。

1 current->thread.error_code=error_code;
2 current->thread.trap_no=vector;
3 force_sig(signumber,current);

  异常处理程序会检查异常发生在用户态还是内核态。当执行异常处理的C函数终止时,程序执行jmp指令跳转到ret_from_exception()函数。

6.中断处理

  中断处理依赖于中断类型,三种主要的中断类型: I/O中断,时钟中断,处理器间中断.

  I/O中断处理:几个设备可以共享同一个IRQ线,同一个向量因此不能预先知道哪个特定的设备产生IRQ,因此使用以下方式解决该问题:

    1.IRQ共享:中断处理程序执行多个中断服务例程(ISR),每个ISR是一个与单独设备(共享IRQ线)相关的函数。

    2.IRQ动态分配:一条IRQ线在可能的最后时刻才与一个设备驱动程序相关联。

  当一个中断发生时,并不是所有的操作都具有相同的急迫性,Linux把紧随中断要执行的操作分为三类:

    1.紧急的:紧急操作要在一个中断处理程序内立即执行,并且是在禁止可屏蔽中断的情况下。

    2.非紧急的:由中断处理程序立即执行,但必须是在开中断的情况下。

    3.非紧急可延迟的:非紧急可延迟的操作由独立的函数来执行,如软中断和tasklet。

  所有的I/O中断处理程序执行四个相同的基本操作:

    1.在内核堆栈中保存IRQ的值和寄存器内容

    2.为正在给IRQ线服务的PIC发送一个应答,允许PIC进一步发出中断

    3.执行共享这个IRQ的所有设备的中断服务例程(ISR)

    4.跳到ret_from_intr()的地址后终止

7.IRQ数据结构

  每个中断向量都有一个irq_desc_t描述符,所有这些描述符组织在一起形成irq_desc数组。

  irqaction描述描述一个特定的硬件设备和一个特定的中断。

  IRQ亲和力:用来平衡多处理器之间中断执行的次数,杜绝某个CPU执行中断次数过多或过少。

8.多种类型的内核栈

  如果thread_union结构的大小为8KB,则当前进程的内核栈被用于所有类型的内核控制路径:异常,中断和可延迟的函数(软中断及tasklet)

  如果thread_union结构的大小为4KB,内核则使用三种类型的内核栈:

    1.异常栈,用于处理异常(包括系统调用)。这个栈包含在每个进程的thread_union数据结构中,因此对系统中的每个进程,内核使用不同的异常栈

    2.硬中断请求栈,即用于处理中断。系统中的每个CPU都有一个硬中断请求栈,而且每个栈占用一个单独的页框

    3.软中断请求栈,用于处理可延迟的函数(软中断或tasklet)。系统中每个CPU都有一个软中断请求栈,而且每个栈占用一个单独的页框。

  所有的硬中断请求存放在harding_stack数组中,所有的软中断请求存放在softing_stack数组中,每个数组元素都是占用一个单独页框的irq_ctx类型的联合体。

       为中断处理程序保存寄存器的值:

1 pushl $n-256
2 jmp common_interrupt
3 
4 common_interrupt:
5     SAVE_ALL
6     movl %esp,%eax
7     call do_IRQ
8     jmp ret_from_intr

  

  do_IRQ()函数:

    调用do_IRQ()函数执行一个与中断相关的所有中断服务例程。该函数声明为:

      __attribute__((regparm(3))) unsigned int do_IRQ(struct pt_regs *regs)

    关键字regparm表示函数到eax寄存器中去找到参数regs的值。

unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    struct irq_desc * desc;
    /* high bit used in ret_from_ code  */
    unsigned vector = ~regs->orig_ax;

    entering_irq();

    /* entering_irq() tells RCU that we're not quiescent.  Check it. */
    RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");

    desc = __this_cpu_read(vector_irq[vector]);

    if (!handle_irq(desc, regs)) {
        ack_APIC_irq();

        if (desc != VECTOR_RETRIGGERED) {
            pr_emerg_ratelimited("%s: %d.%d No irq handler for vector\n",
                         __func__, smp_processor_id(),
                         vector);
        } else {
            __this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
        }
    }

    exiting_irq();

    set_irq_regs(old_regs);
    return 1;
}

9.中断服务例程

  https://www.cnblogs.com/arnoldlu/p/7599585.html

10.IRQ线的动态分配

  在激活一个准备利用IRQ线的设备之前,其相应的驱动程序调用request_irq()。该函数建立一个新的irqaction描述符,并用初值初始化它。

  然后调用setup_irq()函数把这个描述符插入到合适的IRQ链表。如果setup_irq()返回一个出错码,设备驱动程序中止操作,这意味着IRQ线已由另一个设备所使用,而该设备不允许中断共享。

  当设备操作结束时,驱动程序调用free_irq()函数从IRQ链表中删除这个描述符,并释放相应的内存区。

   

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
        const char *name, void *dev)
{
    return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
             irq_handler_t thread_fn, unsigned long irqflags,
             const char *devname, void *dev_id)
{
    struct irqaction *action;
    struct irq_desc *desc;
    int retval;

    if (irq == IRQ_NOTCONNECTED)
        return -ENOTCONN;

    /*
     * Sanity-check: shared interrupts must pass in a real dev-ID,
     * otherwise we'll have trouble later trying to figure out
     * which interrupt is which (messes up the interrupt freeing
     * logic etc).
     *
     * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
     * it cannot be set along with IRQF_NO_SUSPEND.
     */
    if (((irqflags & IRQF_SHARED) && !dev_id) ||
        (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
        ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
        return -EINVAL;

    desc = irq_to_desc(irq);
    if (!desc)
        return -EINVAL;

    if (!irq_settings_can_request(desc) ||
        WARN_ON(irq_settings_is_per_cpu_devid(desc)))
        return -EINVAL;

    if (!handler) {
        if (!thread_fn)
            return -EINVAL;
        handler = irq_default_primary_handler;
    }

    action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
    if (!action)
        return -ENOMEM;

    action->handler = handler;
    action->thread_fn = thread_fn;
    action->flags = irqflags;
    action->name = devname;
    action->dev_id = dev_id;

    retval = irq_chip_pm_get(&desc->irq_data);
    if (retval < 0) {
        kfree(action);
        return retval;
    }

    retval = __setup_irq(irq, desc, action);

    if (retval) {
        irq_chip_pm_put(&desc->irq_data);
        kfree(action->secondary);
        kfree(action);
    }

#ifdef CONFIG_DEBUG_SHIRQ_FIXME
    if (!retval && (irqflags & IRQF_SHARED)) {
        /*
         * It's a shared IRQ -- the driver ought to be prepared for it
         * to happen immediately, so let's make sure....
         * We disable the irq to make sure that a 'real' IRQ doesn't
         * run in parallel with our fake.
         */
        unsigned long flags;

        disable_irq(irq);
        local_irq_save(flags);

        handler(irq, dev_id);

        local_irq_restore(flags);
        enable_irq(irq);
    }
#endif
    return retval;
}

  setup_irq()函数

  

int setup_irq(unsigned int irq, struct irqaction *act)
{
    int retval;
    struct irq_desc *desc = irq_to_desc(irq);

    if (!desc || WARN_ON(irq_settings_is_per_cpu_devid(desc)))
        return -EINVAL;

    retval = irq_chip_pm_get(&desc->irq_data);
    if (retval < 0)
        return retval;

    retval = __setup_irq(irq, desc, act);

    if (retval)
        irq_chip_pm_put(&desc->irq_data);

    return retval;
}

static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
    struct irqaction *old, **old_ptr;
    unsigned long flags, thread_mask = 0;
    int ret, nested, shared = 0;

    if (!desc)
        return -EINVAL;

    if (desc->irq_data.chip == &no_irq_chip)
        return -ENOSYS;
    if (!try_module_get(desc->owner))
        return -ENODEV;

    new->irq = irq;

    /*
     * If the trigger type is not specified by the caller,
     * then use the default for this interrupt.
     */
    if (!(new->flags & IRQF_TRIGGER_MASK))
        new->flags |= irqd_get_trigger_type(&desc->irq_data);

    /*
     * Check whether the interrupt nests into another interrupt
     * thread.
     */
    nested = irq_settings_is_nested_thread(desc);
    if (nested) {
        if (!new->thread_fn) {
            ret = -EINVAL;
            goto out_mput;
        }
        /*
         * Replace the primary handler which was provided from
         * the driver for non nested interrupt handling by the
         * dummy function which warns when called.
         */
        new->handler = irq_nested_primary_handler;
    } else {
        if (irq_settings_can_thread(desc)) {
            ret = irq_setup_forced_threading(new);
            if (ret)
                goto out_mput;
        }
    }

    /*
     * Create a handler thread when a thread function is supplied
     * and the interrupt does not nest into another interrupt
     * thread.
     */
    if (new->thread_fn && !nested) {
        ret = setup_irq_thread(new, irq, false);
        if (ret)
            goto out_mput;
        if (new->secondary) {
            ret = setup_irq_thread(new->secondary, irq, true);
            if (ret)
                goto out_thread;
        }
    }

    /*
     * Drivers are often written to work w/o knowledge about the
     * underlying irq chip implementation, so a request for a
     * threaded irq without a primary hard irq context handler
     * requires the ONESHOT flag to be set. Some irq chips like
     * MSI based interrupts are per se one shot safe. Check the
     * chip flags, so we can avoid the unmask dance at the end of
     * the threaded handler for those.
     */
    if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)
        new->flags &= ~IRQF_ONESHOT;

    /*
     * Protects against a concurrent __free_irq() call which might wait
     * for synchronize_irq() to complete without holding the optional
     * chip bus lock and desc->lock.
     */
    mutex_lock(&desc->request_mutex);

    /*
     * Acquire bus lock as the irq_request_resources() callback below
     * might rely on the serialization or the magic power management
     * functions which are abusing the irq_bus_lock() callback,
     */
    chip_bus_lock(desc);

    /* First installed action requests resources. */
    if (!desc->action) {
        ret = irq_request_resources(desc);
        if (ret) {
            pr_err("Failed to request resources for %s (irq %d) on irqchip %s\n",
                   new->name, irq, desc->irq_data.chip->name);
            goto out_bus_unlock;
        }
    }

    /*
     * The following block of code has to be executed atomically
     * protected against a concurrent interrupt and any of the other
     * management calls which are not serialized via
     * desc->request_mutex or the optional bus lock.
     */
    raw_spin_lock_irqsave(&desc->lock, flags);
    old_ptr = &desc->action;
    old = *old_ptr;
    if (old) {
        /*
         * Can't share interrupts unless both agree to and are
         * the same type (level, edge, polarity). So both flag
         * fields must have IRQF_SHARED set and the bits which
         * set the trigger type must match. Also all must
         * agree on ONESHOT.
         */
        unsigned int oldtype;

        /*
         * If nobody did set the configuration before, inherit
         * the one provided by the requester.
         */
        if (irqd_trigger_type_was_set(&desc->irq_data)) {
            oldtype = irqd_get_trigger_type(&desc->irq_data);
        } else {
            oldtype = new->flags & IRQF_TRIGGER_MASK;
            irqd_set_trigger_type(&desc->irq_data, oldtype);
        }

        if (!((old->flags & new->flags) & IRQF_SHARED) ||
            (oldtype != (new->flags & IRQF_TRIGGER_MASK)) ||
            ((old->flags ^ new->flags) & IRQF_ONESHOT))
            goto mismatch;

        /* All handlers must agree on per-cpuness */
        if ((old->flags & IRQF_PERCPU) !=
            (new->flags & IRQF_PERCPU))
            goto mismatch;

        /* add new interrupt at end of irq queue */
        do {
            /*
             * Or all existing action->thread_mask bits,
             * so we can find the next zero bit for this
             * new action.
             */
            thread_mask |= old->thread_mask;
            old_ptr = &old->next;
            old = *old_ptr;
        } while (old);
        shared = 1;
    }

    /*
     * Setup the thread mask for this irqaction for ONESHOT. For
     * !ONESHOT irqs the thread mask is 0 so we can avoid a
     * conditional in irq_wake_thread().
     */
    if (new->flags & IRQF_ONESHOT) {
        /*
         * Unlikely to have 32 resp 64 irqs sharing one line,
         * but who knows.
         */
        if (thread_mask == ~0UL) {
            ret = -EBUSY;
            goto out_unlock;
        }
        /*
         * The thread_mask for the action is or'ed to
         * desc->thread_active to indicate that the
         * IRQF_ONESHOT thread handler has been woken, but not
         * yet finished. The bit is cleared when a thread
         * completes. When all threads of a shared interrupt
         * line have completed desc->threads_active becomes
         * zero and the interrupt line is unmasked. See
         * handle.c:irq_wake_thread() for further information.
         *
         * If no thread is woken by primary (hard irq context)
         * interrupt handlers, then desc->threads_active is
         * also checked for zero to unmask the irq line in the
         * affected hard irq flow handlers
         * (handle_[fasteoi|level]_irq).
         *
         * The new action gets the first zero bit of
         * thread_mask assigned. See the loop above which or's
         * all existing action->thread_mask bits.
         */
        new->thread_mask = 1UL << ffz(thread_mask);

    } else if (new->handler == irq_default_primary_handler &&
           !(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {
        /*
         * The interrupt was requested with handler = NULL, so
         * we use the default primary handler for it. But it
         * does not have the oneshot flag set. In combination
         * with level interrupts this is deadly, because the
         * default primary handler just wakes the thread, then
         * the irq lines is reenabled, but the device still
         * has the level irq asserted. Rinse and repeat....
         *
         * While this works for edge type interrupts, we play
         * it safe and reject unconditionally because we can't
         * say for sure which type this interrupt really
         * has. The type flags are unreliable as the
         * underlying chip implementation can override them.
         */
        pr_err("Threaded irq requested with handler=NULL and !ONESHOT for irq %d\n",
               irq);
        ret = -EINVAL;
        goto out_unlock;
    }

    if (!shared) {
        init_waitqueue_head(&desc->wait_for_threads);

        /* Setup the type (level, edge polarity) if configured: */
        if (new->flags & IRQF_TRIGGER_MASK) {
            ret = __irq_set_trigger(desc,
                        new->flags & IRQF_TRIGGER_MASK);

            if (ret)
                goto out_unlock;
        }

        /*
         * Activate the interrupt. That activation must happen
         * independently of IRQ_NOAUTOEN. request_irq() can fail
         * and the callers are supposed to handle
         * that. enable_irq() of an interrupt requested with
         * IRQ_NOAUTOEN is not supposed to fail. The activation
         * keeps it in shutdown mode, it merily associates
         * resources if necessary and if that's not possible it
         * fails. Interrupts which are in managed shutdown mode
         * will simply ignore that activation request.
         */
        ret = irq_activate(desc);
        if (ret)
            goto out_unlock;

        desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \
                  IRQS_ONESHOT | IRQS_WAITING);
        irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);

        if (new->flags & IRQF_PERCPU) {
            irqd_set(&desc->irq_data, IRQD_PER_CPU);
            irq_settings_set_per_cpu(desc);
        }

        if (new->flags & IRQF_ONESHOT)
            desc->istate |= IRQS_ONESHOT;

        /* Exclude IRQ from balancing if requested */
        if (new->flags & IRQF_NOBALANCING) {
            irq_settings_set_no_balancing(desc);
            irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
        }

        if (irq_settings_can_autoenable(desc)) {
            irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
        } else {
            /*
             * Shared interrupts do not go well with disabling
             * auto enable. The sharing interrupt might request
             * it while it's still disabled and then wait for
             * interrupts forever.
             */
            WARN_ON_ONCE(new->flags & IRQF_SHARED);
            /* Undo nested disables: */
            desc->depth = 1;
        }

    } else if (new->flags & IRQF_TRIGGER_MASK) {
        unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;
        unsigned int omsk = irqd_get_trigger_type(&desc->irq_data);

        if (nmsk != omsk)
            /* hope the handler works with current  trigger mode */
            pr_warn("irq %d uses trigger mode %u; requested %u\n",
                irq, omsk, nmsk);
    }

    *old_ptr = new;

    irq_pm_install_action(desc, new);

    /* Reset broken irq detection when installing new handler */
    desc->irq_count = 0;
    desc->irqs_unhandled = 0;

    /*
     * Check whether we disabled the irq via the spurious handler
     * before. Reenable it and give it another chance.
     */
    if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) {
        desc->istate &= ~IRQS_SPURIOUS_DISABLED;
        __enable_irq(desc);
    }

    raw_spin_unlock_irqrestore(&desc->lock, flags);
    chip_bus_sync_unlock(desc);
    mutex_unlock(&desc->request_mutex);

    irq_setup_timings(desc, new);

    /*
     * Strictly no need to wake it up, but hung_task complains
     * when no hard interrupt wakes the thread up.
     */
    if (new->thread)
        wake_up_process(new->thread);
    if (new->secondary)
        wake_up_process(new->secondary->thread);

    register_irq_proc(irq, desc);
    new->dir = NULL;
    register_handler_proc(irq, new);
    return 0;

mismatch:
    if (!(new->flags & IRQF_PROBE_SHARED)) {
        pr_err("Flags mismatch irq %d. %08x (%s) vs. %08x (%s)\n",
               irq, new->flags, new->name, old->flags, old->name);
#ifdef CONFIG_DEBUG_SHIRQ
        dump_stack();
#endif
    }
    ret = -EBUSY;

out_unlock:
    raw_spin_unlock_irqrestore(&desc->lock, flags);

    if (!desc->action)
        irq_release_resources(desc);
out_bus_unlock:
    chip_bus_sync_unlock(desc);
    mutex_unlock(&desc->request_mutex);

out_thread:
    if (new->thread) {
        struct task_struct *t = new->thread;

        new->thread = NULL;
        kthread_stop(t);
        put_task_struct(t);
    }
    if (new->secondary && new->secondary->thread) {
        struct task_struct *t = new->secondary->thread;

        new->secondary->thread = NULL;
        kthread_stop(t);
        put_task_struct(t);
    }
out_mput:
    module_put(desc->owner);
    return ret;
}

11.处理器间中断

  处理器间中断允许一个CPU向系统中的其他CPU发送中断信号。

  处理器间中断不是通过IRQ线传输的,而是作为信号直接放在连接所有CPU本地的APIC的总线上。

  在多处理器系统上,Linux定义了下列三种处理器间中断:

    CALL_FUNCTION_VECTOR :发往所有的CPU,强制这些CPU运行发送者传递过来的函数,相应的中断处理程序叫做:call_function_interrupt(),它的调用名为smp_call_function_interrupt()

    RESCHEDULE_VECTOR:当一个CPU接收这种类型的中断时,相应的处理程序限定自己来应答中断,当从中断返回时,所有的重新调度都自动进行。

    INVALIDATE_TLB_VECTOR:发往所有的CPU,强制它们的转换后援缓冲器无效。

  CALL_FUNCTION_VECTOR类型的处理器间中断的低级处理程序是call_function_interrupt(),它的调用名为smp_call_function_interrupt()的高级处理程序。

  每个高级处理程序应答本地APIC上的处理器间中断,然后执行由中断触发的特定操作。

  使用下列函数产生处理期间中断(IPI):

    send_IPI_all():发送一个IPI到所有的CPU(包括发送者)

    send_IPI_allbutself():发送一个IPI到所有的CPU(不包括发送者)

    send_IPI_self():发送一个IPI到发送者的CPU

    send_IPI_mask():发送一个IPI到位掩码指定的一组CPU

 12.软中断

  软中断和tasklet有密切的关系,tasklet是建立在软中断之上的。

  软中断的分配是静态的,tasklet的分配和初始化可以在运行时进行。软中断必须是可重入函数,因为即使同一个类型的软中断也可以并发地运行在多个CPU上。

  表示软中断的主要数据结构是softirq_vec数组,该数组包含类型为softirq_action的32个元素。一个软中断的优先级是相应的softirq_action元素在数组内的下标。

  softirq_action数据结构包括两个字段:指向软中断函数的一个action指针和指向软中断函数需要的通用数据结构的data指针。

  另一个关键字段是32位的preempt_count字段,用来跟踪内核抢占和内核控制路径的嵌套,该字段存放在每个进程描述符的thread_info字段中。

    preempt_count字段:

        0~7位  抢占计数器(显示禁用本地CPU内核抢占的次数,等于0允许抢占)

        8~15位  软中断计数器(表示可延迟函数被禁用的程度,值为0表示可延迟函数处于激活状态)

        16~27   硬中断计数器(表示在本地CPU上中断处理程序的嵌套数)

        28       PREEMPT_ACTIVE标志

  处理软中断:

    open_softirq()函数处理软中断的初始化。

    raise_softirq()函数用来激活软中断:

void raise_softirq(unsigned int nr)
{
    unsigned long flags;

    local_irq_save(flags);
    raise_softirq_irqoff(nr);
    local_irq_restore(flags);
}

inline void raise_softirq_irqoff(unsigned int nr)
{
	__raise_softirq_irqoff(nr);

	/*
	 * If we're in an interrupt or softirq, we're done
	 * (this also catches softirq-disabled code). We will
	 * actually run the softirq once we return from
	 * the irq or softirq.
	 *
	 * Otherwise we wake up ksoftirqd to make sure we
	 * schedule the softirq soon.
	 */
	if (!in_interrupt())
		wakeup_softirqd();
}

 

  需要周期性地但不能太频繁检查活动(挂起)的软中断,检查是在内核代码的几个点上进行的:

      1.当内核调用local_bh_enable()函数激活本地CPU的软中断时

      2.当do_IRQ()完成了I/O中断的处理时或调用irq_exit()宏时

      3.如果系统使用了I/O APIC时,则当smp_apic_timer_interrupt()函数处理完本地定时器中断时

      4.在多处理器系统中,当CPU处理完被CALL_FUNCTION_VECTOR处理器间中断所触发的函数时。

      5.当一个特殊的ksoftirqd/n内核线程被唤醒时

  不管在哪儿唤醒软中断,软中断都会在do_softirq()中执行

asmlinkage __visible void do_softirq(void)
{
    __u32 pending;
    unsigned long flags;

    if (in_interrupt())
        return;

    local_irq_save(flags);

    pending = local_softirq_pending();

    if (pending && !ksoftirqd_running(pending))
        do_softirq_own_stack();

    local_irq_restore(flags);
}

  ksoftirqd内核线程

    每个CPU都有自己的ksoftirqd/n内核线程,每个ksoftirqd内核线程都运行run_ksoftieqd()函数

static int run_ksoftirqd(void * __bind_cpu)
{
    set_current_state(TASK_INTERRUPTIBLE);

    while (!kthread_should_stop()) {
        preempt_disable();
        if (!local_softirq_pending()) {
            preempt_enable_no_resched();
            schedule();
            preempt_disable();
        }

        __set_current_state(TASK_RUNNING);

        while (local_softirq_pending()) {
            /* Preempt disable stops cpu going offline.
               If already offline, we'll be on wrong CPU:
               don't process */
            if (cpu_is_offline((long)__bind_cpu))
                goto wait_to_die;
            local_irq_disable();
            if (local_softirq_pending())
                __do_softirq();
            local_irq_enable();
            preempt_enable_no_resched();
            cond_resched();
            preempt_disable();
            rcu_note_context_switch((long)__bind_cpu);
        }
        preempt_enable();
        set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return 0;

wait_to_die:
    preempt_enable();
    /* Wait for kthread_stop */
    set_current_state(TASK_INTERRUPTIBLE);
    while (!kthread_should_stop()) {
        schedule();
        set_current_state(TASK_INTERRUPTIBLE);
    }
    __set_current_state(TASK_RUNNING);
    return 0;
}

 

13.tasklet

  tasklet是I/O驱动程序中实现可延迟函数的首选方法。tasklet建立在两个叫HI_SOFTIRQ和TASKLET_SOFTIRQ的软中断之上。

  几个tasklet可以和同一个软中断相关联,每个tasklet执行自己的函数。

  tasklet和高优先级的tasklet分别存放在tasklet_vec和tasklet_hi_vec数组中。二者都包含类型为tasklet_head的NR_CPUS个元素,每个元素都由一个

  指向tasklet描述符链表的指针组成。

  tasklet描述符是一个tasklet_struct类型的数据结构,其字段如下所示

1 struct tasklet_struct
2 {
3     struct tasklet_struct *next; 
4     unsigned long state;
5     atomic_t count;
6     void (*func)(unsigned long);
7     unsigned long data;
8 };

   tasklet描述符的state字段含有两个标志:

      TASKLET_STATE_SCHED:该标志被设置,表示tasklet是挂起的,曾被调度执行,意味着tasklet描述符被插入到tasklet_vec和tasklet_hi_vec数组中的一个链表

      TASKLET_STATE_RUN:该标志被设置,表示tasklet正在被执行。

  使用tasklet步骤:

    1.分配一个新的tasklet_struct数据结构

    2.调用tasklet_init()初始化数据结构

    3.调用tasklet_disable_nosync()或tasklet_disable()

    4.调用tasklet_enable()

    5.调用tasklet_scheduled()

  然后当TASKLET软中断激活时,将调用tasklet_action()函数

static void tasklet_action_common(struct softirq_action *a,
                  struct tasklet_head *tl_head,
                  unsigned int softirq_nr)
{
    struct tasklet_struct *list;

    local_irq_disable();
    list = tl_head->head;
    tl_head->head = NULL;
    tl_head->tail = &tl_head->head;
    local_irq_enable();

    while (list) {
        struct tasklet_struct *t = list;

        list = list->next;

        if (tasklet_trylock(t)) {
            if (!atomic_read(&t->count)) {
                if (!test_and_clear_bit(TASKLET_STATE_SCHED,
                            &t->state))
                    BUG();
                t->func(t->data);
                tasklet_unlock(t);
                continue;
            }
            tasklet_unlock(t);
        }

        local_irq_disable();
        t->next = NULL;
        *tl_head->tail = t;
        tl_head->tail = &t->next;
        __raise_softirq_irqoff(softirq_nr);
        local_irq_enable();
    }
}

 

               

 

posted @ 2019-03-21 17:15  ciel-coding杂记  阅读(593)  评论(0编辑  收藏  举报