kernel 中断分析五——irq_thread

时间:2022-06-02 04:13:27

前言

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有被调用到吗?有待验证。