中断线程化

最近在为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版本的实时补丁中,正是这一段代码实现了中断的线程化:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #ifdef CONFIG_IRQ_FORCED_THREADING  
  2. -extern bool force_irqthreads;  
  3. +# ifndef CONFIG_PREEMPT_RT_BASE  
  4. +   extern bool force_irqthreads;  
  5. +# else  
  6. +#  define force_irqthreads (true)  
  7. +# endif  
  8.  #else  
  9. -#define force_irqthreads   (0)  
  10. +#define force_irqthreads   (false)  
  11.  #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

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. int request_threaded_irq(unsigned int irq, irq_handler_t handler,  
  2.              irq_handler_t thread_fn, unsigned long irqflags,  
  3.              const char *devname, void *dev_id)  
  4. {  
  5.     struct irqaction *action;  
  6.     struct irq_desc *desc;  
  7.     int retval;  
  8.   
  9.     /*  
  10.      * Sanity-check: shared interrupts must pass in a real dev-ID,  
  11.      * otherwise we'll have trouble later trying to figure out  
  12.      * which interrupt is which (messes up the interrupt freeing  
  13.      * logic etc).  
  14.      */  
  15.     if ((irqflags & IRQF_SHARED) && !dev_id)    //共享中断必须有唯一确定的设备号,不然中断处理函数找不到发出中断请求的设备,注释写的很清楚  
  16.         return -EINVAL;  
  17.   
  18.     desc = irq_to_desc(irq);  
  19.     if (!desc)  
  20.         return -EINVAL;  
  21.   
  22.     if (!irq_settings_can_request(desc) ||  
  23.         WARN_ON(irq_settings_is_per_cpu_devid(desc)))  
  24.         return -EINVAL;  
  25.   
  26.     if (!handler) { //handler和thread_fn都没有指针传入肯定是出错了,有thread_fn无handler则将irq_default_primary_handler给handler  
  27.         if (!thread_fn)  
  28.             return -EINVAL;  
  29.         handler = irq_default_primary_handler;  
  30.     }  
  31.   
  32.     action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);  
  33.     if (!action)  
  34.         return -ENOMEM;  
  35.   
  36.     action->handler = handler;  
  37.     action->thread_fn = thread_fn;  
  38.     action->flags = irqflags;  
  39.     action->name = devname;  
  40.     action->dev_id = dev_id;  
  41.   
  42.     chip_bus_lock(desc);  
  43.     retval = __setup_irq(irq, desc, action);    //在__setup_irq中确定是否线程化并完成中断处理函数绑定  
  44.     chip_bus_sync_unlock(desc);  
  45.   
  46.     if (retval)  
  47.         kfree(action);  
  48.   
  49. #ifdef CONFIG_DEBUG_SHIRQ_FIXME  
  50.     if (!retval && (irqflags & IRQF_SHARED)) {  
  51.         /*  
  52.          * It's a shared IRQ -- the driver ought to be prepared for it  
  53.          * to happen immediately, so let's make sure....  
  54.          * We disable the irq to make sure that a 'real' IRQ doesn't  
  55.          * run in parallel with our fake.  
  56.          */  
  57.         unsigned long flags;  
  58.   
  59.         disable_irq(irq);  
  60.         local_irq_save(flags);  
  61.   
  62.         handler(irq, dev_id);  
  63.   
  64.         local_irq_restore(flags);  
  65.         enable_irq(irq);  
  66.     }  
  67. #endif  
  68.     return retval;  
  69. }  
