-------------------------中斷上下文注意事項------------------------
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去處理任務隊列。