400 likes | 635 Views
中斷處置. Interrupts. 當周邊裝置事件發生時,為了要通知處理器所發出的一種特殊電子信號,稱為「中斷」 (interrupts). 註冊 ( 告知 ). Device. Driver. Kernel. CPU. 處理程序 (handler). 驅動程式註冊中斷示意圖. PART1. 安裝 ISR. ISR(interrupt service routine). 稱為中斷服務常式或中斷處理程式 (interrupt handler) 當裝置中斷被觸發時, ISR 便會執行,而工作就是回應該中斷的請求 (request) 。
E N D
Interrupts • 當周邊裝置事件發生時,為了要通知處理器所發出的一種特殊電子信號,稱為「中斷」(interrupts) 註冊(告知) Device Driver Kernel CPU 處理程序(handler) 驅動程式註冊中斷示意圖
ISR(interrupt service routine) • 稱為中斷服務常式或中斷處理程式(interrupt handler) • 當裝置中斷被觸發時,ISR便會執行,而工作就是回應該中斷的請求(request)。 • 一般而言,裝置產生中斷時,都是與資料讀寫有關的請求。 • ISR便根據中斷的特性,判斷此中斷式要求驅動程式將資料寫入裝置,或是由裝置讀取資料。 Ref:http://www.jollen.org/blog/2008/03/interrupt_handling_semaphore.html
IRQ(interrupt request) • 又稱中斷線路「interrupt channel」 • 針對每個周邊裝置配置其對應的IRQ • 借由IQR來判別周邊裝置所發出的訊號該由哪個ISR進行處理 中斷 信號 Device Kernel CPU Interrupt vector table Interrupt channel IRQ與ISR對應表 驅動程式查找IVT示意圖
註冊、釋放ISR intrequest_irq(unsigned intirq, irqreturn_t (*handler)(int,void *,structpt_regs *), unsigned long flags, const char *dev_name, void *dev_id); • 回傳值 • 0– 核心同意配置irq通道給裝置 • 負值 – 錯誤碼(例如常見的-EBUSY,表示要求的IRQ已被其他驅動程式佔用) void free_irq(unsigned intirq, void *dev_id)’ • 註:宣告於linux/interrupt.h 補充
request_irq • request_irq() 引數 unsigned intirq 索取的中斷通道之編號 void (*handler)(int, void *,structpt_regs*) 要被安裝ISR之函式指標 unsigned long flags 中斷管理選項之位元遮罩 const char *dev_name 代表ISR之字串(/proc/interrupts) void *dev_id 「共享IRQ」時,釋放IRQ時的裝置辨認之用 「獨佔IRQ」時,可設定為null
request_irq • flag 各個位元 SA_INTERRUPT 設定此位元,表是安裝「快速型」ISR,表示在中斷失效其間,執行完畢所有ISR。 SA_SHIRQ 此位元表示IRQ是否可不同裝置共享 SA_SAMPLE_RANDOM可預期信號(週期)–不值得設定此位元 不可預期訊號(隨機)– 設定此位元,有助於系統 "添亂” 補充
安裝ISR的時機 • 模組初始話時安裝好ISR • 發生模組佔用IRQ,即使硬體閒置,IRQ也不能交至其他裝置 • 裝置首次被開啟時安裝ISR • 也就是在硬體被首次開啟之前,通知硬體可以發出中斷之前,呼叫request_irq
/proc/interrupts • 每個IRQ,核心內部各維護一個計數器。每當中斷抵達CPU,對應的IRQ計數器就會被累增一次。 已安裝IRQ 的中斷 CPU處理的中斷次數 IRQ中斷信號觸發方式 驅動程式為ISR註冊的名稱(dev_name) LinuxKernel集中中斷至CPU0,以提升快取命中率。
/proc/stat • 紀錄關於系統活動的幾項低階系統資訊
/proc/stat • 從系統啟動開始累計到當前時刻,各別的時間 • user –用戶態的CPU時間(單位:jiffies) ,不包含 nice值為負進程 • nice - nice值為負的進程所佔用的CPU時間(單位:jiffies) • system -核心時間(單位:jiffies) • idle – 除硬碟I/O等待時間以外其它等待時間(單位:jiffies) • iowait - 硬盤IO等待時間(單位:jiffies) • irq –硬中斷時間(單位:jiffies) • softirq - 軟中斷時間(單位:jiffies) • CPU 時間=user+nice+system+idle+iowait+irq+softirq user nice system idle iowait irq softirq Ref:http://140.92.88.48/tlog/linux/entry/proc_stat_explained
/proc/stat • 「intr」此行的中斷信息,為系統啟動以來,發生所有的中斷次數 total … irq1 irq2 context switch的次數 從系統啟動到現在為止的時間 創建的任務的個數目 當前運行隊列的任務的數目 當前被阻塞的任務的數目
自動探測IRQ通道 • 簡單說,就是「如何自動的得知裝置IRQ號碼??」 • 傳統的方式 • 借由I/O基底位址,求得對應IRQ 例如:x86 第一PC序列埠慣例為0x3F8與IRQ4的組合… • 現在硬體裝置幾乎都支援的方法 • 驅動程式從裝置的I/O port或PIC(Programmable Interrupt Controller)組態空間讀出一個狀態位元組,便可得知其裝置IRQ。 • 「自動探測IRQ編碼為驅動程式可用性的基本要求」
核心輔助探測法 • 核心提供協助驅動程式探測IRQ號碼的方法 • 僅適用獨占式中斷(非共享) unsigned long probe_irq_on(void) 回傳一個位元遮罩,來判斷哪些中斷尚未被使用,驅動程式保留並將其 傳至probe_irq_off(),且試圖啟動中斷。 intprobe_irq_off(unsigned long) 關閉裝置中斷,傳入位元遮罩 回傳值 0 – 表示沒有中斷發生 負值 – 發生一次以上中斷(同時觸發多個裝置)
自助探測法 • 根據對目標裝置的認知,猜測其可能的IRQ,並逐一起動四個可能的IRQ值,然後檢查是否發生中斷。 • 無法得知可能的IRQ,則要測試可用的IRQ,必須從IRQ0~IRQNR_IRQS-1,逐一測試。 • NR_IRQS定義於<asm/irq.h>
ISR的「快」 「慢」之分 • 能否於下一個中斷來臨之前完成工作,以區隔「快速型」與「慢速型」的ISR • 目前核心版本只留下「快速型ISR」,並以SA_INTERRUPT旗標註冊ISR。 • 除非像計時器中斷,允許ISR不受其他中斷干擾,否則不建議使用SA_INTERRUPT旗標註冊ISR。
ISR • ISR工作在於接待、回饋發出中斷的裝置 • ISR執行時間稱為中斷時期(interrupt time) • ISR函式遵守與kernel timer一樣的限制: • 不能與user-space傳輸資料(與程序無關) • 不能做可能導致休眠的動作(例如呼叫wait_event()) • 只能以GFP_ATOMIC來配置記憶體 • 不能鎖定權狀(semaphore) • 不能呼叫schedule() • 當硬體發生中斷,通常表示user-space程式正在期待事件發生。 補充 ISR 影像擷取器 no Read() buffer
ISR宣告 • 典型的ISR宣告方式 static irqreturn_tintr_handler(intirq,void *dev_id,structpt_regs *regs) • irq irq代表「IRQ編碼」,可記錄在日誌檔裡的資訊,以便除錯之用。 • dev_id 驅動程式可借由dev_id傳遞需要在中斷期間針對個別裝置處理的資料結構;在不用撰寫額外ISR程式就可以用同一個ISR處理同一類裝置 • regs 儲存CPU進入中斷模式前的狀態(每個暫存器的值)
ISR 回傳值 • ISR回傳值代表中斷事件是否已經處置妥當 • 回傳值 IRQ_HANDLED-ISR發現它的裝置發出需要注意的中斷 IRQ_NONE – 沒有發現中斷
抵制一次中斷 • 驅動程式需暫時抵制特定中斷線路的信號傳達 • 核心提供三個輔助函式(/linux/interrupts.h) void disable_irq(intirq); void disable_irq_nonsync(intirq); void enable_irq(intirq); • 借由改變PIC(Programmable Interrupt Controller)上暫存器的特定位元,來達到放型或阻擋特定IRQ的效果。 中斷 信息 Device PIC CPU
抵制中斷的風險 • 呼叫disable_irq()的函式持有ISR所需資源的迴旋鎖,將造成系統死結。 • 呼叫disable_irq_nosync()會改變PIC暫存器的位元遮罩後返回,不管受影響的IRQ當時是否有ISR正在執行,因此需自行承擔互相競狀況的風險。
壓制所有中斷 • 2.6版核心提供兩個函式,壓制CPU處理任何中斷訊息 void local_irq_save(unsigned long flags); void local_irq_disable(void); • local_irq_save()儲存當時中斷狀態存入flags,然後關閉當時 CPU 的中斷信號受理能力 • local_irq_disable()直接關閉當時CPU的中斷受理能力,而不保存狀態
恢復CPU處理中斷能力 void local_irq_restore(unsigned long flags); void local_irq_enable(void); • local_irq_restore - 會恢復先前存入flags的旗標回復給 CPU • local_irq_enable – 無條件回復中斷受理狀態
ISR的上下半段 • 為解決ISR的長時間工作所設計的解決之道 • 將ISR分成兩段 • 前半段(top half) - 指得是呼叫request_irq()時所指定的interrupt handler函數 • 儲存裝置相關資料 • 將 bottom half 排程後執行結束 • 後半段(bottom half) –由排程器另找安全時間才開始執行(真正負責回應中斷的工作)。 • 喚醒正在等待資料的程序、啟動另一次I/O作業…等較為多樣性的工作 http://www.jollen.org/blog/2008/03/interrupt_handling_bottom_half.html
安裝共享式的IRS • 使用request_irq()安裝 • Flags引數設定SA_SHIRQ位元 • dev_id需為系統上獨一無二的數值,且不能設為NULL • request_irq()返回成功的情況(取得IRQ) • 指定的IRQ通道尚未註冊給任何IRS • 指定的IRQ通道的所有ISR,皆註冊為共享模式
ISR的執行 • 核心收到中斷後,觸發每個註冊該IRQ的ISR,而每個ISR皆需做兩個動作 • 判斷是否需要處理該次中斷 • 該次中斷是否來至其他裝置 • ldd3->short.c->short_sh_interrupt return IRQ_NONE Device (name2) IRQ6 中斷信號 Kernel
/proc中的共享式中斷 • /proc中的stat與interrupts的差別 • /proc/stat對ISR資訊一無所知 • /proc/interrupts則知道共用一個IRQ的所有ISRs 已安裝IRQ 的中斷 CPU處理的中斷次數 IRQ中斷信號觸發方式 驅動程式為ISR註冊的名稱(dev_name)
中斷驅動式I/O • CPU與裝置之間資料傳輸可能因任何原因而延遲,因此驅動程式需提供緩衝區。 • 中斷驅動式I/O(interrupt-driven I/O) – 緩衝機制 ISR input process output Device Buffer
中斷驅動式I/O • 中斷驅動資料傳輸成功,要依賴硬體依條件來產生中斷信號: • 資料輸入(周邊→CPU) • 裝置必須在資料到達且可供系統處理器擷取的情況下,對CPU發出中斷信號 • 資料輸出(CPU→周邊) • 裝置必須在準備好接收新資料或回報已成功傳輸出資料時,對CPU發出中斷信號 • ldd3->shortprint.c->shortp_write() • ldd3->shortprint.c->shortp_start_output() • ldd3->shortprint.c->shortp_start_output()
2.6.xx…request_irq Back
request_irq flag • 從Linux Kernel 2.6.19開始有些中斷處理介面改掉 • SA_INTERRUPT改成IRQF_DISABLE, • 其他的flag前面的"SA_”字樣改成"IRQF”,這樣才能正確對應到header file內的定義。 • 譬如 :SA_TIMER -> IRQF_TIMER Back Ref:http://www.wretch.cc/blog/kerker2001
ISR補充 • Interrupt handler 執行於 interrupt mode,並無 process context資訊,因此,在 interrupt mode 下執行的執行碼需注意以下 3 點: • 由於沒有 process context 的關係,因此無法存取 user space。 • 無法存取 current 巨集。current 巨集是一個指向自己的 kernel symbol。 • 不能呼叫 scheduler 做排程,也不能做 sleeping waiting。由於 down/up 的 semaphore API 是 sleeping waiting 的版本,因此在 interrupt mode 必須改用 spinlock,spinlock是以 busy waiting 的方式做 wait operation。 Ref:http://www.jollen.org/blog/2008/03/interrupt_handling_1.html
interrupt handler 實作原則 • 在 interrupt handler 裡,叫醒真正負責此中斷的 task 後立即結束執行。 • Interrupt handler 應儘量執行最少的程式碼。 • Interrupt handler 若執行過久,會造成中斷的關閉時間過長,因此可能會遺失緊接著產生的中斷請求。 • 若 interrupt handler 裡有過長的計算動作或執行過久的程式碼,則應使用 tasklet或 task queue 將該段程式碼做排程,留待其它時間再執行。如此便可避免interrupt handler執行時間過久。 Back Ref: http://www.jollen.org/blog/2008/03/interrupt_handling_semaphore.html