request_threaded_irq函数基本上是将传入的参数放到action结构体,然后调用__setup_irq函数,线程化的具体过程在__setup_irq函数中

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. static int  
  2. __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)  
  3. {  
  4.     struct irqaction *old, **old_ptr;  
  5.     unsigned long flags, thread_mask = 0;  
  6.     int ret, nested, shared = 0;  
  7.     cpumask_var_t mask;  
  8.   
  9.     if (!desc)  
  10.         return -EINVAL;  
  11.   
  12.     if (desc->irq_data.chip == &no_irq_chip)  
  13.         return -ENOSYS;  
  14.     if (!try_module_get(desc->owner))  
  15.         return -ENODEV;  
  16.   
  17.     /*  
  18.      * Check whether the interrupt nests into another interrupt  
  19.      * thread.  
  20.      */  
  21.     nested = irq_settings_is_nested_thread(desc);  
  22.     if (nested) {  
  23.         if (!new->thread_fn) {  
  24.             ret = -EINVAL;  
  25.             goto out_mput;  
  26.         }  
  27.         /*  
  28.          * Replace the primary handler which was provided from  
  29.          * the driver for non nested interrupt handling by the  
  30.          * dummy function which warns when called.  
  31.          */  
  32.         new->handler = irq_nested_primary_handler;  
  33.     } else {  
  34.         if (irq_settings_can_thread(desc))  //request_irq调用通过设置参数_IRQ_NOTHREAD=0线程化,  
  35.                             //没有手动设置IRQ_NOTHREAD=1的中断都被线程化。Linux内核从2.6.39版本开始对中断线程化  
  36.             irq_setup_forced_threading(new);    //实时补丁使force_irqthreads=true,开启强制线程化中断  
  37.     }  
  38.   
  39.     /*  
  40.      * Create a handler thread when a thread function is supplied  
  41.      * and the interrupt does not nest into another interrupt  
  42.      * thread.  
  43.      */  
  44.     if (new->thread_fn && !nested) {  
  45.         struct task_struct *t;  
  46.         static const struct sched_param param = {  
  47.             .sched_priority = MAX_USER_RT_PRIO/2,   //所有被线程化中断优先级都为50  
  48.         };  
  49.   
  50.         t = kthread_create(irq_thread, new, "irq/%d-%s", irq,   //为中断创建内核线程  
  51.                    new->name);  
  52.         if (IS_ERR(t)) {  
  53.             ret = PTR_ERR(t);  
  54.             goto out_mput;  
  55.         }  
  56.   
  57.         sched_setscheduler(t, SCHED_FIFO, ¶m);  
  58.   
  59.         /*  
  60.          * We keep the reference to the task struct even if  
  61.          * the thread dies to avoid that the interrupt code  
  62.          * references an already freed task_struct.  
  63.          */  
  64.         get_task_struct(t);  
  65.         new->thread = t;  
  66.         /*  
  67.          * Tell the thread to set its affinity. This is  
  68.          * important for shared interrupt handlers as we do  
  69.          * not invoke setup_affinity() for the secondary  
  70.          * handlers as everything is already set up. Even for  
  71.          * interrupts marked with IRQF_NO_BALANCE this is  
  72.          * correct as we want the thread to move to the cpu(s)  
  73.          * on which the requesting code placed the interrupt.  
  74.          */  
  75.         set_bit(IRQTF_AFFINITY, &new->thread_flags);  
  76.     }  
  77.   
  78.     if (!alloc_cpumask_var(&mask, GFP_KERNEL)) {  
  79.         ret = -ENOMEM;  
  80.         goto out_thread;  
  81.     }  
  82.   
  83.     /*  
  84.      * Drivers are often written to work w/o knowledge about the  
  85.      * underlying irq chip implementation, so a request for a  
  86.      * threaded irq without a primary hard irq context handler  
  87.      * requires the ONESHOT flag to be set. Some irq chips like  
  88.      * MSI based interrupts are per se one shot safe. Check the  
  89.      * chip flags, so we can avoid the unmask dance at the end of  
  90.      * the threaded handler for those.  
  91.      */  
  92.     if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)  
  93.         new->flags &= ~IRQF_ONESHOT;  
  94.   
  95.     /*  
  96.      * The following block of code has to be executed atomically  
  97.      */  
  98.     raw_spin_lock_irqsave(&desc->lock, flags);  
  99.     old_ptr = &desc->action;  
  100.     old = *old_ptr; //action和desc都是指针,用指向指针的指针获取action的地址,再使old指向action  
  101.     if (old) {  //如果该中断号的处理程序链表desc->action本身就是空,就无所谓共享了  
  102.         /*  
  103.          * Can't share interrupts unless both agree to and are  
  104.          * the same type (level, edge, polarity). So both flag  
  105.          * fields must have IRQF_SHARED set and the bits which  
  106.          * set the trigger type must match. Also all must  
  107.          * agree on ONESHOT.  
  108.          */  
  109.         if (!((old->flags & new->flags) & IRQF_SHARED) ||  
  110.             ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) ||  
  111.             ((old->flags ^ new->flags) & IRQF_ONESHOT))  
  112.             goto mismatch;  
  113.   
  114.         /* All handlers must agree on per-cpuness */  
  115.         if ((old->flags & IRQF_PERCPU) !=  
  116.             (new->flags & IRQF_PERCPU))  
  117.             goto mismatch;  
  118.   
  119.         /* add new interrupt at end of irq queue */  
  120.         do {  
  121.             /*  
  122.              * Or all existing action->thread_mask bits,  
  123.              * so we can find the next zero bit for this  
  124.              * new action.  
  125.              */  
  126.             thread_mask |= old->thread_mask;  
  127.             old_ptr = &old->next;  
  128.             old = *old_ptr; //在desc->action链表中找到空指针,为里后面将new加进去  
  129.         } while (old);  
  130.         shared = 1;  
  131.     }  
  132.   
  133.     /*  
  134.      * Setup the thread mask for this irqaction for ONESHOT. For  
  135.      * !ONESHOT irqs the thread mask is 0 so we can avoid a  
  136.      * conditional in irq_wake_thread().  
  137.      */  
  138.     if (new->flags & IRQF_ONESHOT) {  
  139.         /*  
  140.          * Unlikely to have 32 resp 64 irqs sharing one line,  
  141.          * but who knows.  
  142.          */  
  143.         if (thread_mask == ~0UL) {  
  144.             ret = -EBUSY;  
  145.             goto out_mask;  
  146.         }  
  147.         /*  
  148.          * The thread_mask for the action is or'ed to  
  149.          * desc->thread_active to indicate that the  
  150.          * IRQF_ONESHOT thread handler has been woken, but not  
  151.          * yet finished. The bit is cleared when a thread  
  152.          * completes. When all threads of a shared interrupt  
  153.          * line have completed desc->threads_active becomes  
  154.          * zero and the interrupt line is unmasked. See  
  155.          * handle.c:irq_wake_thread() for further information.  
  156.          *  
  157.          * If no thread is woken by primary (hard irq context)  
  158.          * interrupt handlers, then desc->threads_active is  
  159.          * also checked for zero to unmask the irq line in the  
  160.          * affected hard irq flow handlers  
  161.          * (handle_[fasteoi|level]_irq).  
  162.          *  
  163.          * The new action gets the first zero bit of  
  164.          * thread_mask assigned. See the loop above which or's  
  165.          * all existing action->thread_mask bits.  
  166.          */  
  167.         new->thread_mask = 1 << ffz(thread_mask);  
  168.   
  169.     } else if (new->handler == irq_default_primary_handler &&  
  170.            !(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {  
  171.         /*  
  172.          * The interrupt was requested with handler = NULL, so  
  173.          * we use the default primary handler for it. But it  
  174.          * does not have the oneshot flag set. In combination  
  175.          * with level interrupts this is deadly, because the  
  176.          * default primary handler just wakes the thread, then  
  177.          * the irq lines is reenabled, but the device still  
  178.          * has the level irq asserted. Rinse and repeat....  
  179.          *  
  180.          * While this works for edge type interrupts, we play  
  181.          * it safe and reject unconditionally because we can't  
  182.          * say for sure which type this interrupt really  
  183.          * has. The type flags are unreliable as the  
  184.          * underlying chip implementation can override them.  
  185.          */  
  186.         pr_err("Threaded irq requested with handler=NULL and !ONESHOT for irq %d\n",  
  187.                irq);  
  188.         ret = -EINVAL;  
  189.         goto out_mask;  
  190.     }  
  191.   
  192.     if (!shared) {  //中断处理链表为空,自己创建链表  
  193.         init_waitqueue_head(&desc->wait_for_threads);  
  194.   
  195.         /* Setup the type (level, edge polarity) if configured: */  
  196.         if (new->flags & IRQF_TRIGGER_MASK) {  
  197.             ret = __irq_set_trigger(desc, irq,  
  198.                     new->flags & IRQF_TRIGGER_MASK);  
  199.   
  200.             if (ret)  
  201.                 goto out_mask;  
  202.         }  
  203.   
  204.         desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \  
  205.                   IRQS_ONESHOT | IRQS_WAITING);  
  206.         irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);  
  207.   
  208.         if (new->flags & IRQF_PERCPU) {  
  209.             irqd_set(&desc->irq_data, IRQD_PER_CPU);  
  210.             irq_settings_set_per_cpu(desc);  
  211.         }  
  212.   
  213.         if (new->flags & IRQF_ONESHOT)  
  214.             desc->istate |= IRQS_ONESHOT;  
  215.   
  216.         if (irq_settings_can_autoenable(desc))  
  217.             irq_startup(desc, true);  
  218.         else  
  219.             /* Undo nested disables: */  
  220.             desc->depth = 1;  
  221.   
  222.         /* Exclude IRQ from balancing if requested */  
  223.         if (new->flags & IRQF_NOBALANCING) {  
  224.             irq_settings_set_no_balancing(desc);  
  225.             irqd_set(&desc->irq_data, IRQD_NO_BALANCING);  
  226.         }  
  227.   
  228.         if (new->flags & IRQF_NO_SOFTIRQ_CALL)  
  229.             irq_settings_set_no_softirq_call(desc);  
  230.   
  231.         /* Set default affinity mask once everything is setup */  
  232.         setup_affinity(irq, desc, mask);  
  233.   
  234.     } else if (new->flags & IRQF_TRIGGER_MASK) {  
  235.         unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;  
  236.         unsigned int omsk = irq_settings_get_trigger_mask(desc);  
  237.   
  238.         if (nmsk != omsk)  
  239.             /* hope the handler works with current  trigger mode */  
  240.             pr_warning("irq %d uses trigger mode %u; requested %u\n",  
  241.                    irq, nmsk, omsk);  
  242.     }  
  243.   
  244.     new->irq = irq;  
  245.     *old_ptr = new; //添加到desc->action链表  
  246.   
  247.     /* Reset broken irq detection when installing new handler */  
  248.     desc->irq_count = 0;  
  249.     desc->irqs_unhandled = 0;  
  250.   
  251.     /*  
  252.      * Check whether we disabled the irq via the spurious handler  
  253.      * before. Reenable it and give it another chance.  
  254.      */  
  255.     if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) {  
  256.         desc->istate &= ~IRQS_SPURIOUS_DISABLED;  
  257.         __enable_irq(desc, irq, false);  
  258.     }  
  259.   
  260.     raw_spin_unlock_irqrestore(&desc->lock, flags);  
  261.   
  262.     /*  
  263.      * Strictly no need to wake it up, but hung_task complains  
  264.      * when no hard interrupt wakes the thread up.  
  265.      */  
  266.     if (new->thread)  
  267.         wake_up_process(new->thread);    //内核线程开始运行  
  268.   
  269.     register_irq_proc(irq, desc);   //创建/proc/irq/目录及文件(smp_affinity,smp_affinity_list 等 )  
  270.     new->dir = NULL;  
  271.     register_handler_proc(irq, new);  
  272.     free_cpumask_var(mask); //创建proc/irq/<irq>/handler/   
  273.   
  274.     return 0;  
  275.   
  276. mismatch:  
  277.     if (!(new->flags & IRQF_PROBE_SHARED)) {  
  278.         pr_err("Flags mismatch irq %d. %08x (%s) vs. %08x (%s)\n",  
  279.                irq, new->flags, new->name, old->flags, old->name);  
  280. #ifdef CONFIG_DEBUG_SHIRQ  
  281.         dump_stack();  
  282. #endif  
  283.     }  
  284.     ret = -EBUSY;  
  285.   
  286. out_mask:  
  287.     raw_spin_unlock_irqrestore(&desc->lock, flags);  
  288.     free_cpumask_var(mask);  
  289.   
  290. out_thread:  
  291.     if (new->thread) {  
  292.         struct task_struct *t = new->thread;  
  293.   
  294.         new->thread = NULL;  
  295.         kthread_stop(t);  
  296.         put_task_struct(t);  
  297.     }  
  298. out_mput:  
  299.     module_put(desc->owner);  
  300.     return ret;  
  301. }  
