前言
在x86 kernel 中断分析三——中断处理流程中,对于线程化中断处理函数,handle_irq_event_percpu调用了irq_wake_thread唤醒action->thread,此处唤醒的thread创建于__setup_irq,代码如下:
947 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
948 new->name);
...
962 new->thread = t;
本篇分析irq_thread。
irq_thread
参数data是新注册的irqaction
832 /*
833 * Interrupt handler thread
834 */
835 static int irq_thread(void *data)
836 {
837 struct callback_head on_exit_work;
838 struct irqaction *action = data;
839 struct irq_desc *desc = irq_to_desc(action->irq);
840 irqreturn_t (*handler_fn)(struct irq_desc *desc,
841 struct irqaction *action);
842
843 if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,---------1
844 &action->thread_flags))
845 handler_fn = irq_forced_thread_fn;-----------------------1.1
846 else
847 handler_fn = irq_thread_fn;------------------------------1.2
848
849 init_task_work(&on_exit_work, irq_thread_dtor); ---------------2
850 task_work_add(current, &on_exit_work, false);
851
852 irq_thread_check_affinity(desc, action);-----------------------3
853
854 while (!irq_wait_for_interrupt(action)) {----------------------4
855 irqreturn_t action_ret;
856
857 irq_thread_check_affinity(desc, action);
858
859 action_ret = handler_fn(desc, action);
860 if (action_ret == IRQ_HANDLED)
861 atomic_inc(&desc->threads_handled);
862
863 wake_threads_waitq(desc);--------------------------------5
864 }
865
866 /*
867 * This is the regular exit path. __free_irq() is stopping the
868 * thread via kthread_stop() after calling
869 * synchronize_irq(). So neither IRQTF_RUNTHREAD nor the
870 * oneshot mask bit can be set. We cannot verify that as we
871 * cannot touch the oneshot mask at this point anymore as
872 * __setup_irq() might have given out currents thread_mask
873 * again.
874 */
875 task_work_cancel(current, irq_thread_dtor);------------------6
876 return 0;
877 }
1 . force_irqthreads表示系统支持强制线程化。IRQTF_FORCED_THREAD在kernel 中断分析之四——中断申请 [下]中已有分析,表示申请中断时,thread为NULL(那么handler必不为NULL),此时为了线程化,强制将handler赋给thread_fn,handler设置为irq_default_primary_handler,并设置IRQTF_FORCED_THREAD标志位。若满足以上条件,handler_fn 的值为irq_forced_thread_fn,否则赋值为irq_thread_fn。
1.1 irq_forced_thread_fn
765 /*
766 * Interrupts which are not explicitely requested as threaded
767 * interrupts rely on the implicit bh/preempt disable of the hard irq
768 * context. So we need to disable bh here to avoid deadlocks and other
769 * side effects.
770 */--------------------------1.1.1
771 static irqreturn_t
772 irq_forced_thread_fn(struct irq_desc *desc, struct irqaction *action)
773 {
774 irqreturn_t ret;
775
776 local_bh_disable();
777 ret = action->thread_fn(action->irq, action->dev_id);
778 irq_finalize_oneshot(desc, action);
779 local_bh_enable();
780 return ret;
781 }
这段代码比价直观,唯一让人疑惑的是,在调用action->thread_fn前后进行了开关bh操作,为什么要这么做?
1.1.1注释中描述: 没有明确申请thread interrupt的中断,需要将硬中断上下文的bh/抢占关闭,所以要关闭bh。
如前面说分析,在__setup_irq—>irq_setup_forced_threading中,如果请求中断时没有提供thread_fn(但是提供了handler),
那么进行如下处理
888 if (!new->thread_fn) {
889 set_bit(IRQTF_FORCED_THREAD, &new->thread_flags); //设置IRQTF_FORCED_THREAD,表示经过强制线程化
890 new->thread_fn = new->handler; //之前的handler变成了thread_fn
891 new->handler = irq_default_primary_handler;
892 }
893 }
以下是我的个人理解,根据上面的代码逻辑不难发现,内核系统开发者和驱动开发者之间存在着一个”冲突”,内核开发者希望将满足条件的handler线程化使其运行在进程上下文,而驱动开发者并不一定知情,并且驱动开发者的本意是希望注册的handler运行在中断上下文,以便快速完成自己需要的功能。我们知道,bh是有可能运行在中断上下文(softirq、tasklet),也有可能运行在进程上下文(work queue)。这种情况下,内核开发者一方面要线程化,一方面又要满足驱动开发者快速运行(即不被抢占和轻易打断)的意愿,所以加上了local_bh_disable,即所有中断下半部都不会对其产生影响。
1.2 irq_thread_fn
783 /*
784 * Interrupts explicitly requested as threaded interrupts want to be
785 * preemtible - many of them need to sleep and wait for slow busses to
786 * complete.
787 */
788 static irqreturn_t irq_thread_fn(struct irq_desc *desc,
789 struct irqaction *action)
790 {
791 irqreturn_t ret;
792
793 ret = action->thread_fn(action->irq, action->dev_id);
794 irq_finalize_oneshot(desc, action);
795 return ret;
796 }
多数线程化中断的都会被抢占然后睡眠(这是该设计的本意),所以不需要disable bh。
2 . on_exit_work
从名称可有看出,该callback调用时已经是处理的是中断线程退出方面的工作。
关于task work,专门写了一篇分析介绍:链接(TODO)
初始化并添加一个callback_head,指定其func为irq_thread_dtor,该func的调用时机为进程由内核态返回用户态或者进程退出(do_exit)之前。
然后看一下irq_thread_dtor做了什么?
804 static void irq_thread_dtor(struct callback_head *unused)
805 {
806 struct task_struct *tsk = current;
807 struct irq_desc *desc;
808 struct irqaction *action;
809
810 if (WARN_ON_ONCE(!(current->flags & PF_EXITING)))-----------2.1
811 return;
812
813 action = kthread_data(tsk);---------------------------------2.2
814
815 pr_err("exiting task \"%s\" (%d) is an active IRQ thread (irq %d)\n",
816 tsk->comm, tsk->pid, action->irq);
817
818
819 desc = irq_to_desc(action->irq);-----------------------------2.3
820 /*
821 * If IRQTF_RUNTHREAD is set, we need to decrement
822 * desc->threads_active and wake possible waiters.
823 */
824 if (test_and_clear_bit(IRQTF_RUNTHREAD, &action->thread_flags))-----2.4
825 wake_threads_waitq(desc);
826
827 /* Prevent a stale desc->threads_oneshot */
828 irq_finalize_oneshot(desc, action);---------------------------2.5
829 }
2.1 如果进程不是处于退出状态,直接返回
2.2 获取irqaction
2.3 获取irqdesc
2.4 thread irq的处理过程中,唤醒中断处理线程action->thread,并设置IRQTF_RUNTHREAD,表示该线程应该运行,handle_irq_event_percpu—>irq_wake_thread—>test_and_set_bit(IRQTF_RUNTHREAD, &action->thread_flags)。
这里如果检测到IRQTF_RUNTHREAD,对应bit清0,调用wake_threads_waitq唤醒wait_for_threads下挂载的处于等待状态的thread(我没有找到像wait_for_thread等待队列上添加等待队列项wait_queue_t 的相关代码)。
798 static void wake_threads_waitq(struct irq_desc *desc)
799 {
800 if (atomic_dec_and_test(&desc->threads_active))
801 wake_up(&desc->wait_for_threads);
802 }
2.5 调用irq_finalize_oneshot
669 /*
670 * Oneshot interrupts keep the irq line masked until the threaded
671 * handler finished. unmask if the interrupt has not been disabled and
672 * is marked MASKED.--------------------------2.5.1
673 */
674 static void irq_finalize_oneshot(struct irq_desc *desc,
675 struct irqaction *action)
676 {
677 if (!(desc->istate & IRQS_ONESHOT))-----------2.5.2
678 return;
679 again:
680 chip_bus_lock(desc);
681 raw_spin_lock_irq(&desc->lock);
682
683 /*
684 * Implausible though it may be we need to protect us against
685 * the following scenario:
686 *
687 * The thread is faster done than the hard interrupt handler
688 * on the other CPU. If we unmask the irq line then the
689 * interrupt can come in again and masks the line, leaves due
690 * to IRQS_INPROGRESS and the irq line is masked forever.
691 *
692 * This also serializes the state of shared oneshot handlers
693 * versus "desc->threads_onehsot |= action->thread_mask;" in
694 * irq_wake_thread(). See the comment there which explains the
695 * serialization.
696 */-----------------------2.5.3
697 if (unlikely(irqd_irq_inprogress(&desc->irq_data))) {
698 raw_spin_unlock_irq(&desc->lock);
699 chip_bus_sync_unlock(desc);
700 cpu_relax();
701 goto again;
702 }
703
704 /*
705 * Now check again, whether the thread should run. Otherwise
706 * we would clear the threads_oneshot bit of this thread which
707 * was just set.
708 */----------------2.5.4
709 if (test_bit(IRQTF_RUNTHREAD, &action->thread_flags))
710 goto out_unlock;
711
712 desc->threads_oneshot &= ~action->thread_mask;------2.5.5
713
714 if (!desc->threads_oneshot && !irqd_irq_disabled(&desc->irq_data) &&
715 irqd_irq_masked(&desc->irq_data))
716 unmask_irq(desc);---------2.5.6
717
718 out_unlock:
719 raw_spin_unlock_irq(&desc->lock);
720 chip_bus_sync_unlock(desc);
721 }
2.5.1 在kernel 中断分析之四——中断申请 [下]
中曾有过对ONESHOT类型中断线程的分析,该标志的中断线程在线程运行完毕后才会unmask对应的中断线。
irq_finalize_oneshot就是干这个活儿的。
2.5.2 非ONESHOT类型的直接退出。
2.5.3 正常流程下,handle_irq_event—>irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);—>handle_irq_event_percpu(desc, action);
—>硬中断—>线程化中断(需要的话)—>irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
有一种场景,硬中断唤醒中断线程后,它们分别在不同CPU上运行,线程运行的比硬中断还要快(这种情况比较少见)。这样的后果是,中断线程先
unmask了对应的中断线,而此时desc->irq_data仍然保持IRQD_IRQ_INPROGRESS置1,硬中断还在执行,而中断线已经reenable了。
所以这里做了一个额外的检查,如果此时还在IRQD_IRQ_INPROGRESS状态,那么cpu_relax等待。
2.5.4 经过2.4的check和clear,如果再次发现IRQTF_RUNTHREAD置1,那么说明在这期间irq_wake_thread再次唤醒了中断处理线程(?),直接返回。
2.5.5 清除threads_oneshot中的bit位
2.5.6 unmask irq
3 . irq_thread_check_affinity
线程化(或被强制线程化)的非nested中断在__setup_irq中都会被设置IRQTF_AFFINITY标志位,在中断线程调用irq_thread时通过irq_thread_check_affinity处理,判断是否要改变中断线程的affinity。
最后调用set_cpus_allowed_ptr(current, mask);设置当前进程(即irq thread)的cpu affinity,并迁移到合适的CPU上。
4 . irq_wait_for_interrupt
651 static int irq_wait_for_interrupt(struct irqaction *action)
652 {
653 set_current_state(TASK_INTERRUPTIBLE);------------------4.1
654
655 while (!kthread_should_stop()) {
656
657 if (test_and_clear_bit(IRQTF_RUNTHREAD, ------------4.2
658 &action->thread_flags)) {
659 __set_current_state(TASK_RUNNING);
660 return 0;
661 }
662 schedule();----------------------------------------4.3
663 set_current_state(TASK_INTERRUPTIBLE);
664 }
665 __set_current_state(TASK_RUNNING);
666 return -1;
667 }
4.1 设置当前进程的状态为TASK_INTERRUPTIBLE
4.2 IRQTF_RUNTHREAD被置位说明中断线程被唤醒,对应的中断已经触发并且经过top half的处理。检测IRQTF_RUNTHREAD,如果有,然后将该bit位清除 设置当前进程TASK_RUNNING,退出irq_wait_for_interrupt,到irq_thread的循环中,再次检测cpu affinity,然后调用handler_fn。
4.4 IRQTF_RUNTHREAD没有置位则调用schedule让出CPU控制权,。
5 . wake_threads_waitq
唤醒该irqdesc->wait_for_threads上挂载的等待事件。
6 . 注释表示是正常的退出流程,但是这个时候call back有被调用到吗?有待验证。