設備與處理器之間的工作通常來說是非同步,設備數據要傳遞給處理器通常來說有以下幾種方法:輪詢、等待和中斷。
讓CPU進行輪詢等待總是不能讓人滿意,所以通常都採用中斷的形式,讓設備來通知CPU讀取數據。
2.6內核的函數參數與現在的參數有所區別,這里都主要介紹概念,具體實現方法需要結合具體的內核版本。
request_irq函數申請中斷,返回0表示申請成功,其他返回值表示申請失敗,其具體參數解釋如下:
flags 掩碼可以使用以下幾個:
快速和慢速處理常式 :現代內核中基本沒有這兩個概念了,使用SA_INTERRUPT位後,當中斷被執行時,當前處理器的其他中斷都將被禁止。通常不要使用SA_INTERRUPT標志位,除非自己明確知道會發生什麼。
共享中斷 :使用共享中斷時,一方面要使用SA_SHIRQ位,另一個是request_irq中的dev_id必須是唯一的,不能為NULL。這個限制的原因是:內核為每個中斷維護了一個共享處理常式的列表,常式中的dev_id各不相同,就像設備簽名。如果dev_id相同,在卸載的時候引起混淆(卸載了另一個中斷),當中斷到達時會產生內核OOP消息。
共享中斷需要滿足以下一個條件才能申請成功:
當不需要使用該中斷時,需要使用free_irq釋放中斷。
通常我們會在模塊載入的時候申請安裝中斷處理常式,但書中建議:在設備第一次打開的時候安裝,在設備最後一次關閉的時候卸載。
如果要查看中斷觸發的次數,可以查看 /proc/interrupts 和 /proc/stat。
書中講述了如何自動檢測中斷號,在嵌入式開發中通常都是查看原理圖和datasheet來直接確定。
自動檢測的原理如下:驅動程序通知設備產生中斷,然後查看哪些中斷信號線被觸發了。Linux提供了以下方法來進行探測:
探測工作耗時較長,建議在模塊載入的時候做。
中斷處理函數和普通函數其實差不多,唯一的區別是其運行的中斷上下文中,在這個上下文中有以下注意事項:
中斷處理函數典型用法如下:
中斷處理函數的參數和返回值含義如下:
返回值主要有兩個:IRQ_NONE和IRQ_HANDLED。
對於中斷我們是可以進行開啟和關閉的,Linux中提供了以下函數操作單個中斷的開關:
該方法可以在所有處理器上禁止或啟用中斷。
需要注意的是:
如果要關閉當前處理器上所有的中斷,則可以調用以下方法:
local_irq_save 會將中斷狀態保持到flags中,然後禁用處理器上的中斷;如果明確知道中斷沒有在其他地方被禁用,則可以使用local_irq_disable,否則請使用local_irq_save。
locat_irq_restore 會根據上面獲取到flags來恢復中斷;local_irq_enable 會無條件打開所有中斷。
在中斷中需要做一些工作,如果工作內容太多,必然導致中斷處理所需的時間過長;而中斷處理又要求能夠盡快完成,這樣才不會影響正常的系統調度,這兩個之間就產生了矛盾。
現在很多操作系統將中斷分為兩個部分來處理上面的矛盾:頂半部和底半部。
頂半部就是我們用request_irq來注冊的中斷處理函數,這個函數要求能夠盡快結束,同時在其中調度底半部,讓底半部在之後來進行後續的耗時工作。
頂半部就不再說明了,就是上面的中斷處理函數,只是要求能夠盡快處理完成並返回,不要處理耗時工作。
底半部通常使用tasklet或者工作隊列來實現。
tasklet的特點和注意事項:
工作隊列的特點和注意事項:
② linux驅動中斷,程序運行幾個小時後系統崩潰
中斷與定時器:
中斷的概念:指CPU在執行過程中,出現某些突發事件急待處理,CPU暫停執行當前程序,轉去處理突發事件
,處理完後CPU又返回原程序被中斷的位置繼續執行
中斷的分類:內部中斷和外部中斷
內部中斷:中斷源來自CPU內部(軟體中斷指令、溢出、觸發錯誤等)
外部中斷:中斷源來自CPU外部,由外設提出請求
屏蔽中斷和不可屏蔽中斷:
可屏蔽中斷:可以通過屏蔽字被屏蔽,屏蔽後,該中斷不再得到響應
不可平布中斷:不能被屏蔽
向量中斷和非向量中斷:
向量中斷:CPU通常為不同的中斷分配不同的中斷號,當檢測到某中斷號的中斷到來後,就自動跳轉到與該中斷號對應的地址執行
非向量中斷:多個中斷共享一個入口地址。進入該入口地址後再通過軟體判斷中斷標志來識別具體哪個是中斷
也就是說向量中斷由軟體提供中斷服務程序入口地址,非向量中斷由軟體提供中斷入口地址
/*典型的非向量中斷首先會判斷中斷源,然後調用不同中斷源的中斷處理程序*/
irq_handler()
{
...
int int_src = read_int_status();/*讀硬體的中斷相關寄存器*/
switch(int_src){//判斷中斷標志
case DEV_A:
dev_a_handler();
break;
case DEV_B:
dev_b_handler();
break;
...
default:
break;
}
...
}
定時器中斷原理:
定時器在硬體上也以來中斷,PIT(可編程間隔定時器)接收一個時鍾輸入,
當時鍾脈沖到來時,將目前計數值增1並與已經設置的計數值比較,若相等,證明計數周期滿,產生定時器中斷,並
復位計數值。
如下圖所示:
Linux中斷處理程序架構:
Linux將中斷分為:頂半部(top half)和底半部(bottom half)
頂板部:完成盡可能少的比較緊急的功能,它往往只是簡單的讀取寄存器中的中斷狀態並清除中斷標志後就進行
「登記中斷」(也就是將底半部處理程序掛在到設備的底半部執行隊列中)的工作
特點:響應速度快
底半部:中斷處理的大部分工作都在底半部,它幾乎做了中斷處理程序的所有事情。
特點:處理相對來說不是非常緊急的事件
小知識:Linux中查看/proc/interrupts文件可以獲得系統中斷的統計信息。
如下圖所示:
第一列是中斷號 第二列是向CPU產生該中斷的次數
介紹完相關基礎概念後,讓我們一起來探討一下Linux中斷編程
Linux中斷編程:
1.申請和釋放中斷
申請中斷:
int request_irq(unsigned int irq,irq_handler_t handler,
unsigned long irqflags,const char *devname,void *dev_id)
參數介紹:irq是要申請的硬體中斷號
handler是向系統登記的中斷處理程序(頂半部),是一個回調函數,中斷發生時,系統調用它,將
dev_id參數傳遞給它
irqflags:是中斷處理的屬性,可以指定中斷的觸發方式和處理方式:
觸發方式:IRQF_TRIGGER_RISING、IRQF_TRIGGER_FALLING、IRQF_TRIGGER_HIGH、IRQF_TRIGGER_LOW
處理方式:IRQF_DISABLE表明中斷處理程序是快速處理程序,快速處理程序被調用時屏蔽所有中斷
IRQF_SHARED表示多個設備共享中斷,dev_id在中斷共享時會用到,一般設置為NULL
返回值:為0表示成功,返回-EINVAL表示中斷號無效,返回-EBUSY表示中斷已經被佔用,且不能共享
頂半部的handler的類型irq_handler_t定義為
typedef irqreturn_t (*irq_handler_t)(int,void*);
typedef int irqreturn_t;
2.釋放IRQ
有請求當然就有釋放了
void free_irq(unsigned int irq,void *dev_id);
參數定義與request_irq類似
3.使能和屏蔽中斷
void disable_irq(int irq);//等待目前中斷處理完成(最好別在頂板部使用,你懂得)
void disable_irq_nosync(int irq);//立即返回
void enable_irq(int irq);//
4.屏蔽本CPU內所有中斷:
#define local_irq_save(flags)...//禁止中斷並保存狀態
void local_irq_disable(void);//禁止中斷,不保存狀態
下面來分別介紹一下頂半部和底半部的實現機制
底半部機制:
簡介:底半部機制主要有tasklet、工作隊列和軟中斷
1.底半部是想方法之一tasklet
(1)我們需要定義tasklet機器處理器並將兩者關聯
例如:
void my_tasklet_func(unsigned long);/*定義一個處理函數*/
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data);
/*上述代碼定義了名為my_tasklet的tasklet並將其餘
my_tasklet_func()函數綁定,傳入的參數為data*/
(2)調度
tasklet_schele(&my_tasklet);
//使用此函數就能在是當的時候進行調度運行
tasklet使用模板:
/*定義tasklet和底半部函數並關聯*/
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);
/*中斷處理底半部*/
void xxx_do_tasklet(unsigned long)
{
...
}
/*中斷處理頂半部*/
irqreturn_t xxx_interrupt(int irq,void *dev_id)
{
...
tasklet_schele(&xxx_tasklet);//調度地板部
...
}
/*設備驅動模塊載入函數*/
int __init xxx_init(void)
{
...
/*申請中斷*/
result = request_irq(xxx_irq,xxx_interrupt,
IRQF_DISABLED,"xxx",NULL);
...
return IRQ_HANDLED;
}
/*設備驅動模塊卸載函數*/
void __exit xxx_exit(void)
{
...
/*釋放中斷*/
free_irq(xxx_irq,xxx_interrupt);
...
}
2.底半部實現方法之二---工作隊列
使用方法和tasklet類似
相關操作:
struct work_struct my_wq;/*定義一個工作隊列*/
void my_wq_func(unsigned long);/*定義一個處理函數*/
通過INIT_WORK()可以初始化這個工作隊列並將工作隊列與處理函數綁定
INIT_WORK(&my_wq,(void (*)(void *))my_wq_func,NULL);
/*初始化工作隊列並將其與處理函數綁定*/
schele_work(&my_wq);/*調度工作隊列執行*/
/*工作隊列使用模板*/
/*定義工作隊列和關聯函數*/
struct work_struct(unsigned long);
void xxx_do_work(unsigned long);
/*中斷處理底半部*/
void xxx_do_work(unsigned long)
{
...
}
/*中斷處理頂半部*/
/*中斷處理頂半部*/
irqreturn_t xxx_interrupt(int irq,void *dev_id)
{
...
schele_work(&my_wq);//調度底半部
...
return IRQ_HANDLED;
}
/*設備驅動模塊載入函數*/
int xxx_init(void)
{
...
/*申請中斷*/
result = request_irq(xxx_irq,xxx_interrupt,
IRQF_DISABLED,"xxx",NULL);
...
/*初始化工作隊列*/
INIT_WORK(&my_wq,(void (*)(void *))xxx_do_work,NULL);
}
/*設備驅動模塊卸載函數*/
void xxx_exit(void)
{
...
/*釋放中斷*/
free_irq(xxx_irq,xxx_interrupt);
...
}
③ Linux中斷 異常 系統調用 中斷上半部 中斷下半部 這些有什麼區別和聯系
中斷分軟中斷跟硬中斷,硬中斷是由硬體從外部觸發,軟中斷由軟體觸發,就像linux系統調用int 80一樣。至於中斷的上下部其實就是因為中斷的處理時間跟它的優先順序不一定成正比,所以一般先處理中斷最重要的部分(上半部),待到不怎麼忙的時候,再來處理比較悠閑的部分(下半部)。就像輸入的時候,拿到鍵盤輸入的是什麼才是最重要的(上半部),顯示字元才是次要的(下半部)。
④ Linux-怎麼理解軟中斷
中斷是系統用來響應硬體設備請求的一種機制,它會打斷進程的正常調度和執行,然後調用內核中的中斷處理程序來響應設備的請求。
你可能要問了,為什麼要有中斷呢?我可以舉個生活中的例子,讓感受一下中斷的魅力。
比如你訂了一份外賣,但是不確定外賣什麼時候送到,也沒有別的方法了解外賣的進度,但是,配送員送外賣是不等人的,到了你這兒沒人取的話,就直接走人了,所以你只能苦苦等著,時不時去門口看看外賣送到沒,而不能幹其他事情。
不過呢,如果在訂外賣的時候,你就跟配送員約定好,讓他送到後給你打個電話,那你就不用苦苦等待了,就可以去忙別的事情,直到電話一響,接電話、取外賣就可以了。
這里的「打電話」,其實就是一個中斷。沒接到電話的時候,你可以做其他的事情;只有接到了電話(也就是發生中斷),你才要進行另一個動作:取外賣。
這個例子你就可以發現, 中斷其實是一種非同步的事件處理機制,可以提高系統的並發處理能力。
由於中斷處理程序會打斷其他進程的運行,所以, 為了減少對正常進程運行調度的影響,中斷處理程序就需要盡可能快地運行。 如果中斷本身要做的事情不多,那麼處理起來也不會有太大問題;但如果中斷要處理的事情很多,中斷服務程序就有可能要運行很長時間。
特別是,中斷處理程序在響應中斷時,還會臨時關閉中斷。這就會導致上一次中斷處理完成之前,其他中斷都不能響應,也就是說中斷有可能會丟失。
那麼還是以取外賣為例。假如你訂了 2 份外賣,一份主食和一份飲料,並且是由 2 個不同的配送員來配送。這次你不用時時等待著,兩份外賣都約定了電話取外賣的方式。但是,問題又來了。
當第一份外賣送到時,配送員給你打了個長長的電話,商量發票的處理方式。與此同時,第二個配送員也到了,也想給你打電話。
但是很明顯,因為電話占線(也就是關閉了中斷響應),第二個配送員的電話是打不通的。所以,第二個配送員很可能試幾次後就走掉了(也就是丟失了一次中斷)。
如果你弄清楚了「取外賣」的模式,那對系統的中斷機制就很容易理解了。事實上,為了解決中斷處理程序執行過長和中斷丟失的問題,Linux 將中斷處理過程分成了兩個階段,也就是 上半部和下半部:
比如說前面取外賣的例子,上半部就是你接聽電話,告訴配送員你已經知道了,其他事兒見面再說,然後電話就可以掛斷了;下半部才是取外賣的動作,以及見面後商量發票處理的動作。
這樣,第一個配送員不會佔用你太多時間,當第二個配送員過來時,照樣能正常打通你的電話。
除了取外賣,我再舉個最常見的網卡接收數據包的例子,讓你更好地理解。
網卡接收到數據包後,會通過 硬體中斷 的方式,通知內核有新的數據到了。這時,內核就應該調用中斷處理程序來響應它。你可以自己先想一下,這種情況下的上半部和下半部分別負責什麼工作呢?
對上半部來說,既然是快速處理,其實就是要把網卡的數據讀到內存中,然後更新一下硬體寄存器的狀態(表示數據已經讀好了),最後再發送一個 軟中斷 信號,通知下半部做進一步的處理。
而下半部被軟中斷信號喚醒後,需要從內存中找到網路數據,再按照網路協議棧,對數據進行逐層解析和處理,直到把它送給應用程序。
所以,這兩個階段你也可以這樣理解:
實際上,上半部會打斷 CPU 正在執行的任務,然後立即執行中斷處理程序。而下半部以內核線程的方式執行,並且每個 CPU 都對應一個軟中斷內核線程,名字為 「ksoftirqd/CPU 編號」,比如說, 0 號 CPU 對應的軟中斷內核線程的名字就是 ksoftirqd/0。
不過要注意的是,軟中斷不只包括了剛剛所講的硬體設備中斷處理程序的下半部,一些內核自定義的事件也屬於軟中斷,比如內核調度和 RCU 鎖(Read-Copy Update 的縮寫,RCU 是 Linux 內核中最常用的鎖之一)等。
不知道你還記不記得,前面提到過的 proc 文件系統。它是一種內核空間和用戶空間進行通信的機制,可以用來查看內核的數據結構,或者用來動態修改內核的配置。其中:
運行下面的命令,查看 /proc/softirqs 文件的內容,你就可以看到各種類型軟中斷在不同 CPU 上的累積運行次數:
在查看 /proc/softirqs 文件內容時,你要特別注意以下這兩點。
第一,要注意軟中斷的類型,也就是這個界面中第一列的內容。從第一列你可以看到,軟中斷包括了 10 個類別,分別對應不同的工作類型。比如 NET_RX 表示網路接收中斷,而 NET_TX 表示網路發送中斷。
第二,要注意同一種軟中斷在不同 CPU 上的分布情況,也就是同一行的內容。正常情況下,同一種中斷在不同 CPU 上的累積次數應該差不多。比如這個界面中,NET_RX 在 CPU0 和 CPU1 上的中斷次數基本是同一個數量級,相差不大。
不過你可能發現,TASKLET 在不同 CPU 上的分布並不均勻。TASKLET 是最常用的軟中斷實現機制,每個 TASKLET 只運行一次就會結束 ,並且只在調用它的函數所在的 CPU 上運行。
因此,使用 TASKLET 特別簡便,當然也會存在一些問題,比如說由於只在一個 CPU 上運行導致的調度不均衡,再比如因為不能在多個 CPU 上並行運行帶來了性能限制。
另外,剛剛提到過,軟中斷實際上是以內核線程的方式運行的,每個 CPU 都對應一個軟中斷內核線程,這個軟中斷內核線程就叫做 ksoftirqd/CPU 編號。那要怎麼查看這些線程的運行狀況呢?
其實用 ps 命令就可以做到,比如執行下面的指令:
注意,這些線程的名字外面都有中括弧,這說明 ps 無法獲取它們的命令行參數(cmline)。一般來說,ps 的輸出中,名字括在中括弧里的,一般都是內核線程。
Linux 中的中斷處理程序分為上半部和下半部:
上半部對應硬體中斷,用來快速處理中斷。
下半部對應軟中斷,用來非同步處理上半部未完成的工作。
Linux 中的軟中斷包括網路收發、定時、調度、RCU 鎖等各種類型,可以通過查看 /proc/softirqs 來觀察軟中斷的運行情況。