__setup_irq的内容比较多点,首先通过nested判断该中断是否属于其他中断进程,即和别的中断共享同一个中断号,如果不是,判断是否强制将该中断线程化,很明显打了实时补丁后使能强制线程化中断,强制线程化如果thread_fn为空会使thread_fn指向handler,而handler指向默认的句柄函数,其实在强制中断线程化没有开启的情况下,request_threaded_irq函数根据thread_fn是否为空判断是否将该中断线程化。这里强制线程化后thread_fn显然不会为空。
接下来因为是首次在该中断线创建处理函数,申请一个内核线程,设置线程调度策略(FIFO)和优先级(50),为了让使用该中断号的其他进程共享这条中断线,还必须建立一个中断处理进程action的单向链表,设置一些共享标识等。但是如果现在申请的这个中断与其他已经建立中断内核线程的中断共享中断线,那么就不需要再次建立内核线程和队列,只需在队列中找到空指针(一般是末尾)并插入队列即可。做完这些之后唤醒内核进程(kthread_create)创建的内核进程不能马上执行,需要唤醒。

在Linux中申请中断还可以通过request_any_context_irq、devm_request_threaded_irq等函数,他们最终都调用request_threaded_irq,request_threaded_irq函数的完整形式如下:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. int request_threaded_irq(unsigned int irq, irq_handler_t handler,  
  2.              irq_handler_t thread_fn, unsigned long irqflags,  
  3.              const char *devname, void *dev_id)  
在没有强制中断线程化的时候,thread_fn不为空即可将该中断线程化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值