AI智能摘要
本文代码基于6.17-6.19 (最开始写中断文章的时候只有6.17,写着写着就6.19了) 阅读之前,先留给各位读者两个问题 中断线程是不是和名字一样,工作在进程上下文? 中断线程工作在进程上下文,如何确保实时性? 何时唤醒? 关于中断的核心知识,可以查看版主之前的文章: https://www.
此摘要由AI分析文章内容生成,仅供参考。

本文代码基于6.17-6.19 (最开始写中断文章的时候只有6.17,写着写着就6.19了)

阅读之前,先留给各位读者两个问题

  1. 中断线程是不是和名字一样,工作在进程上下文?

  2. 中断线程工作在进程上下文,如何确保实时性? 何时唤醒?

关于中断的核心知识,可以查看版主之前的文章:

https://www.iliuqi.com/archives/linux-aarch64-all

里面也有笔者绘制的中断流程图,整体逻辑比较完整。

核心结构体

struct irqaction

用于描述一个irqaction,我们使用request_irq()注册一个中断的时候,实际上就是注册一个action。action和设备通常是一一对应的。

如果开启了共享中断,一条中断线上可以有多个action。

/**
 * struct irqaction - per interrupt action descriptor
 * @handler:        interrupt handler function
 * @name:        name of the device
 * @dev_id:        cookie to identify the device
 * @percpu_dev_id:        cookie to identify the device
 * @next:        pointer to the next irqaction for shared interrupts
 * @irq:        interrupt number
 * @flags:        flags (see IRQF_* above)
 * @thread_fn:        interrupt handler function for threaded interrupts
 * @thread:        thread pointer for threaded interrupts
 * @secondary:        pointer to secondary irqaction (force threading)
 * @thread_flags:        flags related to @thread
 * @thread_mask:        bitmask for keeping track of @thread activity
 * @dir:        pointer to the proc/irq/NN/name entry
 */
struct irqaction {
        irq_handler_t                handler; /* 此action对应的硬件中断处理函数 */
        void                        *dev_id;//对应的设备id
        void __percpu                *percpu_dev_id;
     //链表中下一个中断服务例程
        struct irqaction        *next;
        irq_handler_t                thread_fn;//中断线程服务函数
        struct task_struct        *thread;//中断通用线程的task_struct
        struct irqaction        *secondary;
        unsigned int                irq;//irq号,或者说irq线
        unsigned int                flags;//描述irq的标识符,IRQF_
        unsigned long                thread_flags;
        unsigned long                thread_mask;//用于屏蔽对应的中断线
        const char                *name;
        struct proc_dir_entry        *dir;
} ____cacheline_internodealigned_in_smp;

核心flag

IRQF_ONESHOT

这个flag用于保证中断线程仍在执行的时候,该中断线暂时被屏蔽。

在中断初始化的时候,如果检测到这个flag,会初始化对应的action->thread_mask

在中断流处理函数如handle_level_irq()时,会先mask该中断,硬件中断函数处理完之后,会唤醒中断线程处理下半部:

此时会通过action->thread_mask设置desc->threads_oneshot,desc->threads_oneshot不为0说明此中断线当前有中断线程正在处理或准备处理,并原子增加desc->threads_active计数,计数表明当前此中断线上running的中断线程的数量。

当中断线程处理完工作之后,会调用irq_finalize_oneshot(),清除此action的mask位:desc->threads_oneshot &= ~action->thread_mask,如果此desc->threads_oneshot为0,说明此中断线上所有中断线程已经执行完成了,unmask该中断。之后会原子减少desc->threads_active计数。

IRQTF_RUNTHREAD

硬件中断函数执行完之后,会调用__irq_wake_thread()尝试唤醒中断线程,此时会设置这个flag:

test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags)

IRQTF_RUNTHREAD会作为 中断线程 是否可以开始工作的一个依据,中断线程会检查。

IRQD_IRQ_MASKED

表明此中断被mask,这是针对中断线而言的

核心函数

request_threaded_irq()

调用方法类似于:

error = request_threaded_irq(irq, NULL, sh_keysc_isr, IRQF_ONESHOT,dev_name(&pdev->dev), pdev);

