Linux RT(2)-硬实时Linux(RT-Preempt Patch)的中断线程化推荐

特别声明:本系列文章LiAnLab.org著作权所有,转载请注明出处。by @宋宝华Barry

底半部:线程化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 handler, irq_handler_t thread_fn, 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_checkrequest_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.Sarch/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;138 do {139 irqreturn_t res;141 trace_irq_handler_entry(irq, action);142 res = action- handler(irq, action- dev_id);143 trace_irq_handler_exit(irq, action, res);145 if (WARN_ONCE(!irqs_disabled(), irq %u handler %pF enabled interrupts\n ,146 irq, action- handler))147 local_irq_disable();149 switch (res) {150 case IRQ_WAKE_THREAD:151 /*152 * Catch drivers which return WAKE_THREAD but153 * did not set up a thread function154 */155 if (unlikely(!action- thread_fn)) {156 warn_no_thread(irq, action);157 break;158 }160 irq_wake_thread(desc, action);162 /* Fall through to add to randomness */163 case IRQ_HANDLED:164 flags |= action- flags;165 break;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  813 action_ret = handler_fn(desc, action); 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_THREADFrom: Thomas Gleixner tglx@linutronix.de Date: Wed, 16 Mar 2011 14:45:31 +0100PMU interrupt must not be threaded. Remove IRQF_DISABLED while at itas we run all handlers with interrupts disabled anyway.Signed-off-by: Thomas Gleixner tglx@linutronix.de  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 ,

你可以很有个性,但某些时候请收敛。

Linux RT(2)-硬实时Linux(RT-Preempt Patch)的中断线程化推荐

相关文章:

你感兴趣的文章:

标签云: