天天看點

linux中斷線程化分析【轉】

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。

#ifdef CONFIG_IRQ_FORCED_THREADING  

-extern bool force_irqthreads;  

+# ifndef CONFIG_PREEMPT_RT_BASE  

+   extern bool force_irqthreads;  

+# else  

+#  define force_irqthreads (true)  

+# endif  

 #else  

-#define force_irqthreads   (0)  

+#define force_irqthreads   (false)  

 #endif  

下面我們開始正式介紹中斷線程化是怎麼實作的。

Linux核心常見申請中斷的函數request_irq,在核心源碼include/linux/interrupt.h頭檔案中可以看到request_irq僅包含return request_threaded_irq(irq, handler, NULL, flags, name, dev);調用,request_threaded_irq函數在源碼目錄kernel/irq/manage.c檔案中,下面通過分析manage.c中各個相關函數解讀中斷線程化的實作過程。

根據request_irq的調用,首先分析request_threaded_irq

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;  

    /*  

     * 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).  

     */  

    if ((irqflags & IRQF_SHARED) && !dev_id)    //共享中斷必須有唯一确定的裝置号,不然中斷處理函數找不到發出中斷請求的裝置,注釋寫的很清楚  

        return -EINVAL;  

    desc = irq_to_desc(irq);  

    if (!desc)  

    if (!irq_settings_can_request(desc) ||  

        WARN_ON(irq_settings_is_per_cpu_devid(desc)))  

    if (!handler) { //handler和thread_fn都沒有指針傳入肯定是出錯了,有thread_fn無handler則将irq_default_primary_handler給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;  

    chip_bus_lock(desc);  

    retval = __setup_irq(irq, desc, action);    //在__setup_irq中确定是否線程化并完成中斷處理函數綁定  

    chip_bus_sync_unlock(desc);  

    if (retval)  

        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;  

}  

