頂半部和底半部
Linux系統通過將中斷處理常式分成兩部分來解決這個問題。稱為“頂半部”的部分,是實際響應中斷的常式,也就是用request_irq註冊的中斷常式;而所謂的“底半部”是一個被頂半部調度,並在稍後更安全的時間內執行的常式。頂半部處理常式和底半部處理常式之間最大的不同,就是當底半部處理常式執行時,所有的中斷都是開啟的---這就是所謂的在更安全的時間內運行。典型的情況是頂半部儲存裝置的資料到一個裝置特定的換從去並調度它的底半部,然後退出。頂半部所做的操作是非常快的,然後,底半部執行其他必要的工作,例如:喚醒進程、啟動另外的I/O操作等等。這種方式允許在底半部工作時間內,頂半部還可以繼續為新的中斷服務。
linux核心有兩種不同的機制可以用來實現底半部處理。
1.tasklet通常是首選的機制,因為這種機制快,但是所有的tasklet代
碼必須是原子的。
2.工作隊列,它可以具有較高的延時,但允許休眠。
頂半部執行得都很快,因為它僅儲存目前時間並調度底半部。然後底半部負責這些時間的編碼,並喚醒可能等待資料的任何使用者進程。
這裡有個文章可以參考:http://judicious.bokee.com/5864176.html
tasklet
tasklet是一個可以在由系統決定的安全時刻在軟體中斷上下文被調度啟動並執行特殊函數。他們可以被多次調度運行,但tasklet的調度不會累積;也就是說,實際只會運行一次,即使在啟用tasklet的運行之前重複請求該tasklet的運行 也是一樣,不會有同一tasklet的多個執行個體並行的運行,因為他們只運行一次,但是tasklet可以與其他的taklet並行地運行在對稱式多處理器系統上。這樣驅動程式有多個tasklet,他們必須使用某種鎖機制來避免彼此間的衝突。
tasklet可以確保和第一次調度他們的函數運行在同樣的CPU上,這樣,因為tasklet在中斷處理常式結束前並不會開始運行,所以此時的中斷處理常式是安全的。但是在tasklet運行時,當然可以有其他的中斷髮生,因此tasklet和中斷處理常式之間的鎖還是必須的。
執行個體:
必須使用宏DECLEAR_TASKLET聲明tasklet:
DECLEAR_TASKLET(name,function,data);
name是給tasklet起的名字,function是執行tasklet時調用的函數(它帶有一個unsigned long型的參數並且返回void),data是一個用來傳遞給tasklet函數的unsigned long型別參數。
驅動程式中的例子:
void short_do_tasklet(unsigned long) //聲明tasklet的處理函數
DECLEAR_TASKLET(short_tasklet,short_do_tasklet,0);
函數tasklet_schedule用來調度一個tasklet的運行。如果指定tasklet=1選項裝載short,它就會安裝一個不同的中斷處理常式,這個常式儲存資料並如下調度tasklet:
irqreturn_t short_tl_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
do_gettimeofday((struct timeval *)tv_head); //強制轉換一下以免出現“易失”性錯誤
short_incr_tv(&tv_head);
tasklet_schedule(&short_tasklet);
short_wq_count++; //記錄中斷產生次數
return IRQ_HANDLED;
}
實際的tasklet常式,即short_do_tasklet會在系統方便時得到執行,就像先前提到,這個常式執行中斷處理的大多數任務。
void short_do_tasklet(unsigned long unused)
{
int savecount = short_wq_count,written;
short_wq_count = 0; //已經從隊列中移除
/*首先將調用此bh之前發生的中斷數量寫入
written = sprintf((char *)short_head,"bh after %6i/n"
,savecount);
short_incr_bp(&short_head,written);
/*底半部讀取有頂半部填充的tv數組,並向迴圈文本緩衝區中列印資訊,而緩衝區的資料則由讀進程獲得
/*然後寫入時間值,每次寫入16位元組,所以它與PAGE_SIZE對齊
do{
written = sprintf((char *)short_head,“%08u.%06u/n",(int)(tv_tail->tv_sec % 10000000),(int)(tv_tail->tv_usec));
short_incr_bp(&short_head,written);
short_incr_tv(&tv_tail);
}while(tv_tail != tv_head);
wake_up_interrupt(&short_queue);
}
工作隊列
工作隊列會在將來的某個時間,在某個特殊的工作者進程上下文中調用一個函數。因為工作隊列函數運行在進程上下文中,因此可以進行休眠。但是我們不能從工作隊列向使用者空間複製資料(需要藉助一些進階的技巧),要知道,工作者進程無法訪問其他任何進程的地址空間。
如果我們的驅動程式具有特殊的延遲需求(或者可能在工作隊列函數中長時間休眠),則應建立我們自己的工作隊列。我們需要一個work_struct結構。
static struct work_struct short_wq;
下面這行出現在short_init中
INIT_WORK(&short_wq,(void (*)(void *))short_do_tasklet,NULL);
在使用工作隊列時, short構造了另一個中斷處理常式
irqreturn_t short_wq_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
do_gettimeofday((struct timeval *)tv_head);
short_incr_tv(&tv_head);
/*排序bh。不必關心多次調度的情況
schedule_work(&short_wq);
short_wq_count++;
return IRQ_HANDLED;
}
注意:工作隊列和等待隊列是兩碼事。
中斷共用:linux核心支援所有匯流排的中斷共用。
安裝共用的處理常式
就像普通非共用的中斷一樣,共用的中斷都是通過request_irq安裝的,不同在於:
1。請求中斷時,必須制定flags參數中的SA_SHIRQ位。
2.dev_id參數必須是唯一的。任何指向模組地址空間的指標都可以使用,但dev_id不能設定成NULL。
核心為為每個中斷維護一個共用處理常式的列表,這些處理常式的dev_id各不相同,就像是裝置的簽名。如果兩個裝置都註冊NULL作為他們的簽名,那麼卸載的時候引起混淆,當中斷到達時造成核心出現OOPS訊息。由於這個原因,在註冊共用中斷時如果傳遞了NULL的dev_id,現代的核心就會給出警告。當滿足下麵條件之一時,request_irq就會成功:
1.中斷訊號線空閑。
2.任何已經註冊了該中斷訊號線的處理常式也標示了IRQ是共用的。
無論何時,當兩個或則更多的驅動程式共用一根訊號線,而硬體又通過這根訊號線中斷處理器時,核心會調用每一個為這個中斷註冊的處理常式,並將它們自己的dev_id傳回去。因此,一個共用的處理常式必須能夠識別屬於自己的中斷,並且在自己的裝置沒有被中斷的時候迅速推出,返回IRQ_NONE。(也即只有真正產生中斷的裝置處理常式會被執行)
共用處理常式沒有探測函數可用,但使用的中斷訊號線是空閑時標準的探測機制才有效。
釋放處理常式是通過執行free_irq來實現的。這裡dev_id參數被用來從該中斷的共用處理常式列表中選擇正確的處理常式來釋放,這就是為什麼dev_id必須唯一的原因。
void free_irq(unsigned long flags,void *dev_id);
使用共用處理常式的驅動程式需要注意:不能使用enable_irq和disable_irq。如果使用了,共用中斷訊號線的其他裝置就無法工作了;即使在很短的時間內禁用中斷,也會因為這種延遲而為裝置和其他使用者帶來問題。驅動程式並不獨佔IRQ,所以它的行為必須比獨佔IRQ更具”社會化“。
運行處理常式:當核心受到中斷時,所有登入的處理常式都會被調用。一個共用中斷處理常式必須能夠將要處理的中斷和其他裝置產生的中斷區分開來。
當裝載short模組時,需要指定shared = 1選項。
irqreturn_t short_sh_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
int value,written;
struct timeval tv;
value = inb(short_base); /*因為並口沒有"interrupt-pending”位可以檢查,所以用ACK位。如果該位為高,則執行中斷常式 。如果該位為低,則表示該裝置沒有中斷,中斷常式返回IRQ_NONE,不進行後續操作。
if(!(value & 0x80))
return IRQ_NONE;
outb(0x70,short_base);//這步清中斷,使得常式可以繼續相應中斷。
/ *其餘如前邊*/
....
/proc介面和共用的中斷
在系統上安裝共用的中斷處理常式不會對/proc/stat造成影響,但是從/proc/interrupt中會表示出來
中斷驅動的I/O
如果與驅動程式管理的硬體之間的資料轉送因為某種原因被延遲的話,驅動程式作者就應該實現緩衝。資料緩衝區有助於將資料的傳送和接受與系統調用write和read分離開來,從而提高系統效能。
一個好的緩衝機制需採用中斷驅動的 I/O,一個輸入緩衝在中斷時被填充,並由讀取裝置的進程取走緩衝區的資料,一個輸出緩衝由寫裝置的進程填充,並在中斷時送出資料。
要正確進行中斷驅動的資料轉送,則要求硬體應該能夠按照下滿的語義來產生中斷:
1.對於輸入來說,當新的資料已經到達並且處理器準備好就收它時,裝置就中斷處理器,實際執行的動作取決於裝置使用的是I/O連接埠、記憶體映射、還是DMA。
2.對於輸出來說(針對CPU來說),當裝置準備好接受新資料或則對成功的資料轉送進行應答時,就要發出中斷。記憶體映射和具有DMA能力的裝置,通常通過產生中斷來通知系統他們對緩衝區的處理已結束。
在這章的例子中融合以前學過的大部分知識,自己也都分析了下,弄清楚以前一些不是很明白的地方,這裡列出代碼,備忘:
struct IO_irq_dev *IO_irq_devices;/* allocated in scull_init_module */
static atomic_t IO_irq_available = ATOMIC_INIT(1);
static spinlock_t IO_irq_lock = SPIN_LOCK_UNLOCKED;
static DECLARE_WAIT_QUEUE_HEAD(IO_irq_wait);
static unsigned long prevjiffies = 0;
static unsigned int prevkey = 0;
static struct IO_irq_key key1;
static struct IO_irq_key key2;
static struct IO_irq_key key3;
static struct IO_irq_key key4;
struct workqueue_struct *tekkamanwork;
static struct delayed_work irq_work_delay;
static struct work_struct irq_work;
static struct tasklet_struct keytask;
struct kfifo *tekkamanfifo;
static spinlock_t tekkamanlock= SPIN_LOCK_UNLOCKED;
static unsigned char *tekkamantmp;
static unsigned char *tekkamanbuf ;
這是開頭定義的全域變數;1、static DECLARE_WAIT_QUEUE_HEAD(IO_irq_wait);定義等待隊列wait_event_interruptible (IO_irq_wait, atomic_read (&IO_irq_available);2.struct workqueue_struct *tekkamanwork; 定義工作隊列tekkamanwork = create_workqueue("tekkamanwork"); 初始化
result =queue_delayed_work(tekkamanwork , &irq_work_delay, DELAY))!=1
這裡說明下這是實現後半部的工作隊列方式,安排工作隊列的方法有以下幾種:
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct delayed_work *work, unsigned long delay);
schedule_work(struct work_struct *work);
3.static struct delayed_work irq_work_delay;
static struct work_struct irq_work;
定義了兩種工作隊列,第一個為延時形式。
result = queue_delayed_work(...,&irq_work_delay,DELAY);
result = schedule_work(&irq_work)
兩種調度方式
4.注意這幾個結構的定義方式:
工作隊列: struct workqueue_struct
struct work_struct
struct delay_work
這三種形式的不同和運用方法。
具體可以參考代碼