天天看點

Linux RT(2)-硬實時Linux(RT-Preempt Patch)的中斷線程化

更多精華文章請掃描下方二維碼關注Linux閱碼場

Linux RT(2)-硬實時Linux(RT-Preempt Patch)的中斷線程化

底半部:線程化IRQ

線程化中斷的支援在2009年已經進入Linux官方核心,詳見Thomas Gleixner的patch:

http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=3aa551c9b4c40018f0e261a178e3d25478dc04a9

該patch提供一個能力,驅動可以通過

int request_threaded_irq(unsigned int irq, irq_handler_t <strong>handler</strong>,
                         irq_handler_t <strong>thread_fn</strong>, unsigned long irqflags,
                         const char *devname, void *dev_id)
           

申請一個線程化的IRQ,kernel會為中斷的底版本建立一個名字為irq/%d-%s的線程,%d對應着中斷号。其中頂半部(硬中斷)handler在做完必要的處理工作之後,會傳回IRQ_WAKE_THREAD,之後kernel會喚醒irq/%d-%s線程,而該kernel線程會調用thread_fn函數,是以,該線程成為底半部。在後續維護的過程中,筆者曾參與進一步完善該功能的讨論,後續patch包括nested、oneshot等的支援,詳見patch:

http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=399b5da29b9f851eb7b96e2882097127f003e87c

http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=70aedd24d20e75198f5a0b11750faabbb56924e2

http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=b25c340c195447afb1860da580fe2a85a6b652c5

該機制目前在kernel中使用已經十分廣泛,可以認為是繼softirq(含tasklet)和workqueue之後的又一大中斷底半部方式。

頂半部:強制線程化

在使能Linux RT-Preempt後,預設情況下會強制透過request_irq()申請的IRQ的頂半部函數線上程中執行,我們都知道request_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);
}      
           

這意味着通過request_irq()申請的IRQ,在沒有Rt-Preepmt的情況下,kernel并不會為其建立irq線程,因為它在最終調用request_threaded_irq()的時候傳遞的thread_fn為NULL。

如果使能了RT-Preempt Patch的情況下,其中的genirq-force-threading.patch會強制ARM使用threaded irq:

Index: linux-stable/arch/arm/Kconfig
===================================================================
--- linux-stable.orig/arch/arm/Kconfig
+++ linux-stable/arch/arm/Kconfig
@@ -40,6 +40,7 @@ config ARM
        select GENERIC_IRQ_SHOW
        select ARCH_WANT_IPC_PARSE_VERSION
        select HARDIRQS_SW_RESEND
+       select IRQ_FORCED_THREADING
        select CPU_PM if (SUSPEND || CPU_IDLE)
        select GENERIC_PCI_IOMAP
        select HAVE_BPF_JIT
           

在RT-Preempt Patch中,會針對使能了IRQ_FORCED_THREADING的情況,對這一原先沒有線程化IRQ的case進行強制線程化,代碼見__setup_irq():

887 static int
 888 __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
 889 {
 890         ...
 903 
 904         /*
 905          * Check whether the interrupt nests into another interrupt
 906          * thread.
 907          */
 908         nested = irq_settings_is_nested_thread(desc);
 909         if (nested) {
 910                 ...
 920         } else {
 921                 if (irq_settings_can_thread(desc))
 922                         irq_setup_forced_threading(new);
 923         }
 925         /*
 926          * Create a handler thread when a thread function is supplied
 927          * and the interrupt does not nest into another interrupt
 928          * thread.
 929          */
 930         if (new->thread_fn && !nested) {
 931                 struct task_struct *t;
 932 
 933                 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
 934                                    new->name);
 935                 ...
 939                 /*
 940                  * We keep the reference to the task struct even if
 941                  * the thread dies to avoid that the interrupt code
 942                  * references an already freed task_struct.
 943                  */
 944                 get_task_struct(t);
 945                 new->thread = t;
 946         }
           

我們重點看一下其中的921行:

867 static void irq_setup_forced_threading(struct irqaction *new)
 868 {
 869         if (!force_irqthreads)
 870                 return;
 871         if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
 872                 return;
 873 
 874         new->flags |= IRQF_ONESHOT;
 875 
 876         if (!new->thread_fn) {
 877                 set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
 878                 new->thread_fn = new->handler;
 879                 new->handler = irq_default_primary_handler;
 880         }
 881 }
           

第878行和879行,強制将原先的handler複制給thread_fn,而又強制把原來的handler變更為irq_default_primary_handler(),而這個函數,其實神馬都不做,隻是直接傳回IRQ_WAKE_THREAD:

613 /*
 614  * Default primary interrupt handler for threaded interrupts. Is
 615  * assigned as primary handler when request_threaded_irq is called
 616  * with handler == NULL. Useful for oneshot interrupts.
 617  */
 618 static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
 619 {
 620         return IRQ_WAKE_THREAD;
 621 }
           

第874的IRQF_ONESHOT就用到了我們前面說的oneshot功能。

是以,RT-Preempt實際上是把原先的頂半部底半部化了,而現在僞造了一個假的頂半部,它隻是直接傳回一個IRQ_WAKE_THREAD标記而已。

我們來看一下一個中斷發生後,Linux RT-Preempt處理的全過程,首先是會跳到

arch/arm/kernel/entry-armv.S

arch/arm/include/asm/entry-macro-multi.S

中的彙編入口,再進入arm/kernel/irq.c下的asm_do_IRQ 、handle_IRQ,之後generic的handle_irq_event_percpu()被調用:

133 handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
134 {
135         irqreturn_t retval = IRQ_NONE;
136         unsigned int flags = 0, irq = desc->irq_data.irq;
137 
138         do {
139                 irqreturn_t res;
140 
141                 trace_irq_handler_entry(irq, action);
142                 res = action->handler(irq, action->dev_id);
143                 trace_irq_handler_exit(irq, action, res);
144 
145                 if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
146                               irq, action->handler))
147                         local_irq_disable();
148 
149                 switch (res) {
150                 case IRQ_WAKE_THREAD:
151                         /*
152                          * Catch drivers which return WAKE_THREAD but
153                          * did not set up a thread function
154                          */
155                         if (unlikely(!action->thread_fn)) {
156                                 warn_no_thread(irq, action);
157                                 break;
158                         }
159 
160                         irq_wake_thread(desc, action);
161 
162                         /* Fall through to add to randomness */
163                 case IRQ_HANDLED:
164                         flags |= action->flags;
165                         break;
166 
167                 default:

           

我們關注其中的第142行,本質上是調用irq_default_primary_handler(),接到150行,由于irq_default_primary_handler()傳回了IRQ_WAKE_THREAD,是以,generic的中斷處理流程會執行irq_wake_thread(desc, action);去喚醒前面的irq/%d-%s線程,該線程的代碼是

789 static int irq_thread(void *data)
 790 {
 791         static const struct sched_param param = {
 792                 .sched_priority = MAX_USER_RT_PRIO/2,
 793         };
 794         struct irqaction *action = data;
 795         struct irq_desc *desc = irq_to_desc(action->irq);
 796         irqreturn_t (*handler_fn)(struct irq_desc *desc,
 797                         struct irqaction *action);
 798 
 799         if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
 800                                         &action->thread_flags))
 801                 handler_fn = irq_forced_thread_fn;
 802         else
 803                 handler_fn = irq_thread_fn;
 804 
 805         sched_setscheduler(current, SCHED_FIFO, ¶m);
 806         current->irq_thread = 1;
 807 
 808         while (!irq_wait_for_interrupt(action)) {
 809                 irqreturn_t action_ret;
 810 
 811                 irq_thread_check_affinity(desc, action);
 812 
<strong> 813                 action_ret = handler_fn(desc, action);</strong>
 814                 if (!noirqdebug)
 815                         note_interrupt(action->irq, desc, action_ret);
 816 
 817                 wake_threads_waitq(desc);
 818         }
 819 
 820         /*
 821          * This is the regular exit path. __fr
           

其中的813行會調用最終的被指派給thread_fn的原來的handler,這樣原來的中斷頂半部就整個在irq_thread裡面執行了,實作了所謂的頂半部的線程化。

繞開頂半部線程化

當然,在使能了RT-Preempt的情況之下,我們仍然可以繞開頂半部線程化的過程,避免前面的強勢變更,隻需要申請中斷的時候設定IRQ_NOTHREAD标志,如其中的patch:

Subject: arm: Mark pmu interupt IRQF_NO_THREAD
From: Thomas Gleixner <[email protected]>
Date: Wed, 16 Mar 2011 14:45:31 +0100

PMU interrupt must not be threaded. Remove IRQF_DISABLED while at it
as we run all handlers with interrupts disabled anyway.

Signed-off-by: Thomas Gleixner <[email protected]>
---
 arch/arm/kernel/perf_event.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Index: linux-stable/arch/arm/kernel/perf_event.c
===================================================================
--- linux-stable.orig/arch/arm/kernel/perf_event.c
+++ linux-stable/arch/arm/kernel/perf_event.c
@@ -430,7 +430,7 @@ armpmu_reserve_hardware(struct arm_pmu *
                }   
 
                err = request_irq(irq, handle_irq,
-                                 IRQF_DISABLED | IRQF_NOBALANCING,
+                                 IRQF_NOBALANCING | IRQF_NO_THREAD,
                                  "arm-pmu", armpmu);
                if (err) {
                        r_err("unable to request IRQ%d for ARM PMU counters\n",
           

别忘了掃二維碼關注哦~

繼續閱讀