中斷就是CPU正常運行期間,由于內、外部事件引起的CPU暫時停止正在運行的程序,去執行該內部事件或外部事件的引起的服務中去,服務執行完畢后再返回斷點處繼續執行的情形。
中斷的意義
極大提高CPU運行效率
中斷服務程序
中斷處理程序:在中斷發生時被調用的函數稱為中斷服務函數。
中斷服務函數的原則:linux是多進程操作系統
中斷不屬于任何一個進程,因此不能在中斷程序中休眠和調用schedule函數放棄CPU
實現終端處理函數有一個原則,就是盡可能的處理并返回
linux中斷頂部和中斷底部
裸機中并沒有此概念,他們是什么?
linux中斷頂部、底部概念
為保證系統實時性,中斷服務程序必須足夠簡短,但實際應用中某些時候發生中斷時必須處理大量的事物,這時候如果都在中斷服務程序中完成,則會嚴重降低中斷的實時性,基于這個原因,linux系統提出了一個概念:把中斷服務程序分為兩部分-頂半部-底半部
頂半部
完成盡可能少的比較急的功能,它往往只是簡單的讀取寄存器的中斷狀態,并清除中斷標志后就進行“中斷標記”(也就是把底半部處理程序掛到設備的底半部執行隊列中)的工作。
特點:響應速度快
底半部:
中斷處理的大部分工作都在底半部,它幾乎做了中斷處理程序的所有事情。
特點:處理相對來說不是非常緊急的事件
底半部機制主要有:tasklet、工作隊列和軟中斷
裸機中斷設計形式
第一種寫法:把發生中斷所需要做的所有的事情全部寫到中斷服務函數體中
void isr()
{
//中斷程序
}
第二種寫法:中斷只記錄,標志,要做的事情在主循環中編寫。
int flag=0;
void main(void)
{
while(1)
{
if(flag)
{
flag=0;
//中斷需要做的事情
}
}
}
void isr()
{
//只做登記
flag=1;
}
第二種寫法,不會影響到其他中斷響應,保證系統的實時性。
補充:是否所有的中斷都需要分為兩部分來實現呢?
不一定!如果發生中斷要做的事情很少不會影響系統的實時性,則就不必分成兩部分實現。
linux中斷API
linux中斷有專門的中斷子系統,其實現原理很復雜,但是驅動開發者不需要知道其實現的具體細節,只需要知道如何應用該子系統提供的API函數來編寫中斷相關驅動代碼即可。
內核使用一個struct irqaction結構描述一個中斷,編寫中斷終極目標就是實現這個結構,當然結構不是我們自己去定義,但是結構中的材料是我們提供的。
struct irq_desc {
struct irq_data irq_data;
·········//省略
irq_flow_handler_t handle_irq;
struct irqaction *action; /* IRQ action list */
}
最關注的幾個成員
interrupt.h \linux-3.5\include\linux
struct irq_data {
unsigned int irq;
unsigned long hwirq;
unsigned int node;
unsigned int state_use_accessors;
struct irq_chip *chip;
struct irq_domain *domain;
void *handler_data;
void *chip_data;
struct msi_desc *msi_desc;
#ifdef CONFIG_SMP
cpumask_var_t affinity;
#endif
};
struct irqaction {
irq_handler_t handler; /*我們要提供的*/
void *dev_id; /*我們要提供的*/
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned int irq; /*我們要提供的*/
unsigned int flags; /*我們要提供的*/
unsigned long thread_flags;
unsigned long thread_mask;
const char *name; /*我們要提供的*/
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
//有五個成員您需要我們提供
request_irq()
原型
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
可以跟struct irqaction 結構體中的成員對應。
功能:向內核注冊一個中斷服務函數,發生中斷號為irq的中斷的時候,會執行handle指針函數。
參數:
irq:中斷編號(每個中斷源有唯一編號),這里的中斷編號不是看硬件手冊,與裸機不同。由內核分配。
handler:中斷服務函數指針,原型typedef irqreturn_t (*irq_handler_t)(int, void *);
flag:中斷屬性,如快速中斷,共享中斷,如果是外部中斷還有:上升沿,下降沿觸發這類標志。
name:中斷名字,注冊后會在/proc/irq/``irq號name文件夾出現。
dev_id:這個參數時傳遞給中斷服務函數。對共享中斷來說,這個參數一定要有,當注銷共享中斷其中一個時,用這個指定標識注銷哪一個。對于有唯一入口的中斷,可以傳遞NULL,但一般來說都會傳遞一個有意義的指針,在中斷程序中使用,以方便編程。
返回值:0表示成功,返回-EINVAL表示中斷號無效 ,返回-EBUSY表示中斷被占用。
頭文件:include/linux/interrupt.h
linux共享中斷
共享中斷是指多個中斷共享一個中斷線的情況,在中斷到來時,會遍歷共次中斷的所有中斷處理函數,直到某一個中斷服務函數時返回IRQ_HANDLED
在中斷處理程序頂半部中,應根據中斷相關硬件寄存器中的信息,判斷是否為本設備的中斷,若不是立即返回IRQ_NONE
request_irq函數參數補充說明
中斷編號
在linux系統中總段編號可能和裸機的中斷編號數值不相同。如何確定一個中斷源的中斷編號?
一般是由芯片廠商寫好所有可中斷號宏定義。在內核源碼中可以找到,如果是外部中斷,內核還提供了通用API函數,可以通過IO管腳編號轉換成它對應的中斷編號
中斷服務函數類型是typedef irqreturn_t (*irq_handler_t)(int, void *);是一個指向有一個整形參數,一個void類型指針,返回值是irqreturn_t的函數指針。這個數據結構規定了中斷服務函數的原型。返回值類型在內核中定義為:
/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
第一個參數int類型的參數是中斷號,第二個void*類型指針是共享中斷的標識id,
這兩個參數在中斷服務被調用的時候傳遞進來的內容實際上就是reauest_irq()在注冊s時候的irq,dev參數。這一點在源碼中有體現,編程的時候要注意,善于利用這個特性編寫程序。
中斷屬性flag
在include/linux/interrupt.h中定義可用的數值,以IRQF開頭的宏,若設置了IRQF_DISABLED,則表示中斷類型屬于獨占類型的中斷,不是共享中斷,若設置了 IRQF_SHARED,則表示鍍鉻設備共享中斷,若設置了 IRQF_SAMPLE_RANDOM,表示對系統獲取隨機數有好處,(這幾個宏可以通過或的方式組合使用),若是外部中斷還要設置IRQF_TRIGGER_XXX標志,表示選擇外部中斷的觸發電平,內核中相關定義如下:
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
* These flags used only by the kernel as part of the
* irq handling routines.
*
* IRQF_DISABLED - keep irqs disabled when calling the action handler.
* DEPRECATED. This flag is a NOOP and scheduled to be removed
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
* IRQF_NO_SUSPEND - Do not disable this IRQ during suspend
* IRQF_FORCE_RESUME - Force enable it on resume even if IRQF_NO_SUSPEND is set
* IRQF_NO_THREAD - Interrupt cannot be threaded
* IRQF_EARLY_RESUME - Resume IRQ early during syscore instead of at device
* resume time.
*/
#define IRQF_DISABLED 0x00000020
#define IRQF_SAMPLE_RANDOM 0x00000040
#define IRQF_SHARED 0x00000080
#define IRQF_PROBE_SHARED 0x00000100
#define __IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
#define IRQF_NO_SUSPEND 0x00004000
#define IRQF_FORCE_RESUME 0x00008000
#define IRQF_NO_THREAD 0x00010000
#define IRQF_EARLY_RESUME 0x00020000
#define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
常用值:IRQF_DISABLED,IRQF_SHARED 這兩個可以用來設置任何中斷,但是不能同時使用
IRQF_TRIGGER_RISING IRQF_TRIGGER_FALLING 只對外部中斷有用,可以和上面的IRQF_DISABLED IRQF_SHARED之一組合使用
IRQF_SHARED:添加上這個標志之后,此中斷號還可以注冊
IRQF_DISABLED:添加上這個標志之后,此中斷號只能執行一次
中斷名name
中斷名字,在cat /proc/interrupts中可以看到此名稱,同時會出現/proc/irq/
irq號/name文件夾出現
中斷設備id識別標志dev
這個參數會在發生中斷,執行服務函數時,作為實參傳遞給中斷服務函數的第二個參數。傳遞什么內容,完全由開發者決定,只要有利于更好的編寫中斷服務函數的都可以傳遞。
在飛共享中斷時候可以是NULL,也可以傳遞任何用戶自定的結構地址,在共享中斷時必須傳遞有效參數,用于發生中斷時,用于在注銷中斷時識別具體要注銷中斷服務函數中的具體哪一個子項共享中斷。
很多書,很多人認為dev這個參數能用于識別在發生中斷時候,是否是本設備產生的中斷,但是實際并不是這樣,要看開發者給這個參數傳遞什么內容,如果傳遞的是一個和中斷狀態有關的硬件寄存器地址,中斷服務程序中可以讀取這個寄存器的內容,從而判斷是否是本設備產生的中斷,如果只是傳遞一個普通的變量地址,并不能通過這個地址區分到底是那個硬件設備產生的中斷
handled
irqreturn_t (*irq_handler_t)(int, void *);
返回值:有下面三種情況
/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
參數:
第一個參數:中斷函數注冊時的中斷號irq
第二個參數:注冊的時候最后一個參數dev_id
中斷服務函數原型:
irqreturn_t isr_function(int irq,void* dev_id)
中斷服務函數模板:
irqreturn_t isr_function(int irq,void* dev_id)
{
return IRQ_HANDLED;
}
對于共享中斷:
irqreturn_t isr_function(int irq,void* dev_id)
{
//根據dev_id判斷是否是本設備產生的中斷
if(readreg(dev_id)!=本設備產生中斷)
return IRQ_NONE;
//以下是本設備產生中斷的代碼
return IRQ_HANDLED;
}
free_irq()
描述 說明
函數原型 void free_irq(int irq,void *dev_id)
函數功能 從內核中斷服務函數鏈表中刪除一個中斷結構
函數參數 同上
函數返回值 無
函數頭文件 include/linux/interrupt.h
函數定義文件 kernel\irq\manage.c (用EXPORT_SYMBOL(free_irq)導出給內核模塊使用)
這個函數跟request_irq函數功能相反,當設備不使用中斷時,使用這個函數把相關中斷在中斷鏈表中的節點釋放掉釋放掉。
disable_irq,disable_irq_nosync
描述 說明
函數原型 void disabled_irq(unsigned int irq)
函數功能 關閉指定的中斷,如果中斷沒有執行完,等待執行完在關閉,不能再中斷中使用,否則自己關閉自己,引起內核崩潰
函數原型 void disabled_irq_nosync(unsigned int irq)
函數功能 關閉中斷,不等待中斷執行完畢,可以在中斷函數中執行
enable_irq
描述 說明
函數原型 void enabled_irq(unsigned int irq)
函數功能 使能指定中斷
local_save_flags(flags)
描述 說明
宏原型 local_save_flags(flags)
宏功能 禁止本CPU全部中斷,并保存CPU狀態信息,(現在很多芯片是多核CPU)這個函數需要和local_irq_restore函數配合使用
宏參數 flags:unsigned long類型用于保存當前CPU狀態信息
宏返回值 無
使用示例:
unsigned long flags;
local_save_flags(flags);
······
local_irq_restore(flags);
local_irq_restore(flags)
描述 說明
宏原型 local_irq_restore(flags)
宏功能 與local_save_flags(flags)功能函數相反,配對使用,用來使能由與local_save_flags禁止的中斷
宏參數 flags:unsigned long類型用于恢復當前CPU狀態信息
宏返回值 無
local_irq_disable()
禁止本CPU中斷,不能保存當前CPU信息
local_irq_enable()
使能由local_irq_disable禁止的中斷,不能還原CPU信息
linux3.5內核關于CPU中斷號
linux中斷注冊函數中的irq中斷號并不是芯片物理上的編號,而是芯片廠商在移植linux系統時定在相關架構的頭文件中定義好的,在內核源碼中,名字一般是irqs.h
irqs.h \linux-3.5\arch\arm\mach-exynos\include\mach
下面是其中關于外部中斷的一部分內容,還有其他內容這里沒有列舉
/* For EXYNOS4 SoCs */
#define EXYNOS4_IRQ_EINT0 IRQ_SPI(16)
#define EXYNOS4_IRQ_EINT1 IRQ_SPI(17)
#define EXYNOS4_IRQ_EINT2 IRQ_SPI(18)
#define EXYNOS4_IRQ_EINT3 IRQ_SPI(19)
#define EXYNOS4_IRQ_EINT4 IRQ_SPI(20)
#define EXYNOS4_IRQ_EINT5 IRQ_SPI(21)
#define EXYNOS4_IRQ_EINT6 IRQ_SPI(22)
#define EXYNOS4_IRQ_EINT7 IRQ_SPI(23)
#define EXYNOS4_IRQ_EINT8 IRQ_SPI(24)
#define EXYNOS4_IRQ_EINT9 IRQ_SPI(25)
#define EXYNOS4_IRQ_EINT10 IRQ_SPI(26)
#define EXYNOS4_IRQ_EINT11 IRQ_SPI(27)
#define EXYNOS4_IRQ_EINT12 IRQ_SPI(28)
#define EXYNOS4_IRQ_EINT13 IRQ_SPI(29)
#define EXYNOS4_IRQ_EINT14 IRQ_SPI(30)
#define EXYNOS4_IRQ_EINT15 IRQ_SPI(31)
·····
可以看出這個文件是存放在和芯片體系架構相關的文件夾中
對于外部中斷,可以通過IO口編號轉換成對應的中斷編號。關于IO口在下小節中說明
static inline int gpio_to_irq(unsigned int gpio)