處理器與外圍裝置進行通訊有兩種方式
1. 輪詢(效率低下)
2. 中斷
中斷原理
當我們在敲擊鍵盤的時候,鍵盤控制器會發送一箇中斷給處理器,告訴OS有中斷產生,處理器停下當前的工作,轉而由內 核呼叫中斷服務程式。(中斷控制器傳送中斷給處理器的時候,處理器根據中斷號查詢中斷向量表,找到中斷服務程式的入口地址,才能去執行中斷服務程式)。
中斷 (Interrupt) 介紹
中斷是現今對稱多處理中重要的機制,一般 CPU 同一時間只能處理一個指令,當這個 CPU 被某個行程占用時,只有透過中斷來讓CPU處理其他行程的指令。一般作業系統的中斷分為硬體和軟體中斷:
硬體中斷:
1. 在硬體週邊設備需要 CPU 時(例如,要跟 CPU 要資料,或是有資料要給 CPU),會對 CPU 發出中斷要求
2. CPU運行某個指令,發生錯誤,應而被迫中斷。或者由I/O發起硬體訊號,而將CPU中斷。
軟體中斷:
CPU執行組合語言中的中斷指令(像是 INT 3)
例外(exceptions)分為三種
程式錯誤例外
程式錯誤例外是在處理器偵測到程式有不合法的行為,或是作業系統發生某些錯誤時,由處理器自動發出的例外。這種例外又可分為三種:faults、traps、和 aborts。
軟體產生例外
由 INTO、INT3、和 BOUND 指令產生的。例如,INT3 命令會發出一個「程式中斷點」的例外。用 INT n 指令也可以「模擬」例外,但是,例外發生時,處理器會把錯誤碼推入堆疊中,而 INT n 指令則不會
所以,如果直接使用 INT n 指令呼叫例外處理程序,則該程序會從堆疊中取出錯誤碼,而在返回時,會回到錯誤的位址。
機器錯誤例外
在 Pentium 以後的處理器,可以檢查其內部是否有錯誤。在處理器有錯誤時,會產生機器錯誤例外。
中斷向量
中斷和例外是利用一個數字來區分不同的中斷或例外,這個數字稱為「中斷向量」(interrupt vector)。中斷向量是一個由 00H 到 FFH 的數字。其中,00H 到 1FH 的中斷向量是保留作系統用途的,不可任意使用;而其它的中斷向量則可以自由使用。保留的中斷向量如下表所示:
向量編號 | 助憶碼 | 說明 | 型態 | 錯誤碼 | 來源 |
00H | #DE | 除法錯誤 | Fault | 無 | DIV 和 IDIV 指令。 |
01H | #DB | 除錯 | Fault/Trap | 無 | 任何對程式或資料的參考、或是 INT 1 指令。 |
02H | - | NMI 中斷 | Interrupt | 無 | 不可遮罩的外部中斷。 |
03H | #BP | 中斷點 | Trap | 無 | INT3 指令。 |
04H | #OF | 溢出 | Trap | 無 | INTO 指令。 |
05H | #BR | 超出 BOUND 範圍 | Fault | 無 | BOUND 指令。 |
06H | #UD | 非法的指令 | Fault | 無 | UD2 或未定義的指令碼。 |
07H | #NM | 沒有 FPU | Fault | 無 | 浮點運算指令或 WAIT/FWAIT 指令。 |
08H | #DF | 雙重錯誤 | Fault | 有 | 任何會產生例外的指令。 |
09H | - | 保留 | Fault | 無 | 386 以後的處理器不產生此例外。 |
0AH | #TS | 不合法的 TSS | Fault | 有 | 工作切換、或存取 TSS。 |
0BH | #NP | 分段不存在 | Fault | 有 | 載入或存取分段。 |
0CH | #SS | 堆疊分段錯誤 | Fault | 有 | 載入 SS 載存器或存取堆疊。 |
0DH | #GP | 一般性錯誤 | Fault | 有 | 存取記憶體或進行其它保護檢查。 |
0EH | #PF | 分頁錯誤 | Fault | 有 | 存取記憶體。 |
0FH | - | 保留 | |||
10H | #MF | 浮點運算錯誤 | Fault | 無 | 浮點運算指令或 WAIT/FWAIT 指令。 |
11H | #AC | 對齊檢查 | Fault | 有 | 存取記憶體。 |
12H | #MC | 機器檢查 | Abort | 無 | 和機器型號有關。 |
13H ~ 1FH | - | 保留 | |||
20H ~ FFH | - | 自由使用 |
中斷處理程式
1,在響應中斷的時候,核心會執行一個函式--中斷服務程式(interrupt handler)或者叫做中斷服務例程(interrupt service routine,ISR)。中斷處理程式是裝置驅動程式的一部分。
2,中斷處理程式與核心其他函式的區別在於:中斷處理程式是由核心呼叫來響應中斷的,運行於中斷上下文(interrupt context)。
3,中斷處理程式應該儘量快的執行,儘可能快地恢復中斷程式碼的執行。
上半部與下半部的對比
又想讓中斷服務程式儘量快地執行,同時又想讓程式完成儘可能多的工作,這兩個目標顯然是有矛盾的,基於這樣的矛盾,我們把中斷服務程式劃分為兩個部分:上半部(top half)和下半部(bottom half)。
1,上半部:接收到一箇中斷就馬上開始執行,具有嚴格的時限要求,比如 對硬體裝置的響應和對硬體進行復位,這些工作都是在所有中斷被禁止 的情況下完成的。中斷服務程式是上半部。
2,下半部:沒有特別嚴格的時限要求,允許稍後完成的工作被劃分到下半部來做。一般來說中斷服務程式返回的時候會立刻執行下半部。
註冊中斷服務程式
驅動程式通過以下的函式來註冊一箇中斷服務程式:
int request_irq(unsigned int irq,//中斷號
irqreturn_t (*handler)(int,void *,struct pt_regs*),//中斷服務程式
unsigned long irqflags,
const char *devname,//產生中斷的裝置名
void *dev_id)
1,irqflags引數可以是0,也可是幾個掩碼(SA_INTERRUPT,SA_SAMPLE_RANDOM,SA_SHIRQ)的或操作。
SA_INTERRUPT:表明該中斷處理程式是快速中斷處理程式(fast interrupt handler)。加此標誌說明中斷服務程式在禁止 所有中斷的情況之下執行。如果沒有這個標誌的話,除了正在執行的中斷服務程式對應的那條中斷線遮蔽之外,其他的中斷都是處於啟用狀態。
SA_SAMPLE_RANDOM:(待考查)。
SA_SHIRQ:表示可以在多箇中斷處理程式間共享中斷線。在同一個中斷線上的每個中斷處理程式必須設定該標誌。
2,dev_id主要用於共享中斷線。當一箇中斷處理程式需要釋放的時候,dev_id將提供唯一的資訊,用於標識具體刪除哪個中斷處理程式,如果沒有該標誌,核心無法知道同一個中斷線上到底要刪除哪個中斷服務程式。如果無需共享中斷線,那麼該指標設定為NULL即可。
request_irq如果呼叫成功則返回0,失敗的話返回非0,最常見的是返回_EBUSY,表示此時中斷線正在被使用,或者沒有設定SA_SHIRQ標誌。
***********************注意點**********************
request_irq函式可能導致睡眠,所以該函式不能用在中斷上下文和不能阻塞的程序中。原因是:在註冊中斷處理程式的過程中,核心需要在/proc/irq檔案建立一個與中斷對應的項,proc_makedir ()就是用來建立新的procfs項的。proc_makedir()通過proc_create()來對procfs進行設定,而proc_create()會呼叫kmalloc()來請求分配記憶體。而kmalloc()是可以睡眠的。
************************注意點*********************
釋放中斷處理程式:
void free_irq(unsigned int irq,void *dev_id);
如果指定的中斷線未設定共享標誌,那麼在刪除中斷處理程式的同時禁用這條中斷線,如果中斷線是共享的,那麼根據dev_id來刪除指定的中斷處理程式。而只有等到該中斷線上的所有中斷處理程式都刪除完了才會禁用該中斷線。
六,編寫中斷處理程式
1,static irqreturn_t (*handler)(int irq,void *dev_id,struct pt_regs* regs)irq指中斷號,dev_id和request_irq中的dev_id必須一致。
以 Linux 為例,interupt 分成 2 部分
中斷上下文
當執行一箇中斷處理程式或者下半部時,核心執行在中斷上下文。因為中斷沒有程序的背景,所以中斷上下文中不能睡眠(核心排程的單位是程序,要進行的是程序上下文切換)。另外,中斷處理程式並沒有自己的棧,它共享被中斷程序的核心棧。如果沒有正在執行的程序,它就使用idle程序棧。
中斷下半部 (bottom half)
可以推遲的工作
一,什麼是下半部?下半部就是與中斷處理密切相關,但是中斷處理程式本身不執行的工作。
二,為什麼要用下半部?中斷處理程式執行的時間應該儘量的短,因為在中斷服務例程執行期間,當前的中斷線會被遮蔽(如果設置了SA_INTTERUPT則會遮蔽所有的中斷),這樣其他的中斷就極有可能無法得到處理器的響應。為此,為了儘量縮短中斷服務程式的執行,我們要把一些對時間要求不嚴格的工作推遲去作。這就是為什麼需要使用下半部的原因。
三,下半部實現方法
有軟中斷、tasklet、工作佇列 (workqueue)
其中 tasklet 是基於軟中斷實現的。
軟中斷是一組靜態定義的下半部介面,一共有32個,可以在所有處理器上同時執行(甚至相同型別的軟中斷可以同時執行)。
tasklet則沒有這麼寬鬆的條件--相同型別的 tasklet 是不能同時執行的。對於大部分下半部處理來說,tasklet 就足夠了,像網路這樣要求非常高的才需要軟中斷。
另外,軟中斷是在編譯期靜態註冊的,而tasklet可以通過程式碼動態註冊。
四,軟中斷的實現
軟中斷由 softirq_action結構表示,定義在<linux/interrupt.h>中
struct softirq_action{
void (*action)(struct softirq_action*);//for process the bottom half
void *data;//parameter for the function above
在<kernel/softirq.c>中包含了擁有32個該結構成員的陣列,每個被註冊的softirq佔據陣列的一項,
所以,最多應該可以有32個softirq,這是一個定值,無法動態改變。
1,軟中斷處理程式action的函式原型
void softirq_handler(struct softirq_action*);
軟中斷不會被另外一個軟中斷搶佔,唯一可以搶佔軟中斷的只有中斷服務程式。但是,其他型別的軟
中斷,甚至是同類型的軟中斷可以在其他處理器上面執行。
2,執行軟中斷
一個軟中斷註冊之後只有被標記了才會執行,這被稱作觸發軟中斷(raising the softirq)。一般的,
中斷處理程式(上半部)在返回前會標記軟中斷,使其在稍後執行。
在 以下地方,softirq會被檢查和執行:
1)處理完一個硬體中斷(中斷服務程式上半部)
2)在ksoftirqd核心執行緒中
3)在那些顯示檢查和執行待處理的軟中斷的程式碼中,如網路子系統
不管是在什麼地方,以什麼樣的方式來喚起softirq,softirq都要在do_softirq()中執行。該函式
實現比較簡單,就是用迴圈遍歷待處理的softirq,每個都呼叫一下。
3,使用軟中斷
軟中斷留給系統中對時間有嚴格要求以及最重要的下半部使用,目前,只有網路子系統和SCSI直接使用軟中斷,像核心定時器和 tasklet 都是建立在軟中斷上的。
1)分配索引
在編譯期間,通過<linux/interrupt.h>中的列舉型別來靜態宣告一個軟中斷。核心用從0開始的索引來表示一種
相對優先順序。index小的比大的優先順序要高。建立一個新的軟中斷必須在此列舉型別中加入新的項。而且加入的的時候必須根據你希望賦予它的優先順序來決定加入到什麼位置。一般加入到網路相關的項之後,最後一項之前。
2)註冊軟中斷處理程式
在執行時通過open_softirq()註冊軟中斷處理程式。該函式有3個引數:軟中斷索引號,中斷處理函式指標,還有就是data資料域指標。
3)觸發軟中斷
在列舉型別列表中新增新項以及通過open_softirq()之後軟中斷服務程式就可以執行了,raising_softirq()函式
可以將軟中斷設定為一個掛起狀態,讓它在下次do_softirq()的時候投入執行。
五,Tasklets
tasklets是建立在軟中斷上的,換句話說,tasklets本身就是一種軟中斷。
tasklets由兩類軟中斷表示:HI_SOFTIRQ和TASKLET_SOFTIRQ,兩者的唯一區別就是前者比後者的優先順序高。
1,tasklet結構體,定義在<linux/interrupt.h>中
struct tasklet_struct{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
}
func函式指標是tasklet中斷處理函式,data是函式引數,state是tasklet的狀態,有0,TASKLET_STATE_SCHED和
TASKLET_STATE_RUN。TASKLET_STATE_SCHED表示tasklet已經被排程,準備投入執行,而TASKLET_STATE_RUN則
表示tasklet正在執行。count是引用計數器,當count不為0時,tasklet被禁止,為0時,tasklet才被啟用。
2,排程tasklets
已排程的tasklet存放在兩個單處理器資料結構:tasklet_vec和tasklet_hi_vec。這兩個結構都是由tasklet_struct
組成的,每一個節點代表不同的tasklet。
tasklets由tasklet_schedule()和tasklet_hi_schedule()函式進行呼叫。以下是tasklet_schedule()的執行細節:
1)如果tasklet的狀態是TASKLET_STATE_SCHED,則說明tasklet已經被排程,函式立即返回。
2)儲存中斷狀態,然後禁止中斷,這樣可以保證處理器資料不會混亂。
3)把需要排程的tasklet加到tasklet_vec或tasklet_hi_vec連結串列中。
4)喚起TASKLET_SOFTIRQ或HI_SOFTIRQ,這樣下一次在do_softirq()中就會執行該tasklet。
5)恢復中斷到原狀態並返回。
例外的型態
程式錯誤例外有三種:
- Fault:這種例外通常是可以更正的,而且在更正後,程式應該可以繼續無誤地進行。在發生 fault 時,處理器會回到造成 fault 指令之前的狀況。在堆疊中存放的返回位址,存放的是造成 fault 的指令的位址。所以,在例外處理完,返回原程式時,會重新執行原來造成 fault 的指令。正常的話,應該不會再產生 fault。例如,最常見的 fault 應該是分頁錯誤(Page-fault),分頁錯誤的處理程序應該要讀入所需要的分頁,再返回程式中,讓程式可以繼續執行。
- Trap:這種例外是在被 trap 的指令執行完後,立刻產生的。因此,它的返回位址是被 trap 的指令的下一個指令。在例外返回後,程式可以正確無誤地斷續執行。如果被 trap 的是一個跳躍指令(例如,JMP 指令)也不會有問題,它的返回位址會是 JMP 指令的目標。
- Abort:這種例外是非常嚴重的錯誤,沒有辦法返回原程式繼續執行。通常,只有在機器錯誤、或是在系統表格中有不一致或不合法的數值時,才會出現這種例外。這類例外的處理程序,應該是把錯誤發生時的狀態存下,並儘可能安全地關閉應用程式和系統。
因為在中斷或(有些)例外發生時,原程式必須能繼續執行,因此,中斷和例外一定是在指令和指令中間發生,而不會在指令進行的途中發生。
中斷服務需要注意哪些
1. 中断函数代码应尽量简洁。一般不宜在中断函数内编写大量复杂冗长的代码;应尽量避免在中断函数内调用其他自定义函数;
2、尽量避免在中断内调用数学函数。因为某些数学函数涉及相关的库函数调用和中间变量较多,可能出现交叉调用。在必须使用数学函数时,可考虑将复杂的数学函数运算任务交给主程序完成,中断函数通过全局变量引用其结果;
3、宏的定义与调用。在中断函数中调用宏,可减少在函数调用中压栈 (push) 与出栈 (pop) 的开销。
九个小注意事项
1、中断函数不能进行参数传递
2、中断函数没有返回值
3、在任何情况下都不能直接调用中断函数
4、中断函数使用浮点运算要保存浮点寄存器的状态。
5、如果在中断函数中调用了其它函数,则被调用函数所使用的寄存器必须与中断函数相同,被调函数最好设置为可重入的。
6、(可忽略)C51编译器对中断函数编译时会自动在程序开始和结束处加上相应的内容,具体如下:
在程序开始处对ACC、B、DPH、DPL和PSW入栈,结束时出栈。
中断函数未加using n修饰符的,开始时还要将R0~R1入栈,结束时出栈。
如中断函数加using n修饰符,则在开始将PSW入栈后还要修改PSW中的工作寄存器组选择位。
C51编译器从绝对地址8m 3处产生一个中断向量,其中m为中断号,也即interrupt后面的数字。该向量包含一个到中断函数入口地址的绝对跳转。
7、中断函数最好写在文件的尾部,并且禁止使用extern存储类型说明。防止其它程序调用。
8、在设计中断时,要注意的是哪些功能应该放在中断程序中,哪些功能应该放在主程序中。一般来说中断服务程序应该做最少量的工作,这样做有很多好处。
首先系统对中断的反应面更宽了,有些系统如果丢失中断或对中断反应太慢将产生十分严重的后果,这时有充足的时间等待中断是十分重要的。
其次它可使中断服务程序的结构简单,不容易出错。中断程序中放入的东西越多,他们之间越容易起冲突。简化中断服务程序意味着软件中将有更多的代码段,但可把这些都放入主程序中。
9、中断服务程序的设计对系统的成败有至关重要的作用,要仔细考虑各中断之间的关系和每个中断执行的时间,特别要注意那些对同一个数据进行操作的中断
举例说明
中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
- __interrupt double compute_area (double radius)
- {
- double area = PI * radius * radius;
- printf("\nArea = %f", area);
- return area;
- }
这个函数有太多的错误了:
1) ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4) 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。
https://www.itread01.com/content/1547179205.html
No comments:
Post a Comment