这里没有指定 硬中断服务函数,只指定了中断线程,相当于完全线程化,不指定硬件中断处理函数,目前来看这种使用方式还是非常常见的。

irq:中断线/中断号

handler:硬件中断处理函数

thread_fn:中断下半部线程

irqflags:IRQ FLAG

devname:设备名

dev_id:传给handler的传参

中断线程化常和IRQF_ONESHOT联动使用:

  • 当硬中断 handler 返回后,不会立即重新使能中断线。

  • 中断线保持禁用状态,直到对应的 线程化 handler 完成,内核才会重新使能中断。防止中断多次触发影响中断线程处理

/**
 * request_threaded_irq - allocate an interrupt line
 * @irq:        Interrupt line to allocate
 * @handler:        Function to be called when the IRQ occurs.
 *                Primary handler for threaded interrupts.
 *                If handler is NULL and thread_fn != NULL
 *                the default primary handler is installed.
 * @thread_fn:        Function called from the irq handler thread
 *                If NULL, no irq thread is created
 * @irqflags:        Interrupt type flags
 * @devname:        An ascii name for the claiming device
 * @dev_id:        A cookie passed back to the handler function
 *
 * This call allocates interrupt resources and enables the interrupt line
 * and IRQ handling. From the point this call is made your handler function
 * may be invoked. Since your handler function must clear any interrupt the
 * board raises, you must take care both to initialise your hardware and to
 * set up the interrupt handler in the right order.
 *
 * If you want to set up a threaded irq handler for your device then you
 * need to supply @handler and @thread_fn. @handler is still called in hard
 * interrupt context and has to check whether the interrupt originates from
 * the device. If yes it needs to disable the interrupt on the device and
 * return IRQ_WAKE_THREAD which will wake up the handler thread and run
 * @thread_fn. This split handler design is necessary to support shared
 * interrupts.
 *
 * @dev_id must be globally unique. Normally the address of the device data
 * structure is used as the cookie. Since the handler receives this value
 * it makes sense to use it.
 *
 * If your interrupt is shared you must pass a non NULL dev_id as this is
 * required when freeing the interrupt.
 *
 * Flags:
 *
 *        IRQF_SHARED                Interrupt is shared
 *        IRQF_TRIGGER_*                Specify active edge(s) or level
 *        IRQF_ONESHOT                Run thread_fn with interrupt line masked
 */
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 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.
         *
         * 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_NO_AUTOEN)) ||
            (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
            ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
                return -EINVAL;

        desc = irq_to_desc(irq);//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;//没有中断服务函数也没有线程,返回
            /*default handler会直接返回return IRQ_WAKE_THREAD */
            //如果有线程,设置中断处理函数为default handler
                handler = irq_default_primary_handler;    
        }

        action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);//为irqaction分配内存
        if (!action)
                return -ENOMEM;

    //设置对应的action,一个action对应于一个具体的设备
        action->handler = handler;//中断服务函数
        action->thread_fn = thread_fn;//中断线程,绑定到action的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;
}
EXPORT_SYMBOL(request_threaded_irq);

💎IRQ注册核心 __setup_irq()

传参分别是,中断号,中断描述符,中断action(对应于一个外部设备)

如果设置了IRQF_ONESHOT,会在下面200行左右,初始化这个action的thread_mask,唤醒中断线程时,会用这个thread_mask保证对应中断线的屏蔽。

/*
 * Internal function to register an irqaction - typically used to
 * allocate special interrupts that are part of the architecture.
 *
 * Locking rules:
 *
 * desc->request_mutex        Provides serialization against a concurrent free_irq()
 *   chip_bus_lock        Provides serialization for slow bus operations
 *     desc->lock        Provides serialization against hard interrupts
 *
 * chip_bus_lock and desc->lock are sufficient for all other management and
 * interrupt related functions. desc->request_mutex solely serializes
 * request/free_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;

        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;//设置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.
         */
      //检查这个中断是不是嵌套线程中断,需要用irq_set_nested_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 {
        //大部分IRQ都不是nested,所以likely会走这里
                if (irq_settings_can_thread(desc)) {
            //在内核启用了 强制线程化中断 (forced interrupt threading) 的情况下,
            //把一个普通的中断处理函数强制转换成线程化中断。成功返回0,一般不启用 强制线程化
            //如果开启了PREEMPT_RT实时补丁,中断必须强制线程化
            //内部也会设置action的secondary
                        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) {
        //调用setup函数,正式对中断线程进行初始化
                ret = setup_irq_thread(new, irq, false);
                if (ret)
                        goto out_mput;
                if (new->secondary) {
                //如果开启了中断强制线程化,还需要初始化secondary action
                        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.
         */
     //检查中断控制器(irq_chip)是否自带 IRQCHIP_ONESHOT_SAFE 属性(如 MSI 中断)。
     //如果是,内核会去掉 IRQF_ONESHOT 标志
        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_hardirq() to complete without holding the optional
         * chip bus lock and desc->lock. Also protects against handing out
         * a recycled oneshot thread_mask bit while it's still in use by
         * its previous owner.
         */
      //防止两个进程同时对同一个 IRQ 进行申请或释放。
        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.
                 * Interrupt lines used for NMIs cannot be shared.
                 */
                unsigned int oldtype;

                if (irq_is_nmi(desc)) {
                        pr_err("Invalid attempt to share NMI for %s (irq %d) on irqchip %s.\n",
                                new->name, irq, desc->irq_data.chip->name);
                        ret = -EINVAL;
                        goto out_unlock;
                }

                /*
                 * 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)))
                        goto mismatch;

                if ((old->flags & IRQF_ONESHOT) &&
                    (new->flags & IRQF_COND_ONESHOT))
                        new->flags |= IRQF_ONESHOT;
                else if ((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) {
        //如果设置了ONESHOT...
                /*
                 * Unlikely to have 32 resp 64 irqs sharing one line,
                 * but who knows.
                 */
                if (thread_mask == ~0UL) {
                        ret = -EBUSY;
                        goto out_unlock;
                }
 /*
 * 该 action 的 thread_mask 会被按位或(OR)操作到 desc->threads_active 中,
 * 用于表示 IRQF_ONESHOT 的线程处理程序已被唤醒但尚未完成。
 * 当线程完成时,对应的位会被清除。当共享中断线的所有线程都完成后,
 * desc->threads_active 变为零,中断线将被解除屏蔽。
 * 详见 handle.c:irq_wake_thread() 的实现。
 *
 * 如果主中断处理程序(硬中断上下文)没有唤醒任何线程,
 * 则会在硬中断流处理程序(如 handle_[fasteoi|level]_irq)中
 * 检查 desc->threads_active 是否为零以解除中断线的屏蔽。
 *
 * 新注册的 action 会分配到 thread_mask 的第一个零比特位。
 * 参考上述循环中对所有现有 action->thread_mask 的按位或操作。
 */
            //设置该中断对应action的thread_mask
                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 %s (irq %d)\n",
                       new->name, irq);
                ret = -EINVAL;
                goto out_unlock;
        }

.........................

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

        irq_setup_timings(desc, new);

        wake_up_and_wait_for_irq_thread_ready(desc, new);//确保对应的中断线程可以被唤醒
        wake_up_and_wait_for_irq_thread_ready(desc, new->secondary);

        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_put(t);
        }
        if (new->secondary && new->secondary->thread) {
                struct task_struct *t = new->secondary->thread;

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

这个函数主要做一些 中断强制线程化 的处理

  1. 如果中断不需要强制线程化,或者该中断已经线程化,退出,不需要执行操作

  2. 设置此线程化中断为IRQF_ONESHOT

  3. 如果原中断既有 硬件中断回调函数 又有 中断线程处理函数,需要设置secondary action,做额外的处理

  • secondary的handler设置为irq_forced_secondary_handler

  • secondary->thread_fn设置为primary action的thread_fn

  • 名称、中断号、设备信息沿用primary action

  1. 设置IRQTF_FORCED_THREAD

  2. 设置primary action的硬中断处理函数为irq_default_primary_handler,中断线程函数为原本的硬中断处理函数。

static int irq_setup_forced_threading(struct irqaction *new)
{
    //如果没有开启强制线程化,直接返回
        if (!force_irqthreads())
                return 0;
      //如果中断的 flags 包含以下任意一个标记,则直接返回 0,表示不强制线程化
        if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
                return 0;

        /*
         * No further action required for interrupts which are requested as
         * threaded interrupts already
         */
     //如果当前中断的 handler 已经是默认的线程化主处理函数(irq_default_primary_handler)
     //说明它已经被线程化,直接返回 0。
        if (new->handler == irq_default_primary_handler)
                return 0;

        new->flags |= IRQF_ONESHOT;

        /*
         * Handle the case where we have a real primary handler and a
         * thread handler. We force thread them as well by creating a
         * secondary action.
         */
    //如果中断既有主处理程序(handler)又有线程函数(thread_fn),
    //则需要创建一个次级 irqaction(new->secondary)
    //这个secondary也是一个irqaction类型的指针
        if (new->handler && new->thread_fn) {
                /* Allocate the secondary action */
                new->secondary = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
                if (!new->secondary)
                        return -ENOMEM;
             //设置secondary的硬中断处理函数为irq_forced_secondary_handler
                new->secondary->handler = irq_forced_secondary_handler;
                new->secondary->thread_fn = new->thread_fn;
                new->secondary->dev_id = new->dev_id;
                new->secondary->irq = new->irq;
                new->secondary->name = new->name;
        }
        /* Deal with the primary handler */
        set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
        new->thread_fn = new->handler;
        new->handler = irq_default_primary_handler;
        return 0;
}
setup_irq_thread()

会使用kthread_create()创建内核线程,这些内核线程对应使用的函数都是irq_thread()。但命名不同。见下图,命名会跟中断名称&中断号相关

800%cpu   1%user   0%nice   3%sys 793%idle   0%iow   2%irq   1%sirq   0%host
  735 root         RT   0    0    0    0 S  0.3   0.0   0:09.10 [irq/210-dcvsh-irq-4]
11847 root         20   0  10G 4.8M 4.2M S  0.0   0.0   0:00.01 grep irq
11392 root      

GO ON

static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
        struct task_struct *t;

        if (!secondary) {
        //创建kthread,函数为irq_thread.
                t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
                                   new->name);
        } else {
                t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
                                   new->name);
        }

        if (IS_ERR(t))
                return PTR_ERR(t);

        /*
         * 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.
         */
        new->thread = get_task_struct(t);//将创建的irq_thread赋值给action->thread
        /*
         * 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.
         */
     //告知中断线程,需要设置它的CPU亲和性
        set_bit(IRQTF_AFFINITY, &new->thread_flags);
        return 0;
}
wake_up_and_wait_for_irq_thread_ready()

注意陷阱,setup_irq_thread()只是调用kthread_create(),并没有run,run包含了create & TTWU,此时irq_thread还没运行过呢!要在这里首次尝试唤醒!!

唤醒成功的话,IRQTF_READY才会被设置,然后再回过头来唤醒setup_irq_thread()这个进程。

这么做的意义可能就是确保这个中断线程可以被唤醒,可以工作。

/*
 * Internal function to wake up a interrupt thread and wait until it is
 * ready.
 */
static void wake_up_and_wait_for_irq_thread_ready(struct irq_desc *desc,
                                                  struct irqaction *action)
{
        if (!action || !action->thread)
                return;

        wake_up_process(action->thread);
        wait_event(desc->wait_for_threads,
                   test_bit(IRQTF_READY, &action->thread_flags));
}

❤通用中断线程 irq_thread()

/*
 * Interrupt handler thread
 */
