【Linux kernel】中斷

-------------------------中斷上下文注意事項------------------------
1)"中斷上下文(包括軟中斷上下文)不可以調用schedule()函數及其封裝函數,如msleep(msecs), 因爲不允許在中斷上下文發生進程調”(在支持內核搶佔的情況下不成立),
但是可以調用wake_up_process(task)及其封裝函數,如schedule_work(work)和tasklet_schedule()
2)爲避免睡眠的可能性,不能使用信號量和互斥鎖,而應該使用自旋鎖 spin lock及其變體。
3)中斷上下文不能使用帶GFP_KERNEL標誌的kmalloc函數,應該使用GFP_ATOMIC標誌
4)共享中斷
4.1)調用irq_request時,要將dev_id傳入,以使得自己的設備action能被free.
4.2)因爲同一根中斷線上的所有action->handler(也就是ISR)都會被調用(見handle_irq_event_percpu函數),所以要在自己的ISR中讀取寄存器的狀態判斷是否真的是自己的設備發生了中斷, 如果不是則要返回IRQ_NONE, 否則返回其他值,如IRQ_HANDLED.

-------------中斷和軟中斷被調用的流程----------------------
 /* Interrupt dispatcher */   //entry-armv.S定義了中斷向量表
 vector_stub irq, IRQ_MODE, 4
 .long __irq_usr   @  0  (USR_26 / USR_32)   //irq_usr
 .long __irq_invalid   @  1  (FIQ_26 / FIQ_32)
 .long __irq_invalid   @  2  (IRQ_26 / IRQ_32)
 .long __irq_svc   @  3  (SVC_26 / SVC_32)  //irq_svc
->__irq_usr:
        irq_handler    //中斷處理入口
        b ret_to_user_from_irq
    ENDPROC(__irq_usr)
    ->.macro irq_handler
          arch_irq_handler_default  //這是默認的irq hanlder, 一般在liunx中會定義MULTI_IRQ_HANDLER配置項,它意味着允許平臺的代碼可以動態設置irq處理程序:平臺代碼可以修改全局變量:handle_arch_irq,從而可以修改irq的處理程序。
         .endm
          -> .macro arch_irq_handler_default
              bne asm_do_IRQ   //asm_do_IRQ(unsigned int irq, struct pt_regs *regs) 
              .endm
            ->handle_IRQ(irq) //處於中斷上下文
                -> irq_enter()
                -> generic_handle_irq(irq)  //處理硬中斷irq
                     ->desc->handle_irq(irq, desc)   //==handle_level_irq()或handle_edge_irq()
                                  <- __irq_set_handler_locked()?
                      ==handle_level_irq()
                         ->handle_irq_event()
                             ->handle_irq_event_percpu()
                                  {
                                        do {
                                            res = action->handler(irq, action->dev_id);  //irq中斷處理函數
                                            switch (res) {
                                                case IRQ_WAKE_THREAD:
                                                     irq_wake_thread(desc, action);  //喚醒irq thread(置爲TASK_WAKING狀態)
                                            }
                                            irqaction* action = action->next;  //一個共享中斷有多個irqaction(如多個設備共享一箇中斷號)               
                                         } while (action);
                                   }
                ->irq_exit()  //由此可以看出,軟中斷是中斷下半部分的一種處理機制。
                     ->invoke_softirq()
                          ->__do_softirq()
                            {
                                softirq_action *h =softirq_vec;
                                //開始遍歷HRTIMER_SOFTIRQ、TIMER_SOFTIRQ、TASKLET_SOFTIRQ等類型的軟中斷
                                do { 
                                  h->action(h) //==tasklet_action()(如果當前遍歷的是TASKLET_SOFTIRQ類型的軟中斷的話; 也有可能是run_timer_softirq())
                                        ->(tasklet_struct*)t->func(t->data) //遍歷tasklet_vec tasklet由DECLARE_TASKLET定義的;此時tasklet運行於中斷上下文, 因此tasklet中不能睡眠。
                                    h++;
                                    pending >>= 1;
                                  } while (pending);
                                  
                                  pending = local_softirq_pending(); 
                                  if (pending)  
                                     wakeup_softirqd();  
                                 
                            }
irq_set_irq_type() ?
-> __irq_set_trigger()
     -> ret = chip->irq_set_type(&desc->irq_data, flags);
 
------------------------------tasklet-----------------
tasklet_schedule() //一般是在中斷處理函數中調用,也就是說在中斷上下文中raise這宗類型的softirq,等待在下半段irq_exit()中來執行
->raise_softirq_irqoff(TASKLET_SOFTIRQ) //在禁止中斷的情況下觸發tasklet類型的軟中斷.由open_softirq(TASKLET_SOFTIRQ,tasklet_action) 來註冊的
     -> wakeup_softirqd()
           -> wake_up_process(ksoftirqd)  // 喚醒ksoftirqd內核線程處理軟中斷(即中斷的下半部分處理),每個CPU有這樣一個內核線程
		   
從tasklet_action()可以看出,tasklet有以下幾個特徵:
1)同一個tasklet只能同時在一個cpu上執行,但不同的tasklet可以同時在不同的cpu上執行;所以tasklet不必是可重入的
2)一旦tasklet_schedule被調用,內核會保證tasklet一定會在某個cpu上執行一次;
3)如果tasklet_schedule被調用時,tasklet不是出於正在執行狀態,則它只會執行一次;
4)如果tasklet_schedule被調用時,tasklet已經正在執行,則它會在稍後被調度再次被執行;
5)兩個tasklet之間如果有資源衝突,應該要用自旋鎖進行同步保護