request_threaded_irq函數基本上是将傳入的參數放到action結構體,然後調用__setup_irq函數,線程化的具體過程在__setup_irq函數中

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;  

    cpumask_var_t mask;  

    if (desc->irq_data.chip == &no_irq_chip)  

        return -ENOSYS;  

    if (!try_module_get(desc->owner))  

        return -ENODEV;  

     * 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))  //request_irq調用通過設定參數_IRQ_NOTHREAD=0線程化,  

                            //沒有手動設定IRQ_NOTHREAD=1的中斷都被線程化。Linux核心從2.6.39版本開始對中斷線程化  

            irq_setup_forced_threading(new);    //實時更新檔使force_irqthreads=true,開啟強制線程化中斷  

     * Create a handler thread when a thread function is supplied  

     * and the interrupt does not nest into another interrupt  

    if (new->thread_fn && !nested) {  

        struct task_struct *t;  

        static const struct sched_param param = {  

            .sched_priority = MAX_USER_RT_PRIO/2,   //所有被線程化中斷優先級都為50  

        };  

        t = kthread_create(irq_thread, new, "irq/%d-%s", irq,   //為中斷建立核心線程  

                   new->name);  

        if (IS_ERR(t)) {  

            ret = PTR_ERR(t);  

        sched_setscheduler(t, SCHED_FIFO, ¶m);  

         * We keep the reference to the task struct even if  

         * the thread dies to avoid that the interrupt code  

         * references an already freed task_struct.  

        get_task_struct(t);  

        new->thread = t;  

         * Tell the thread to set its affinity. This is  

         * important for shared interrupt handlers as we do  

         * not invoke setup_affinity() for the secondary  

         * handlers as everything is already set up. Even for  

         * interrupts marked with IRQF_NO_BALANCE this is  

         * correct as we want the thread to move to the cpu(s)  

         * on which the requesting code placed the interrupt.  

        set_bit(IRQTF_AFFINITY, &new->thread_flags);  

    if (!alloc_cpumask_var(&mask, GFP_KERNEL)) {  

        ret = -ENOMEM;  

        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;  

     * The following block of code has to be executed atomically  

    raw_spin_lock_irqsave(&desc->lock, flags);  

    old_ptr = &desc->action;  

    old = *old_ptr; //action和desc都是指針,用指向指針的指針擷取action的位址,再使old指向action  

    if (old) {  //如果該中斷号的處理程式連結清單desc->action本身就是空,就無所謂共享了  

         * 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.  

        if (!((old->flags & new->flags) & IRQF_SHARED) ||  

            ((old->flags ^ 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))  

        /* 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; //在desc->action連結清單中找到空指針,為裡後面将new加進去  

        } 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_mask;  

         * 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 = 1 << 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_mask;  

    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, irq,  

                    new->flags & IRQF_TRIGGER_MASK);  

            if (ret)  

                goto out_mask;  

        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;  

        if (irq_settings_can_autoenable(desc))  

            irq_startup(desc, true);  

        else  

            /* Undo nested disables: */  

            desc->depth = 1;  

        /* 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 (new->flags & IRQF_NO_SOFTIRQ_CALL)  

            irq_settings_set_no_softirq_call(desc);  

        /* Set default affinity mask once everything is setup */  

        setup_affinity(irq, desc, mask);  

    } else if (new->flags & IRQF_TRIGGER_MASK) {  

        unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;  

        unsigned int omsk = irq_settings_get_trigger_mask(desc);  

        if (nmsk != omsk)  

            /* hope the handler works with current  trigger mode */  

            pr_warning("irq %d uses trigger mode %u; requested %u\n",  

                   irq, nmsk, omsk);  

    new->irq = irq;  

    *old_ptr = new; //添加到desc->action連結清單  

    /* 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, irq, false);  

    raw_spin_unlock_irqrestore(&desc->lock, flags);  

     * 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);    //核心線程開始運作  

    register_irq_proc(irq, desc);   //建立/proc/irq/目錄及檔案(smp_affinity,smp_affinity_list 等 )  

    new->dir = NULL;  

    register_handler_proc(irq, new);  

    free_cpumask_var(mask); //建立proc/irq/<irq>/handler/   

    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();  

    ret = -EBUSY;  

out_mask:  

    free_cpumask_var(mask);  

out_thread:  

    if (new->thread) {  

        struct task_struct *t = new->thread;  

        new->thread = NULL;  

        kthread_stop(t);  

        put_task_struct(t);  

out_mput:  

    module_put(desc->owner);  

    return ret;  

__setup_irq的内容比較多點,首先通過nested判斷該中斷是否屬于其他中斷程序,即和别的中斷共享同一個中斷号,如果不是,判斷是否強制将該中斷線程化,很明顯打了實時更新檔後使能強制線程化中斷,強制線程化如果thread_fn為空會使thread_fn指向handler,而handler指向預設的句柄函數,其實在強制中斷線程化沒有開啟的情況下,request_threaded_irq函數根據thread_fn是否為空判斷是否将該中斷線程化。這裡強制線程化後thread_fn顯然不會為空。

接下來因為是首次在該中斷線建立處理函數,申請一個核心線程,設定線程排程政策(FIFO)和優先級(50),為了讓使用該中斷号的其他程序共享這條中斷線,還必須建立一個中斷處理程序action的單向連結清單,設定一些共享辨別等。但是如果現在申請的這個中斷與其他已經建立中斷核心線程的中斷共享中斷線,那麼就不需要再次建立核心線程和隊列,隻需在隊列中找到空指針(一般是末尾)并插入隊列即可。做完這些之後喚醒核心程序(kthread_create)建立的核心程序不能馬上執行,需要喚醒。

在Linux中申請中斷還可以通過request_any_context_irq、devm_request_threaded_irq等函數,他們最終都調用request_threaded_irq,request_threaded_irq函數的完整形式如下:

在沒有強制中斷線程化的時候,thread_fn不為空即可将該中斷線程化。

本文轉自張昺華-sky部落格園部落格,原文連結:http://www.cnblogs.com/sky-heaven/p/7457910.html,如需轉載請自行聯系原作者

繼續閱讀