static int irq_thread(void *data)
{
        struct callback_head on_exit_work;
        struct irqaction *action = data;
        struct irq_desc *desc = irq_to_desc(action->irq);
        irqreturn_t (*handler_fn)(struct irq_desc *desc,
                        struct irqaction *action);

        irq_thread_set_ready(desc, action);//设置对应的中断线程为IRQTF_READY

        sched_set_fifo(current);//设置调度策略为FIFO
     //设置为fifo,相当于将这个中断线程函数设置为了RT线程,RT线程优先级高于CFS线程,因此会被优先调度

        if (force_irqthreads() && test_bit(IRQTF_FORCED_THREAD,
                                           &action->thread_flags))
        //中断强制线程化设置不一样的处理函数
                handler_fn = irq_forced_thread_fn;
        else
                handler_fn = irq_thread_fn;//设置handler_fn为irq_thread_fn

        init_task_work(&on_exit_work, irq_thread_dtor);
        task_work_add(current, &on_exit_work, TWA_NONE);

        while (!irq_wait_for_interrupt(desc, action)) {
                irqreturn_t action_ret;//用于保存返回值

                action_ret = handler_fn(desc, action);
                if (action_ret == IRQ_WAKE_THREAD)
          //如果primary action的线程处理函数返回IRQ_WAKE_THREAD
          //说明在线程化之前,它是一个硬件处理函数,且有对应的线程处理函数
          //因此在这里唤醒对应的secondary action的irq thread
                        irq_wake_secondary(desc, action);

                wake_threads_waitq(desc);//这里会减少threads_active计数
        }

        /*
         * This is the regular exit path. __free_irq() is stopping the
         * thread via kthread_stop() after calling
         * synchronize_hardirq(). So neither IRQTF_RUNTHREAD nor the
         * oneshot mask bit can be set.
         */
        task_work_cancel_func(current, irq_thread_dtor);
        return 0;
}

irq_thread_set_ready()

告知内核&前面的wait,该中断线程已经被成功唤醒,就绪。

/*
 * Internal function to notify that a interrupt thread is ready.
 */
static void irq_thread_set_ready(struct irq_desc *desc,
                                 struct irqaction *action)
{
        set_bit(IRQTF_READY, &action->thread_flags);
        wake_up(&desc->wait_for_threads);
}

irq_wait_for_interrupt()

static int irq_wait_for_interrupt(struct irq_desc *desc,
                                  struct irqaction *action)
{
        for (;;) {
                set_current_state(TASK_INTERRUPTIBLE);//S状态
                irq_thread_check_affinity(desc, action);//绑核

                if (kthread_should_stop()) {
                //如果判断这个内核线程需要被终止,但如果IRQTF_RUNTHREAD被设置
                //说明被唤醒,再工作最后一次
                        /* may need to run one last time */
                        if (test_and_clear_bit(IRQTF_RUNTHREAD,
                                               &action->thread_flags)) {
                                __set_current_state(TASK_RUNNING);
                                return 0;
                        }
                        __set_current_state(TASK_RUNNING);//设置R状态
                        return -1;
                }

         //如果不需要被终止,简单检查一下IRQTF_RUNTHREAD有没有被唤醒流程设置
                if (test_and_clear_bit(IRQTF_RUNTHREAD,
                                       &action->thread_flags)) {
                        __set_current_state(TASK_RUNNING);//设置R状态
                        return 0;
                }
                schedule();//没人唤醒我,S状态,调度
        }
}

irq_thread_fn()

调用对应中断action的 中断线程函数

/*
 * Interrupts explicitly requested as threaded interrupts want to be
 * preemptible - many of them need to sleep and wait for slow busses to
 * complete.
 */
static irqreturn_t irq_thread_fn(struct irq_desc *desc,        struct irqaction *action)
{
        //调用对应的中断线程处理函数
        irqreturn_t ret = action->thread_fn(action->irq, action->dev_id);

        if (ret == IRQ_HANDLED)
                atomic_inc(&desc->threads_handled);//增加中断线程处理的计数

        irq_finalize_oneshot(desc, action);//善后,判断当前中断线是不是可以取消mask
        return ret;
}
irq_finalize_oneshot()
/*
 * Oneshot interrupts keep the irq line masked until the threaded
 * handler finished. unmask if the interrupt has not been disabled and
 * is marked MASKED.
 */
static void irq_finalize_oneshot(struct irq_desc *desc,
                                 struct irqaction *action)
{
        if (!(desc->istate & IRQS_ONESHOT) ||
            action->handler == irq_forced_secondary_handler)
            //如果不是IRQS_ONESHOT,直接返回
                return;
again:
        chip_bus_lock(desc);
        raw_spin_lock_irq(&desc->lock);//上中断描述符的spinlock

        /*
         * Implausible though it may be we need to protect us against
         * the following scenario:
         *
         * The thread is faster done than the hard interrupt handler
         * on the other CPU. If we unmask the irq line then the
         * interrupt can come in again and masks the line, leaves due
         * to IRQS_INPROGRESS and the irq line is masked forever.
         *
         * This also serializes the state of shared oneshot handlers
         * versus "desc->threads_oneshot |= action->thread_mask;" in
         * irq_wake_thread(). See the comment there which explains the
         * serialization.
         */
        if (unlikely(irqd_irq_inprogress(&desc->irq_data))) {
                raw_spin_unlock_irq(&desc->lock);
                chip_bus_sync_unlock(desc);
                cpu_relax();
                goto again;
        }

        /*
         * Now check again, whether the thread should run. Otherwise
         * we would clear the threads_oneshot bit of this thread which
         * was just set.
         */
        if (test_bit(IRQTF_RUNTHREAD, &action->thread_flags))
                goto out_unlock;

    //清楚在唤醒irq thread过程中设置的desc->threads_oneshot
        desc->threads_oneshot &= ~action->thread_mask;

        if (!desc->threads_oneshot && !irqd_irq_disabled(&desc->irq_data) &&
            irqd_irq_masked(&desc->irq_data))
    //只有当中断线上所有中断线程处理完成并清除其对应的掩码位后,threads_oneshot 才会变为 0,
    //此时内核才会重新启用硬件中断。需要先检查IRQD_IRQ_MASKED
                unmask_threaded_irq(desc);//这里会取消之前这个中断线的mask状态

out_unlock:
        raw_spin_unlock_irq(&desc->lock);
        chip_bus_sync_unlock(desc);
}

中断线程唤醒流程:

注意,这里是在硬件中断上下文

handle_irq_event()

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
        irqreturn_t ret;

        desc->istate &= ~IRQS_PENDING;//取消之前设置的PENDING状态
      //将对应中断线设置为IRQD_IRQ_INPROGRESS状态
        irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
        raw_spin_unlock(&desc->lock);

    //此函数会遍历中断线上所有的action,并处理各个action的回调函数
        ret = handle_irq_event_percpu(desc);

        raw_spin_lock(&desc->lock);
        irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
        return ret;
}
handle_irq_event_percpu()
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
        irqreturn_t retval;

        retval = __handle_irq_event_percpu(desc);

        add_interrupt_randomness(desc->irq_data.irq);

        if (!irq_settings_no_debug(desc))
                note_interrupt(desc, retval);
        return retval;
}

helper:

irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc)
{
        irqreturn_t retval = IRQ_NONE;
        unsigned int irq = desc->irq_data.irq;
        struct irqaction *action;

        record_irq_time(desc);

        for_each_action_of_desc(desc, action) {
        //遍历中断线上的所有action
                irqreturn_t res;//res用于保存返回值

                /*
                 * If this IRQ would be threaded under force_irqthreads, mark it so.
                 */
                if (irq_settings_can_thread(desc) &&
                    !(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
                        lockdep_hardirq_threaded();

                trace_irq_handler_entry(irq, action);

                if (static_branch_unlikely(&irqhandler_duration_check_enabled)) {
                        u64 ts_start = local_clock();

                        res = action->handler(irq, action->dev_id);
                        irqhandler_duration_check(ts_start, irq, action);
                } else {
                        res = action->handler(irq, action->dev_id);//调用底层设备的硬中断处理函数
                }

                trace_irq_handler_exit(irq, action, res);//获取res返回值

                if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
                              irq, action->handler))
                        local_irq_disable();
           
           /*    确保到这里还是禁用了本地中断     */
                switch (res) {
                case IRQ_WAKE_THREAD://如果是有中断线程的中断
   //设备中断处理函数会返回这个,告知内核接下来需要唤醒对应的irq_thread去进程上下文办公
                        /*
                         * 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;
                        }

                        __irq_wake_thread(desc, action);
                        break;

                default:
                        break;
                }

                retval |= res;
        }

        return retval;
}

唤醒中断线程 __irq_wake_thread()

唤醒时,会设置IRQTF_RUNTHREAD

此函数只是使用TTWU唤醒中断线程,将中断线程加入rq,真正运行还得是进程上下文

void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action)
{
        /*
         * In case the thread crashed and was killed we just pretend that
         * we handled the interrupt. The hardirq handler has disabled the
         * device interrupt, so no irq storm is lurking.
         */
    //如果中断线程已经被标记为 PF_EXITING(例如线程崩溃或被终止),
    //则直接返回,避免唤醒无效线程。
        if (action->thread->flags & PF_EXITING)
                return;

        /*
         * Wake up the handler thread for this action. If the
         * RUNTHREAD bit is already set, nothing to do.
         */
     //设置IRQTF_RUNTHREAD,当中断线程再次被调度时,发现这给flag被设置
     //会退出等待,并设置state为TASK_RUNNING
        if (test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags))
                return;

        /*
         * It's safe to OR the mask lockless here. We have only two
         * places which write to threads_oneshot: This code and the
         * irq thread.
         *
         * This code is the hard irq context and can never run on two
         * cpus in parallel. If it ever does we have more serious
         * problems than this bitmask.
         *
         * The irq threads of this irq which clear their "running" bit
         * in threads_oneshot are serialized via desc->lock against
         * each other and they are serialized against this code by
         * IRQS_INPROGRESS.
         *
         * Hard irq handler:
         *
         *        spin_lock(desc->lock);
         *        desc->state |= IRQS_INPROGRESS;
         *        spin_unlock(desc->lock);
         *        set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
         *        desc->threads_oneshot |= mask;
         *        spin_lock(desc->lock);
         *        desc->state &= ~IRQS_INPROGRESS;
         *        spin_unlock(desc->lock);
         *
         * irq thread:
         *
         * again:
         *        spin_lock(desc->lock);
         *        if (desc->state & IRQS_INPROGRESS) {
         *                spin_unlock(desc->lock);
         *                while(desc->state & IRQS_INPROGRESS)
         *                        cpu_relax();
         *                goto again;
         *        }
         *        if (!test_bit(IRQTF_RUNTHREAD, &action->thread_flags))
         *                desc->threads_oneshot &= ~mask;
         *        spin_unlock(desc->lock);
         *
         * So either the thread waits for us to clear IRQS_INPROGRESS
         * or we are waiting in the flow handler for desc->lock to be
         * released before we reach this point. The thread also checks
         * IRQTF_RUNTHREAD under desc->lock. If set it leaves
         * threads_oneshot untouched and runs the thread another time.
         */
     //将此action的thread_mask或到desc->threads_oneshot上
     //表明此中断线上的此中断线程准备开始执行
        desc->threads_oneshot |= action->thread_mask;

        /*
         * We increment the threads_active counter in case we wake up
         * the irq thread. The irq thread decrements the counter when
         * it returns from the handler or in the exit path and wakes
         * up waiters which are stuck in synchronize_irq() when the
         * active count becomes zero. synchronize_irq() is serialized
         * against this code (hard irq handler) via IRQS_INPROGRESS
         * like the finalize_oneshot() code. See comment above.
         */
        atomic_inc(&desc->threads_active);

        wake_up_process(action->thread);//尝试唤醒该中断对应通用线程的irq_thread,TTWU
}

SUM

  1. 中断线程会在 中断上下文 被TTWU唤醒,被标记为IRQTF_RUNTHREAD,之后将其加入rq。实际执行是在 进程上下文

  2. 中断线程是RT进程,优先级高,通常会被优先调度,以此确保实时性

  3. 线程化的中断在注册的时候可能会使用IRQF_ONESHOT flag,确保 执行中断线程执行函数的时候,底层的硬件中断是被屏蔽的,防止嵌套。(通常是电平中断使用这个flag,因为电平中断为了防止中断反复触发,会设置mask)

  4. 中断虽然线程化,但硬件中断还是会发生,也会走正常的异常处理流程&硬件中断处理,只是硬中断处理函数几乎不执行任何操作,或者只执行一点点操作,所有任务基本上都是交给 中断线程 处理。相当于是把原本硬件中断上下文中需要处理的绝大部分内容交给了 irq_thread内核线程