run_ksoftirqd() //ksoftirqd 內核線程的入口函數
    ->__do_softirq() //在禁止中斷的情況下調用,此時tasklet運行於內核線程中
	
----------------------CPU中斷的開和關----------------------------------
#define local_irq_save(flags)
->raw_local_irq_save(flags);
    ->flags = arch_local_irq_save();
        unsigned long arch_local_irq_save(void)//CPU interrupt mask handling.
        {
            unsigned long flags;
            asm volatile(
            "mrs %0, cpsr @ arch_local_irq_save\n"  //將CPSR狀態寄存器讀取,保存到flags中
            "cpsid  i"  //關中斷
            : "=r" (flags) : : "memory", "cc");
            return flags;
        }
另外,
#define local_irq_restore(flags)
   ->raw_local_irq_restore
        ->arch_local_irq_restore
            ->arch_local_irq_restore
                arch_local_irq_restore(unsigned long flags) //restore saved IRQ&FIQ state
                {
                asm volatile(
                "msr cpsr_c, %0 @ local_irq_restore"  //將flags寫會到CPSR
                :
                : "r" (flags)
                : "memory", "cc");
                }

--------------------------irq thread(中斷線程化)----------------------
1) irq thread是TASK_INTERRUPTIBLE的
2) 入口函數是:irq_thread()
-----------workqueue-------------------
主要文件:workqueue.c, workqueue.h
主要數據結構:
structwork_struct{
 atomic_long_t data;
 struct list_head entry;
 work_func_tfunc;
#ifdef CONFIG_LOCKDEP
 struct lockdep_map lockdep_map;
#endif
};
/*
 * Global per-cpu workqueue.  There's one and only one for each cpu
 * and all works are queued and processed here regardless of their
 * target workqueues.
 */
structglobal_cwq
{
    struct list_head worklist; /* L: list of pending works */
}
 
/*
 * The poor guys doing the actual heavy lifting.  All on-duty workers
 * are either serving the manager role, on idle list or on busy hash.
 */
structworker
{
    struct work_struct *current_work; /* L: work being processed */
    struct task_struct *task;  /* I: worker task */
}
/*
 * The per-CPU workqueue.  The lower WORK_STRUCT_FLAG_BITS of
 * work_struct->data are used for flags and thus cwqs need to be
 * aligned at two's power of the number of flag bits.
 */
structcpu_workqueue_struct
{
    struct global_cwq *gcwq;  /* I: the associated gcwq */
     struct workqueue_struct *wq;  /* I: the owning workqueue */
}
/*
 * The externally visible workqueue abstraction is an array of
 * per-CPU workqueues:
 */
structworkqueue_struct
{
    union {
      struct cpu_workqueue_struct __percpu *pcpu;
      struct cpu_workqueue_struct  *single;
      unsigned long    v;
     }cpu_wq;
    char   name[];  /* I: workqueue name */
}
主要接口:
//用於創建一個workqueue隊列,爲系統中的每個CPU都創建一個內核線程
#definecreate_workqueue(name)     \
                 alloc_workqueue((name), WQ_MEM_RECLAIM, 1)
//用於創建workqueue,只創建一個內核線程
#define create_singlethread_workqueue(name)   \
                 alloc_workqueue((name), WQ_UNBOUND | WQ_MEM_RECLAIM, 1)
#define alloc_workqueue(fmt, flags, max_active, args...) \
         __alloc_workqueue_key((fmt), (flags), (max_active), \
                 NULL, NULL, ##args)
//調度執行一個具體的任務,執行的任務將會被掛入Linux系統提供的workqueue: system_wq
int schedule_work(struct work_struct *work)
{
     returnqueue_work(system_wq, work); //調度執行一個指定workqueue中的任務
}
//延遲一定時間去執行一個具體的任務,功能與schedule_work類似,多了一個延遲時間
int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
{
     returnqueue_delayed_work(system_wq, dwork, delay);
}
static LIST_HEAD(workqueues);
extern struct workqueue_struct *system_wq;
extern struct workqueue_struct *system_long_wq;
extern struct workqueue_struct *system_nrt_wq;
extern struct workqueue_struct *system_unbound_wq;
extern struct workqueue_struct *system_freezable_wq;
extern struct workqueue_struct *system_nrt_freezable_wq;
當用戶調用workqueue的初始化接口create_workqueue或者create_singlethread_workqueue對workqueue隊列進行初始化時,內核就開始爲用戶分配一個workqueue對象,並且將其鏈到一個全局的workqueue隊列中。然後Linux根據當前CPU的情況,爲workqueue對象分配與CPU個數相同的cpu_workqueue_struct對象,每個cpu_workqueue_struct對象都會存在一條任務隊列。緊接着,Linux爲每個cpu_workqueue_struct對象分配一個內核thread,即內核daemon去處理每個隊列中的任務。至此,用戶調用初始化接口將workqueue初始化完畢,返回workqueue的指針。
       在初始化workqueue過程中,內核需要初始化內核線程,註冊的內核線程工作比較簡單,就是不斷的掃描對應cpu_workqueue_struct中的任務隊列,從中獲取一個有效任務,然後執行該任務。所以如果任務隊列爲空,那麼內核daemon就在cpu_workqueue_struct中的等待隊列上睡眠,直到有人喚醒daemon去處理任務隊列。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章