?? (ldd) ch09-中斷處理(下)(轉載).txt
字號:
(LDD) Ch09-中斷處理(下)(轉載)
共享中斷
PC機一個眾所周知的“特性”就是不能將不同的設備掛到同一個中斷信號線上。
但是,Linux打破了這一點。甚至我的ISA硬件手冊―一本沒提到Linux的書―也說“最多
只有一個設備”可以掛到中斷信號線上,除非硬件設備設計的不好,電信號上并無這樣
的限制。問題在于軟件。
Linux軟件對共享的支持是為PCI設備做的,但也可用于ISA卡。不必說,非PC平
臺和總線也支持共享。
為了開發能處理共享中斷信號的驅動程序,必須考慮一些細節。下面會討論到,
使用共享中斷的驅動程序不能使用本章描述的一些特性。但最好盡可能對共享中斷提供
支持,因為這樣對最終用戶來說比較方便。
安裝共享的處理程序
和已經擁有的中斷一樣,要與它共享的中斷也是通過request_irq函數來安裝的
,但它們有兩處不同:
l 申請共享中斷時,必須在flags參數中指定SA_SHIRQ位
l dev_id參數必須是唯一的。任何指向模塊的地址空間的指針都可以,當然dev_
id一定不能設為NULL。
內核為每個中斷維護了一張共享處理函數的列表,并且這些處理函數的dev_id各不相同
,就象是驅動程序的簽名。如果兩個驅動程序都將NULL注冊為它們對同一個中斷的簽名
,那么在卸載時會混淆起來,當中斷到達時內核就會出現oops消息。我第一次測試共享
,那么在卸載時會混淆起來,當中斷到達時內核就會出現oops消息。我第一次測試共享
中斷時就發生過這種事情(當時我只是想著“一定要將SA_SHIRQ位加到這兩個驅動程序上
”)。
滿足這些條件之后,如果中斷信號線空閑或者下面兩個條件同時得到滿足,那么request
_irq就會成功:
l 前面注冊的處理函數的flags參數指定了SA_SHIRQ位。
l 新的和老的處理函數同為快速處理函數,或者同為慢速處理函數。
需要滿足這些要求的原因很明顯:快速和慢速處理函數處于不同的環境,不能互相混淆
。類似的,你也不能與已經安裝為不共享的中斷處理函數共享相同的中斷。但關于快速
和慢速處理函數的限制對最近的2.1版的內核來說是不必要的,因為兩種處理函數已經合
并了。
并了。
當兩個或兩個以上的驅動程序共享同一根中斷信號線,而硬件又通過這根信號線中斷了
處理器時,內核激活這個中斷注冊的所有處理函數,并將自己的dev_id傳遞給它們。因
此,共享處理函數必須能夠識別出它對應于哪個中斷。
如果你在申請中斷信號之前需要探測你的設備的話,內核無法提供幫助。沒有共享中斷
的探測函數。僅當使用的中斷信號線空閑時,標準的探測機制才能奏效;但如果被其它
的具有共享特性的驅動程序占用的話,那么即使你的程序已經可以正常工作了,探測也
會失敗。
那么,唯一的可以用來探測共享中斷信號的技術就是DIY探測。驅動程序必須為所有可能
的中斷信號線申請共享處理函數,然后觀察中斷在何處報告。這里和前面介紹的DIY的探
測之間的差別在于,此時探測處理函數必須檢查是否真的發生了中斷,因為為響應共享
中斷信號線上的其它設備的中斷它可能已經被調用過了。
釋放處理函數同樣是通過執行release_irq來實現的。這里dev_id參數用于從該中斷的共
享處理函數列表中正確地選出要釋放的那個處理函數。這就是dev_id指針必須唯一的原
因。
使用共享處理程序的驅動程序時還要小心:不能使用enable_irq和disable_irq。如果它
使用了這兩個函數,共享中斷信號線的其它設備就無法正常工作了。一般地,程序員必
須牢記他的驅動程序并不獨占這個中斷,因此它的行為必須比獨占中斷信號線時更“社
會化”些。
運行處理函數
如上所述,當內核接收到中斷時,所有注冊過的處理函數都會被激活。共享中斷
處理程序必須能將需要處理的中斷和其它設備產生的中斷區分開來。
裝載short時指定shared=1將安裝下面的處理程序而不是缺省的處理程序:
void short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
int value;
struct timeval tv;
/* 如果不是short,立即返回 */
value = inb(short_base);
if (!(value & 0x80)) return;
/* 清除中斷位 */
outb(value & 0x7F, short_base);
/* 其余不變 */
do_gettimeofday(&tv);
short_head += sprintf((char *)short_head,"%08u.%06u\n",
(int)(tv.tv_sec % 100000000),
(int)(tv.tv_usec));
if (short_head == short_buffer + PAGE_SIZE)
short_head = short_buffer; /* 繞回來 */
wake_up_interruptible(&short_queue); /* 喚醒所有讀進程 */
}
解釋如下。因為并口沒有 “待處理的中斷”位可供檢查,為此處理函數使用了A
CK位。如果該位為高,報告的中斷就是送給short的,并且處理函數將清除該位。
處理函數是通過將并口的數據端口的高位清零來清除中斷位的-short假定并口
的9和10引腳是連在一起的。如果與short共享同一中斷的設備產生了一個中斷,short會
知道它的信號線并未激活,因此什么也不會做。
顯然,真正的驅動程序做的工作會更多些;特別的,它要使用dev_id參數來得到
自己的硬件結構。
特性完全的驅動程序可能會將工作劃分為上半部和下半部,但這很容易添加,對
實現共享的代碼并無太大影響。
/proc接口
/proc接口
系統中安裝的共享中斷處理程序不會影響/proc/stat文件(該文件甚至并不知道
處理程序的存在)。但是,/proc/interrupts文件會有些變化。
為同一個中斷號安裝的處理程序會出現在/proc/interrupts文件的同一行上。下
面的快照取自我的計算機,是在我將short和我的幀捕捉卡裝載為共享中斷處理程序之后
:
0: 1153617 timer
1: 13637 keyboard
2: 0 cascade
3: 14697 + serial
5: 190762 NE2000
7: 2094 + short, + cx100
7: 2094 + short, + cx100
13: 0 math error
14: 47995 + ide0
15: 12207 + ide1
這里共享中斷信號是IRQ7號中斷;激活的處理程序列在同一行,用逗號隔開。顯
然內核是無法區分short中斷和捕捉卡(cx100)中斷的。
中斷驅動的I/O
如果和處理的硬件間的數據傳輸因為某些原因會被延遲的話,那么驅動程序的寫
函數必須實現緩沖。數據緩沖可以將數據的發送和接收與write及read系統調用分離開來
,提高系統的整體性能。
一個好的緩沖機制是“中斷驅動的I/O”,它在中斷時間內填充一個輸入緩沖區
并由讀設備的進程將其取空;或由寫設備的進程來填充一個輸入緩沖區并在中斷時間內
將其取空。
將其取空。
中斷驅動的數據傳輸要正確進行,要求硬件必須安下面的語義產生中斷:
l 對輸入而言,當新數據到達,系統處理器準備讀取它時,設備就中斷處理器。
實際執行的動作取決于設備是否使用了I/O端口,內存映射或者DMA。
l 對輸出而言,當設備準備好接收新數據或對成功的數據傳輸進行確認時都會發
出中斷。內存映射和能進行DMA的設備通常是通過產生中斷來通知系統它們的對緩沖區的
處理已經結束。
read或write調用時間和實際的數據到達時間之間的關系是在第5章“字符設備驅動程序
的擴展操作”的“阻塞型和非阻塞型操作”一節中介紹的。中斷驅動的I/O引入了共享數
據項的并發進程間的同步問題,因此所有這些問題都與競爭條件有關。
競爭條件
當變量或其它數據項在中斷時間內被修改時,由于競爭條件的存在,驅動程序的
操作就有可能造成它們的不一致。當操作不是原子地執行時,競爭條件就會發生,但在
執行時仍假定數據會保持一致性。因此“競爭”是在非原子性的操作和其它可能被同時
執行的代碼之間發生的。典型的,競爭條件會在三種情況下發生:在函數內隱式地調用s
chedule,阻塞操作和由中斷代碼或系統調用訪問共享數據。最后一種情況發生得最頻繁
,因此我們在這一章處理競爭條件。
處理競爭條件是編程時最麻煩的一部分,因為相關的臭蟲滿足的條件很苛刻,不
容易再現,很難分辨出中斷代碼和驅動程序的方法間是否存在競爭條件。程序員必須極
為小心地避免數據或元數據的沖突。
一般用于避免競爭條件的技術是在驅動程序的方法中實現的,這些方法必須保證
當數據項受到沒有預料到的修改時得到正確的處理。但另一方面,中斷處理函數并不需
要特別的處理,因為相對設備的方法,它的操作是原子性的。
可以使用不同的技術來防止數據沖突,我下面將介紹最常用的一些技術。我不給
出完整的代碼,因為各種情況下最好的實現代碼取決于被驅動的設備的操作模式以及程
序員的不同愛好。
最常用的防止數據被并發地訪問的方法有:
l 使用循環緩沖區和避免使用共享變量。
l 在訪問共享變量的方法里暫時禁止中斷。
l 使用鎖變量,它是原子地增加和減少的。
當訪問可能在中斷時間內被修改了的變量時,不論你選用的是哪種方法,都必須決定如
何進行處理。這樣的變量可以聲明為volatile的,來阻止編譯器對該值的訪問進行優化(
例如,它阻止編譯器在整個函數的運行期內將這個值放進一個寄存器中)。但是,使用vo
latile變量后,編譯器產生的代碼會很糟糕,因此你可能會轉向使用cli和sti。Linux實
現這些函數時使用了gcc的制導來保證在中斷標志位被修改之前處理器處于安全狀態。
使用循環緩沖區
使用循環緩沖區是處理并發訪問問題的一種有效方法:當然最好的處理方法還是
不允許并發訪問。
循環緩沖區使用了一種被稱為“生產者和消費者”的算法-一個進程將數據放進
緩沖區中,另一個則將它取出來。如果只有一個生產者和一個消費者,那就避免了并發
訪問。在short模塊中有兩個生產者和消費者的例子。其中一個情形是,讀進程等待消費
在中斷時間里生產的數據;而另一個情形是,下半部消費上半部生產的數據。
共有兩個指針用于對循環緩沖區進行尋址:head和tail。head是數據的寫入位置
,由數據的生產者更新。數據從tail處讀出,它是由消費者更新的。正如我上面提到的
,如果數據是在中斷時間內寫的,那么多次訪問head多次時就必須小心。你必須將head
,如果數據是在中斷時間內寫的,那么多次訪問head多次時就必須小心。你必須將head
定義成volatile的或者在進入競爭條件前將中斷禁止。
循環緩沖區在填滿前工作的很好。如果緩沖區滿了,就可能出問題,但你可以有
多種不同的解決方法可供選擇。short中的實現就是簡單地丟棄數據;并不檢查溢出,如
果head超過了tail,那么整個緩沖區中的數據都丟失了。其它的實現還有丟棄最后那個
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -