中斷I/O
? ? ? ? CPU與外設(shè)之間的一種通信方式志衣。? 與CPU內(nèi)部的異常類似屯援。但區(qū)別就在于異常的發(fā)生是與處理器的時鐘信號的同步的,所以異常有時也被稱為同步中斷念脯。而中斷是異步的狞洋,可能發(fā)生在任何時候(如果中斷被允許的話)。
中斷處理程序
? ? ? ? 中斷處理程序是內(nèi)核用來響應(yīng)相應(yīng)中斷的程序绿店,通常來說每個設(shè)備都擁有對應(yīng)的中斷處理程序吉懊,來進行相應(yīng)的處理。舉例來說假勿,如果用戶按下鍵盤借嗽,則鍵盤會發(fā)送信號給中斷控制器,接著中斷控制器又通過CPU的中斷引腳通知CPU转培,在執(zhí)行完當(dāng)前指令后恶导,CPU立即檢測中斷引腳(似乎在每條指令執(zhí)行后,都會進行一次檢測)堡距,發(fā)現(xiàn)有中斷發(fā)生甲锡。CPU經(jīng)過一系列的檢查后兆蕉,如果當(dāng)前允許中斷,則確定中斷是由鍵盤產(chǎn)生的缤沦,然后啟動鍵盤的中斷處理程序虎韵,讀取用戶的輸入。
? ? ? ? 中斷程序必須執(zhí)行得盡可能的快缸废,這對于系統(tǒng)性能來說十分重要包蓝。但是通常來說,中斷程序的工作量十分浩大企量,比如說網(wǎng)絡(luò)設(shè)備的中斷程序:首先需要向設(shè)備確認(rèn)已了解到中斷的發(fā)生(設(shè)備不再發(fā)出中斷信號)测萎,然后將設(shè)備中的數(shù)據(jù)復(fù)制到內(nèi)存中,進行甄別届巩,把數(shù)據(jù)發(fā)送到其對應(yīng)的協(xié)議的椆枨疲或者是應(yīng)用程序中。顯然恕汇,在今天巨大的網(wǎng)絡(luò)吞吐量下腕唧,這是個耗時的工作。
? ? ? ? 于是就產(chǎn)生了矛盾瘾英,所以我們把中斷服務(wù)程序分為上下兩部分枣接。上半部分執(zhí)行耗時極少的工作,比如確認(rèn)已檢測到中斷或者將相應(yīng)設(shè)備重置缺谴。然后將那些繁重的但惶,卻又不是很迫切而可以延遲的工作,交付給中斷程序的下半部分湿蛔。在頂部執(zhí)行時膀曾,系統(tǒng)通常會禁止中斷的投遞,也就是說頂部的工作不會被其他中斷打斷(注意:此時在中斷上下文)阳啥,因為這部分工作相當(dāng)重要妓肢。而底部的工作,會在未來某個恰當(dāng)?shù)臅r機執(zhí)行苫纤,并且允許其他中斷的發(fā)生碉钠。
登記中斷處理程序
? ? ? ? 我們可以調(diào)用request_irq()函數(shù)(Kernel/irq.c)來向系統(tǒng)注冊一個中斷服務(wù)程序。
? ? ? ? 接下來解析每個參數(shù)的含義卷拘。irq指明要申請的的中斷號喊废,對某些設(shè)備來說,其對應(yīng)的中斷號是硬編碼的——也就是說不可更改栗弟,比如系統(tǒng)定時器和鍵盤污筷。至于其他的設(shè)備所使用的的中斷號,則是可以動態(tài)分配的。handler顧名思義就是對應(yīng)的中斷處理程序瓣蛀。irqflags可以為0陆蟆,或者從以下的值中選擇(可以使一個或者多個):
? ? ? ? SA_INTERRUPT:這種取值的中斷處理程序是快中斷處理程序。Linux系統(tǒng)通常將中斷程序相對地由快慢來劃分惋增。發(fā)展到了今天叠殷,個中的區(qū)? ? 別就是,當(dāng)快中斷程序執(zhí)行時诈皿,將當(dāng)前處理器的中斷禁用林束。這樣做會使處理程序更快地完成執(zhí)行,不會被其他中斷打擾稽亏。默認(rèn)情況下——也就是不啟用該標(biāo)志的情況下壶冒,除了與當(dāng)前處理程序共享中斷線的中斷外,所有中斷都是可用的截歉∨痔冢回憶《軟硬件接口》中的中斷屏蔽字位,可以建立起對應(yīng)關(guān)系——對每個中斷來說瘪松,確實存在私有的中斷屏蔽字胸嘁,當(dāng)起處理程序執(zhí)行時,禁用這些中斷凉逛。
? ? ? ? SA_SAMPLE_RANDOM:暫時不知道這是干啥的。
? ? ? ? SA_SHIRQ:設(shè)置了此為的處理程序表示其irq參數(shù)所指定的中斷線可以被其他中斷處理程序分享群井。如果不設(shè)置此位状飞,那么中斷線是被單一設(shè)備獨占的。早期的計算機系統(tǒng)中設(shè)備并不多书斜,因此設(shè)備獨占中斷線號的方法是可行的诬辈,當(dāng)往計算機里增加的設(shè)備越來越多時,獨占的方式明顯是不切實際的荐吉。因此產(chǎn)生了共享中斷線號的思想焙糟。那么當(dāng)一條共享中斷線中發(fā)來中斷信號,我們該如何辨別到底是哪個設(shè)備發(fā)生了中斷样屠?其實就是將該共享中斷線上的所以處理程序調(diào)用穿撮,不過在每個中斷處理程序的內(nèi)部首先檢查(參數(shù)信息以及設(shè)備硬件的支持)是不是這個中斷處理程序?qū)?yīng)的設(shè)備產(chǎn)生的中斷,通常是通過讀取該硬件設(shè)備提供的中斷標(biāo)志位進行判斷痪欲。如果不是悦穿,立即返回,如果是业踢,則處理完成栗柒,如果鏈表中沒有一個是,則說明出現(xiàn)錯誤知举。
? ? ? ? 下面回到對參數(shù)的介紹瞬沦。顧名思義devname就是設(shè)備的名字太伊,ASCII的文本。這個參數(shù)會被/proc/irq和/proc/interrupts所使用逛钻。第五個參數(shù)dev_id是用于共享中斷線的僚焦。如果不提供這樣一個唯一的設(shè)備ID,那么我們就沒有辦法從共享中斷線中移除指定的中斷處理程序绣的。想象一下叠赐,每個人都長得差不多,那么辨別我們的方法就是身份證了屡江。通常來講芭概,這個參數(shù)會設(shè)置為硬件驅(qū)動的設(shè)備結(jié)構(gòu)體——獨一無二的,并且可能在處理程序中會被用到惩嘉。
? ? ? 注意:dev_id不是用于識別究竟是哪個設(shè)備產(chǎn)生中斷的0罩蕖!文黎!
釋放中斷處理程序
? ? ? ? 通過調(diào)用free_irq()來實現(xiàn)惹苗。
? ? ? ? 可以看到dev_id發(fā)揮了重要作用,它被用于判定處理程序是否為我們想要移除和釋放的處理程序耸峭。注意到第8行桩蓉,用了一個沒有釋放內(nèi)存但卻很有趣的刪除手段。
編寫中斷處理程序
? ? ? ? 中斷處理程序定義是這樣的:
? ? ? ? ? ? ? ? static irqreturn_t? intr_handler(int irq, void *dev_id, struct pt_regs*regs)
? ? ? ? 在這里劳闹,傳遞dev_id的原因是用于區(qū)分共享中斷處理程序的不同設(shè)備院究。在早期每個設(shè)備獨占中斷線號,用Irq區(qū)分即可本涕。但今非昔比业汰。還有比較重要的是返回值類型irqreturn_t——實際上就是int。其值只有兩種菩颖。我們用宏IRQ_RETVAL(val)來檢測样漆,val非0就返回IRQ_HANDLED,否則就返回IRQ_NONE晦闰。IRQ_HANDLED表示設(shè)備確實有中斷請求放祟,我們調(diào)用了正確的處理程序。如果是IRQ_NONE則表示未檢測到設(shè)備的中斷請求呻右。每個中斷處理程序的實現(xiàn)取決于設(shè)備舞竿,但通常來說都要向設(shè)備確認(rèn):我已了解到中斷了。
? ? ? ? 再次強調(diào):當(dāng)某中斷服務(wù)程序執(zhí)行時窿冯,其共享中斷線號上的所有中斷被屏蔽骗奖。并且相同的處理程序不可能在相同處理器并行地執(zhí)行。
中斷上下文
? ? ? ? ? 當(dāng)處理程序運行時,無論是上下部分执桌,它都是處于中斷上下文的鄙皇。
? ? ? ? ? 在Linux2.6里,每個進程的內(nèi)核棧大小都初始地被分配為一個頁面大小仰挣,減輕內(nèi)存壓力伴逸。中斷處理進程也被單獨地分配了一個頁大小的空間用作棧——此前膘壶,中斷進程將與被中斷的進程共享其內(nèi)核棧错蝴。如果自己新建中斷服務(wù)程序也無須擔(dān)心可用內(nèi)存的大小——因為內(nèi)核總是分配絕對的,最小的椡前牛空間顷锰。
? ? ? ? 個人猜想:中斷處理程序是預(yù)先駐留在內(nèi)存給定位置的,比如8086機器亡问,是存放在0x0000H開始的一段內(nèi)存空間官紫。當(dāng)中斷發(fā)生時,便根據(jù)中斷號啟動相應(yīng)的服務(wù)程序州藕。在此過程中嗜暴,內(nèi)核并沒有給中斷程序創(chuàng)建進程描述符立轧,所以其是不可調(diào)度的,這也呼應(yīng)了中斷程序上半部分必須執(zhí)行地盡可能快的原則炼邀。并且在整個中斷執(zhí)行過程中虫啥,current的值依舊指向原進程的進程描述符烈钞。
? ? ? ? 關(guān)于中斷上下文的詳細(xì)知識蝙搔,我閱讀更多源碼后再補充锯蛀。
中斷控制
? ? ? ? 以下是幾個關(guān)于中斷控制的接口函數(shù)。
? ? ? ? 啟用中斷: local_irq_enable()
? ? ? ? 禁用中斷: local_irq_disable()
? ? ? ? 保存中斷狀態(tài):local_irq_save((unsigned long) flags)
? ? ? ? 恢復(fù)中斷狀態(tài):local_irq_restore((unsigned long) flags)
? ? ? ? 禁止指定中斷線:void disable_irq(unsigned int irq)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? void disable_irq_nosync(unsigned int irq)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? void sychronize_irq(unsigned int irq)
? ? ? ? 以上函數(shù)禁止指定中斷線向所有處理器的投遞馅精,但個中關(guān)系還不明朗。
? ? ? ? 啟用指定中斷線:void enable_irq(unsigned int irq)
? ? ? ? 檢查中斷狀態(tài):#define in_interrupt() (irq_count()) 如果內(nèi)核處于中斷上下文返回非0值粱檀,包括正在執(zhí)行的中斷處理程序或是下半部分程序洲敢。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #define in_irq() (hardirq_count()) 當(dāng)內(nèi)核正在執(zhí)行中斷處理程序(特指頂部),則返回非0值茄蚯。