最近在为3.8版本的Linux内核打RT_PREEMPT补丁,并且优化系统实时性,这篇文章主要对RTlinux中中断线程化部分进行分析。我们知道在RT_PREEMPT补丁中之所以要将中断线程化就是因为硬中断的实时性太高,会影响实时进程的实时性,所以需要将中断处理程序线程化并设置优先级,使中断处理线程的优先级比实时进程优先级低,从而提高系统实时性。
网上看到一些网友说在2.6.25.8版本的内核,linux引入了中断线程化,具体是不是2.6.25.8版本开始引入中断线程化我没有去求证,因为版本比较老了改动很多,但据我的查证从2.6.30开始内核引入request_threaded_irq函数,从这个版本开始可以通过在申请中断时为request_irq设置不同的参数决定是否线程化该中断。而在2.6.39版内核__setup_irq引入irq_setup_forced_threading函数,开始可以通过# define force_irqthreads(true)强制使中断线程化,那么从这个版本开始想实现中断线程化就已经变得很简单了,让force_irqthreads为真即可,所以在3.8版本的实时补丁中,正是这一段代码实现了中断的线程化:
#ifdef CONFIG_IRQ_FORCED_THREADING -extern bool force_irqthreads; +# ifndef CONFIG_PREEMPT_RT_BASE + extern bool force_irqthreads; +# else +# define force_irqthreads (true) +# endif #else -#define force_irqthreads (0) +#define force_irqthreads (false) #endif下面我们开始正式介绍中断线程化是怎么实现的。
Linux内核常见申请中断的函数request_irq,在内核源码include/linux/interrupt.h头文件中可以看到request_irq仅包含return request_threaded_irq(irq, handler, NULL, flags, name, dev);调用,request_threaded_irq函数在源码目录kernel/irq/manage.c文件中,下面通过分析manage.c中各个相关函数解读中断线程化的实现过程。
根据request_irq的调用,首先分析request_threaded_irq
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; /* * 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). */ if ((irqflags & IRQF_SHARED) && !dev_id) //共享中断必须有唯一确定的设备号,不然中断处理函数找不到发出中断请求的设备,注释写的很清楚 return -EINVAL; desc = irq_to_desc(irq); if (!desc) return -EINVAL; if (!irq_settings_can_request(desc) || WARN_ON(irq_settings_is_per_cpu_devid(desc))) return -EINVAL; if (!handler) { //handler和thread_fn都没有指针传入肯定是出错了,有thread_fn无handler则将irq_default_primary_handler给handler if (!thread_fn) return -EINVAL; handler = irq_default_primary_handler; } action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); if (!action) return -ENOMEM; action->handler = handler; action->thread_fn = thread_fn; action->flags = irqflags; action->name = devname; action->dev_id = dev_id; chip_bus_lock(desc); retval = __setup_irq(irq, desc, action); //在__setup_irq中确定是否线程化并完成中断处理函数绑定 chip_bus_sync_unlock(desc); if (retval) 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; }request_threaded_irq函数基本上是将传入的参数放到action结构体,然后调用__setup_irq函数,线程化的具体过程在__setup_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; cpumask_var_t mask; if (!desc) return -EINVAL; if (desc->irq_data.chip == &no_irq_chip) return -ENOSYS; if (!try_module_get(desc->owner)) return -ENODEV; /* * Check whether the interrupt nests into another interrupt * 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 { if (irq_settings_can_thread(desc)) //request_irq调用通过设置参数_IRQ_NOTHREAD=0线程化, //没有手动设置IRQ_NOTHREAD=1的中断都被线程化。Linux内核从2.6.39版本开始对中断线程化 irq_setup_forced_threading(new); //实时补丁使force_irqthreads=true,开启强制线程化中断 } /* * 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) { struct task_struct *t; static const struct sched_param param = { .sched_priority = MAX_USER_RT_PRIO/2, //所有被线程化中断优先级都为50 }; t = kthread_create(irq_thread, new, "irq/%d-%s", irq, //为中断创建内核线程 new->name); if (IS_ERR(t)) { ret = PTR_ERR(t); goto out_mput; } sched_setscheduler(t, SCHED_FIFO, ¶m); /* * 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. */ get_task_struct(t); new->thread = t; /* * 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. */ set_bit(IRQTF_AFFINITY, &new->thread_flags); } if (!alloc_cpumask_var(&mask, GFP_KERNEL)) { ret = -ENOMEM; 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. */ if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE) new->flags &= ~IRQF_ONESHOT; /* * The following block of code has to be executed atomically */ raw_spin_lock_irqsave(&desc->lock, flags); old_ptr = &desc->action; old = *old_ptr; //action和desc都是指针,用指向指针的指针获取action的地址,再使old指向action if (old) { //如果该中断号的处理程序链表desc->action本身就是空,就无所谓共享了 /* * 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. */ if (!((old->flags & new->flags) & IRQF_SHARED) || ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) || ((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; //在desc->action链表中找到空指针,为里后面将new加进去 } 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) { /* * Unlikely to have 32 resp 64 irqs sharing one line, * but who knows. */ if (thread_mask == ~0UL) { ret = -EBUSY; goto out_mask; } /* * The thread_mask for the action is or'ed to * desc->thread_active to indicate that the * IRQF_ONESHOT thread handler has been woken, but not * yet finished. The bit is cleared when a thread * completes. When all threads of a shared interrupt * line have completed desc->threads_active becomes * zero and the interrupt line is unmasked. See * handle.c:irq_wake_thread() for further information. * * If no thread is woken by primary (hard irq context) * interrupt handlers, then desc->threads_active is * also checked for zero to unmask the irq line in the * affected hard irq flow handlers * (handle_[fasteoi|level]_irq). * * The new action gets the first zero bit of * thread_mask assigned. See the loop above which or's * all existing action->thread_mask bits. */ new->thread_mask = 1 << 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 irq %d\n", irq); ret = -EINVAL; goto out_mask; } if (!shared) { //中断处理链表为空,自己创建链表 init_waitqueue_head(&desc->wait_for_threads); /* Setup the type (level, edge polarity) if configured: */ if (new->flags & IRQF_TRIGGER_MASK) { ret = __irq_set_trigger(desc, irq, new->flags & IRQF_TRIGGER_MASK); if (ret) goto out_mask; } desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \ IRQS_ONESHOT | IRQS_WAITING); irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); if (new->flags & IRQF_PERCPU) { irqd_set(&desc->irq_data, IRQD_PER_CPU); irq_settings_set_per_cpu(desc); } if (new->flags & IRQF_ONESHOT) desc->istate |= IRQS_ONESHOT; if (irq_settings_can_autoenable(desc)) irq_startup(desc, true); else /* Undo nested disables: */ desc->depth = 1; /* Exclude IRQ from balancing if requested */ if (new->flags & IRQF_NOBALANCING) { irq_settings_set_no_balancing(desc); irqd_set(&desc->irq_data, IRQD_NO_BALANCING); } if (new->flags & IRQF_NO_SOFTIRQ_CALL) irq_settings_set_no_softirq_call(desc); /* Set default affinity mask once everything is setup */ setup_affinity(irq, desc, mask); } else if (new->flags & IRQF_TRIGGER_MASK) { unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK; unsigned int omsk = irq_settings_get_trigger_mask(desc); if (nmsk != omsk) /* hope the handler works with current trigger mode */ pr_warning("irq %d uses trigger mode %u; requested %u\n", irq, nmsk, omsk); } new->irq = irq; *old_ptr = new; //添加到desc->action链表 /* Reset broken irq detection when installing new handler */ desc->irq_count = 0; desc->irqs_unhandled = 0; /* * Check whether we disabled the irq via the spurious handler * before. Reenable it and give it another chance. */ if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) { desc->istate &= ~IRQS_SPURIOUS_DISABLED; __enable_irq(desc, irq, false); } raw_spin_unlock_irqrestore(&desc->lock, flags); /* * Strictly no need to wake it up, but hung_task complains * when no hard interrupt wakes the thread up. */ if (new->thread) wake_up_process(new->thread); //内核线程开始运行 register_irq_proc(irq, desc); //创建/proc/irq/目录及文件(smp_affinity,smp_affinity_list 等 ) new->dir = NULL; register_handler_proc(irq, new); free_cpumask_var(mask); //创建proc/irq/<irq>/handler/ 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_mask: raw_spin_unlock_irqrestore(&desc->lock, flags); free_cpumask_var(mask); out_thread: if (new->thread) { struct task_struct *t = new->thread; new->thread = NULL; kthread_stop(t); put_task_struct(t); } out_mput: module_put(desc->owner); return ret; }__setup_irq的内容比较多点,首先通过nested判断该中断是否属于其他中断进程,即和别的中断共享同一个中断号,如果不是,判断是否强制将该中断线程化,很明显打了实时补丁后使能强制线程化中断,强制线程化如果thread_fn为空会使thread_fn指向handler,而handler指向默认的句柄函数,其实在强制中断线程化没有开启的情况下,request_threaded_irq函数根据thread_fn是否为空判断是否将该中断线程化。这里强制线程化后thread_fn显然不会为空。
在Linux中申请中断还可以通过request_any_context_irq、devm_request_threaded_irq等函数,他们最终都调用request_threaded_irq,request_threaded_irq函数的完整形式如下:
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)在没有强制中断线程化的时候,thread_fn不为空即可将该中断线程化。