linux中斷線程化分析【轉】


轉自:http://blog.csdn.net/qq405180763/article/details/24120895

最近在為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版本的實時補丁中,正是這一段代碼實現了中斷的線程化:

  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

  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函數中

  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函數的完整形式如下:

  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不為空即可將該中斷線程化。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM