轉自http://my.oschina.net/fzliu/blog/33028
儘管有些設備僅通過它們的I/O寄存器就可以得到控制,但現實中的大部分設備卻比這複雜一些。設備需要與外部世界打交道,如旋轉的磁盤,繞卷的磁帶,遠距離連接的電纜等。這些設備的許多工作通常是在與處理器完全不同的時間週期內完成的,並且總是要比處理器慢。這種讓處理器等待外部事件的情況總是不能令人滿意,所以必須有一種方法可以讓設備在產生某個事件時通知處理器,這種方法就是中斷。在大多數情況下,一個驅動程序只需要爲它自己設備的中斷註冊一個處理例程,並在中斷到達時進行正確處理。從本質上講,中斷處理例程和其它代碼併發運行,對併發控制技術的透徹理解對處理中斷來講非常重要——引自linux
device driver。
安裝中斷處理例程:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
釋放中斷號:
void free_irq(unsigned int irq, void *dev_id)
irq爲中斷號使用cat /proc/interrupts 可查看當前已經使用的中斷號。 handler中斷處理函數,irqflags設置中斷方式,如上升沿,下降沿,低電平觸發等,devname爲中斷的名字(隨意)。dev_id 用於共享的中斷信號線,必須唯一,可以用它來指向驅動程序的私有數據區(用來識別哪個設備產生中斷),可以設置爲NULL。
其中,irqflags定義於<linux/irq.h>,irq定義於 arch/arm/mach-s3c2410/include/mach/irqs.h(對於s3c2410來說)。
中斷處理例程可在驅動程序初始化時或者第一次打開設備時進行安裝,但爲斷信號線的數量是非常有限的,如果一個模塊在初始化時請求了IRQ,那麼即使驅動程序只是佔用它而從未使用,也將阻止其它驅動使用該中斷,而在打開設備時申請中斷,則可以其享這些有限的中斷資源。
調用request_irq的正確位置應該是在設備第一次打開,硬件被告知產生中斷之前。調用free_irq的位置是最後一次關閉設備,硬件被告知不用再中斷處理器之後。
中斷處理例程的實現:
static irqreturn_t key_interrupt(int irq, void *dev_id,sturct pt_regs *regs);通常使用下面的方式,只傳入兩個參數。
static irqreturn_t key_interrupt(int irq, void *dev_id);
中斷處理例程應該返回一個值,用來指明是否真正處理了一箇中斷,如果處理例程確實發現其設備的確處理,應該返回IRQ_HANDLED;否則返回IRQ_NONE。可使用宏IRQ_RETVAL(handle)來產生這個返回值。
中斷處理例程是在中斷時間內運行的,它的行爲會受到一些限制。處理例程不能向用戶空間發送或者接收數據,因爲它不是任何進程的上下文中執行的,處理例程不能做任何可能發生休眠的操作,例如調用wait_event,使用不帶GFP_ATOMIC標誌的內存分配操作,或者鎖住一個信號量等等,不能調用schdule函數。
中斷處理例程的一個典型任務是:如果中斷通知進程所等待的事件已發生,就會喚醒在該設備上休眠的進程。
中斷處理例程應該儘可能的短,執行一個長時間的任務時最好的方法是使用底半部機制。
Linux內核中斷機制:爲了在中斷執行時間儘可能短和中斷處理需要完成大量工作之間找到一個平衡點,Linux將中斷處理程序分解爲兩個半部,頂半部和底半部。
頂半部完成儘可能少的比較緊急的任務,它往往只是簡單地讀取寄存器中的中斷狀態並清除中斷標誌位就進行“登記工作”,將底半部處理程序掛到該設備的底半部執行隊列中去。
Linux實現下半部的機制主要有tasklet和工作隊列。
一:tasklet
記住tasklet是一個可以在由系統決定的安全時刻在軟件中斷上下文被調度運行的特殊函數,它們可以被多次調度運行,但tasklet不會積累,也就是說,實際只會運行一次。不會有同一個tasklet的多個實例並行的運行,因爲它們只運行一次,但是tasklet可以與其它的tasklet並行的運行在對稱多處理器(SMP)系統上。如果驅動程序中有多個tasklet,它們必須使用某種機制鎖來避免衝突。tasklet在中斷處理例程結束前不會開始運行,tasklet運行時,可以有其它的中斷髮生。tasklet通常是底半部處理的優選機制,因爲這種機制非常快,但是所有的tasklet都必須是原子的。
tasklet使用相當簡單,我們只需要定義tasklet及其處理函數並將二者關聯:
void my_tasklet_func(unsigned long); //定義一個處理函數:
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); //定義一個tasklet結構my_tasklet,與
my_tasklet_func(data)函數相關聯 。data爲傳入處理函數的參數。
然後,在需要調度tasklet的時候引用一個簡單的API就能使系統在適當的時候進行調度運行: tasklet_schedule(&my_tasklet);
實例:
02 |
void test_tasklet_action(unsigned long t); |
03 |
DECLARE_TASKLET(test_tasklet,
test_tasklet_action, 0); |
05 |
void test_tasklet_action(unsigned long t) |
07 |
printk( "tasklet
is executing\n" ); |
12 |
static irqreturn_t
xxx_interrupt( int irq, void *dev_id) |
15 |
tasklet_schedule(&test_tasklet); |
21 |
int __init
xxx_init( void ) |
24 |
request_irq(IRQ_EINT0,xxx_interrupt,
IRQ_TYPE_LEVEL_LOW, "xxx" ,
NULL); |
30 |
void __exit
xxx_exit( void ) |
二:工作隊列
工作隊列會在將來某個時間,在某個特殊的工作者進程上下文中調用一個函數,因爲工作隊列函數運行在進程上下文中,因此可在必要時進行休眠,但是我們不能從工作隊列向用戶空間複製數據,要知道工作者進程無法訪問其它任何進程的地址空間,除非使用進程間工享技術。
struct work_struct my_wq;/*定義一個工作隊列*/
void my_wq_func(unsigned long);/*定義一個處理函數*/
INIT_WORK(&my_wq,(void (*)(void *))my_wq_func,NULL);/*初始化工作隊列並將其與處理函數綁定*/
示例:
01 |
struct work_struct
my_wq; |
03 |
void my_wq_func(unsigned long ); |
06 |
void my_wq_func(unsigned long t) |
13 |
static irqreturn_t
xxx_interrupt( int irq, void *dev_id) |
16 |
schedule_work(&
my_wq); |
23 |
int __init
xxx_init( void ) |
26 |
request_irq(IRQ_EINT0,xxx_interrupt,
IRQ_TYPE_LEVEL_LOW, "xxx" ,
NULL); |
27 |
INIT_WORK(&my_wq,( void (*)( void *))my_wq_func,NULL); |
33 |
void __exit
xxx_exit( void ) |