linux中斷流程詳解
異常體系比較複雜,但是linux已經準備了很多的函式和框架,但是因為中斷是和具體的開發板相關,所以中斷需要我們自己來處理一些方面,但是這也是很少的一部分,很多公用的處理函式核心已經實現,linux核心搭建了一個非常容易擴充的中斷處理體系。
中斷系統結構涉及的方面很多,而且分佈在很多的函式中,這裡我主要理清一些結構和流程順序已經在哪些函式中實現,我不知道其他人怎麼樣?但是我自己一開始怎是找不到linux核心是怎麼把GPIO設定成中斷的,我找了很久都找不到,還有我們很多的設定,初始化等等東西好像都沒有實現,清除中斷暫存器也不知道是怎麼實現的,只是知道使用中斷,差不多用request_irq函式就差不多了。下面我就把這些涉及的方方面面的流程整理出來。
一、重要結構
/include/linux/irq.h
irq_desc 核心中記錄一個irq_desc的陣列,陣列的每一項對應一箇中斷或者一組中斷使用同一個中斷號,一句話irq_desc幾乎記錄所有中斷相關的東西,這個結構是中斷的核心。其中包括倆個重要的結構irq_chip 和irqaction 。
irq_chip 裡面基本上是一些回撥函式,其中大多用於操作底層硬體,設定暫存器,其中包括設定GPIO為中斷輸入就是其中的一個回撥函式,分析一些原始碼
/include/linux/irq.h
我們可以看到這裡實現的是一個框架,需要我們進一步的填充裡面的函式。我們在分析另一個結構irqactionstruct irq_chip { const char *name; unsigned int (*startup)(unsigned int irq); 啟動中斷 void (*shutdown)(unsigned int irq); 關閉中斷 void (*enable)(unsigned int irq); 使能中斷 void (*disable)(unsigned int irq); 禁止中斷 void (*ack)(unsigned int irq); 中斷應答函式,就是清除中斷標識函式 void (*mask)(unsigned int irq); 中斷遮蔽函式 void (*mask_ack)(unsigned int irq); 遮蔽中斷應答函式,一般用於電平觸發方式,需要先遮蔽再應答 void (*unmask)(unsigned int irq); 開啟中斷 void (*eoi)(unsigned int irq); void (*end)(unsigned int irq); int (*set_affinity)(unsigned int irq, const struct cpumask *dest); int (*retrigger)(unsigned int irq); int (*set_type)(unsigned int irq, unsigned int flow_type); 設定中斷型別,其中包括設定GPIO口為中斷輸入 int (*set_wake)(unsigned int irq, unsigned int on); void (*bus_lock)(unsigned int irq); 上鎖函式 void (*bus_sync_unlock)(unsigned int irq); 解鎖 /* Currently used only by UML, might disappear one day.*/ #ifdef CONFIG_IRQ_RELEASE_METHOD void (*release)(unsigned int irq, void *dev_id); #endif /* * For compatibility, ->typename is copied into ->name. * Will disappear. */ const char *typename; };
include/linux/interrupt.h
我們用irq_request函式註冊中斷時,主要做倆個事情,根據中斷號生成一個irqaction結構並新增到irq_desc中的action結構連結串列,另一發面做一些初始化的工作,其中包括設定中斷觸發方式,設定一些irq_chip結構中沒有初始化的函式為預設,開啟中斷,設定GPIO口為中斷輸入模式(這裡後面有詳細流程分析)。struct irqaction { irq_handler_t handler; 使用者註冊的中斷處理函式 unsigned long flags; 中斷標識 const char *name; 使用者註冊的中斷名字,cat/proc/interrupts時可以看到 void *dev_id; 可以是使用者傳遞的引數或者用來區分共享中斷 struct irqaction *next; irqaction結構鏈,一個共享中斷可以有多箇中斷處理函式 int irq; 中斷號 struct proc_dir_entry *dir; irq_handler_t thread_fn; struct task_struct *thread; unsigned long thread_flags; };
二、中斷流程
整個中斷可以分為幾個大的流程
1.中斷初始化流程 注意這個階段就是我非常迷惑的一個階段,很多初始化,設定暫存器等等問題都是核心啟動的時候就已經初始化了,這個階段做很多工作,其中最重要的就是初始化了irq_chip結構。使得其中的眾多函式已經設定好了,可以被呼叫了。注意這裡只是實現了irq_chip結構的函式,要響應中斷還有很多事情要做。
2.中斷註冊流程 這個流程是我們比較熟悉的,因為我們每次用中斷的時候就是註冊一箇中斷函式。request_irq首先生成一個irqaction結構,其次根據中斷號找到irq_desc陣列項(還記得吧,核心中irq_desc是一個數組,沒一項對應一箇中斷號),然後將irqaction結構新增到irq_desc中的action連結串列中。當然還做一些其他的工作,註冊完成後,中斷函式就可以發生並被處理了。
3.中斷的處理流程 這個流程主要是產生中斷後呼叫irq_chip中的函式來遮蔽,清除中斷等等,然後呼叫irqaction結構中使用者註冊的中斷函式處理中斷,當然還有很多其他的事情要做,不過主要流程是這樣。
中斷流程應該還包括中斷解除安裝,不過內容比較簡單這裡就不過囉唆了,下面我們倆詳細分析一下這些具體的流程。
<一>中斷初始化流程
下面我們分析核心中斷初始化的過程以及如何呼叫到一個新平臺的irq初始化函式。
這裡我們以s3c2410平臺為例,他的中斷初始化函式定義在:
void __init s3c24xx_init_irq(void)
{
……
} 在arch/arm/mach-s3c2410/mach-smdk2410.c內通過MACHINE_START巨集將s3c24xx_init_irq賦值給mach_desc結構體的.init_irq成員。
|
注:MACHINE_START巨集的作用是對mach_desc結構體進行初始化。mach_desc裡定義了一些關鍵的體系架構相關的函式。Porting kernel到新平臺時,這個結構體是非常關鍵的。 init_irq這個成員在系統初始化的時候會被賦值給init_arch_irq全域性變數,如下:
|
注:可以看到這裡不僅初始化了init_arch_irq 全域性變數,同時初始化了system_timer,init_machine等全域性變數。這是kernel支援多平臺的一種機制。當然這裡system_timer和init_machine我不多描述,有興趣的可以大家自己去看。機制和init_arch_irq大同小異。 init_arch_irq函式指標定義在體系架構無關的arch/arm/kernel/irq.c內
/* arch/arm/kernel/irq.c */
void (*init_arch_irq)(void) __initdata = NULL; 並且在init_IRQ函式內會去執行它。
|
asmlinkage void __init start_kernel(void)
{
……
trap_init();
rcu_init();
init_IRQ();
pidhash_init();
clockevents_init();
init_timers();
……
}
上面轉載的文章分析的很好,我也是看了他的問題才找到一些答案的,比如init_arch_irq 函式指標,說明的是這是個全域性的函式指標,跟體系結構相關。我接著上面的繼續分析一下s3c24xx_init_irq()這個函式
這個函式比較長,主要是為每一箇中斷號都要進行設定,主要完成幾個方面的工作
1.清楚中斷標誌
/* first, clear all interrupts pending... */
last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C24XX_EINTPEND);
if (pend == 0 || pend == last)
break;
__raw_writel(pend, S3C24XX_EINTPEND);
printk("irq: clearing pending ext status %08x\n", (int)pend);
last = pend;
}
這裡有個問題沒有弄明白,要是有人知道很感謝告訴我一聲,就是上面的for迴圈,迴圈清除中斷標識,而且設定了一個last變數,不知道是不是為了防止一次擦除中斷失敗。
2. 設定irq_chip結構,中斷的處理函式入口和中斷標識
for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
irqdbf("registering irq %d (extended s3c irq)\n", irqno);
set_irq_chip(irqno, &s3c_irqext_chip);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
首先是填充irq_chip結構,然後是設定中斷的處理函式入口,這個響應中斷的時候就通過函式入口呼叫使用者註冊的中斷處理函式,中斷標識設定為可以使用。<二>中斷註冊處理流程
requesrt_irq函式
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
裡面就一個request_threaded_irq函式,引數的話irq為中斷號,handler為使用者的中斷函式,flags中斷標識,name中斷名稱,dev可以是傳遞的引數可以是作為區分共享中斷。對應irqaction結構中的dev_id,其實request_irq主要是把這些引數來構造一個irqaction結構新增到連結串列中。
繼續看request_threaded_irq函式
/kernel/irq/manage.c
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
chip_bus_lock(irq, desc);
retval = __setup_irq(irq, desc, action);
chip_bus_sync_unlock(irq, desc);
程式碼比較多,但是主要是利用使用者傳遞的引數構造一個irqaction結構,並呼叫__setup_irq函式
__setup_irq內容比較多,主要完成幾個方面的工作
1.新增irqaction結構到irq_desc的action連結串列中,需要判斷是否為共享中斷,只有共享中斷可以新增多箇中斷處理函式,如果是共享中斷,則要檢查中斷處理函式是否和連結串列中其他函式的觸發方式等是否相同,只有一致才可以新增到連結串列中。
2.設定一些irq_chip結構中的函式指標指向預設函式
3.設定中斷的觸發方式和啟動中斷
完成request_irq後中斷就可以被接收和處理了。下面我在整理一下設定GPIO口為中斷輸入的流程
request_irq() --> request_threaded_irq -->__setup_irq() --> __irq_set_trigger() (定義在kernel/irq/manage.c裡) -->set_type
還有印象沒,set_type是irq_chip結構中的一個回撥函式,在arch/arm/plat-s3c24xx/irq.c具體的中斷的irq_chip結構中set_type即對應s3c_irqext_type() (定義在arch/arm/plat-s3c24xx/irq.c裡),看一個外部中斷的程式碼
arch/arm/plat-s3c24xx/irq.c
static struct irq_chip s3c_irqext_chip = {
.name = "s3c-ext",
.mask = s3c_irqext_mask,
.unmask = s3c_irqext_unmask,
.ack = s3c_irqext_ack,
.set_type = s3c_irqext_type,
.set_wake = s3c_irqext_wake
};
再看s3c_irqext_type() 函式,其中就設定了GPIO埠為中斷輸入模式 if ((irq >= IRQ_EINT0) && (irq <= IRQ_EINT3))
{
gpcon_reg = S3C2410_GPFCON;
extint_reg = S3C24XX_EXTINT0;
gpcon_offset = (irq - IRQ_EINT0) * 2;
extint_offset = (irq - IRQ_EINT0) * 4;
}
<三>中斷的處理流程
中斷處理流程又是一個比較複雜的過程,要牽涉到ARM的工作模式,異常,異常向量,還有一堆彙編程式碼。這些我不怎麼懂,我只是看了其他一些人的分析,大概整個流程出來吧。
首先異常向量表,儲存一條跳轉指令,一般存放在0x00000000或者0xffff000地址,linux使用後者,中斷髮生後CPU進入異常模式,將跳轉到相應的異常向量表處執行,異常向量表儲存跳轉指令,經過一段彙編,最後跳轉到中斷的入口asm_do_IRQ
arch/arm/kernel/irq.c
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
irq_enter();
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(irq >= NR_IRQS)) {
if (printk_ratelimit())
printk(KERN_WARNING "Bad IRQ%u\n", irq);
ack_bad_irq(irq);
} else {
generic_handle_irq(irq);
}
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
主要呼叫generic_handle_irq(irq)
include/linux/irq.h
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
desc->handle_irq(irq, desc);
#else
if (likely(desc->handle_irq))
desc->handle_irq(irq, desc);
else
__do_IRQ(irq);
#endif
}
static inline void generic_handle_irq(unsigned int irq)
{
generic_handle_irq_desc(irq, irq_to_desc(irq));
}
generic_handle_irq呼叫前面定義的generic_handle_irq_desc,而generic_handle_irq_desc也沒做什麼,呼叫desc——>handle_irq,這個函式就是irq_desc中的成員,我們在s3c24xx_init_irq()中不是設定了一箇中斷函式入口的嗎?(set_irq_handler() ),這裡就用到了。這裡呼叫desc->handle_irq分為倆種情況,一是單獨的中斷號的,一是共享中斷號的,倆者的區別在於後者需要先判斷是共享中斷的中的哪一個然後再真正的去呼叫handle_irq,所以我這裡分析一下單獨中斷號的處理流程,共享中斷也是一樣可以分析。
我們在回到s3c24xx_init_irq()中,分析一個具體的,以外部中斷為例
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
irqdbf("registering irq %d (ext int)\n", irqno);
set_irq_chip(irqno, &s3c_irq_eint0t4);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
上面程式碼我們看到,set_irq_handler的值是handler_edge_irq ,這裡是處理邊沿觸發的中斷函式,當然還有電平觸發方式的中斷(handler_level_irq),繼續看程式碼
kernel/irq/chip.c
void
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
spin_lock(&desc->lock); 上鎖
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
/*
* If we're currently running this IRQ, or its disabled,
* we shouldn't process the IRQ. Mark it pending, handle
* the necessary masking and go out
*/
if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) || 判斷
!desc->action)) {
desc->status |= (IRQ_PENDING | IRQ_MASKED);
mask_ack_irq(desc, irq); 遮蔽並清除中斷
goto out_unlock;
}
kstat_incr_irqs_this_cpu(irq, desc); 中斷統計計數
/* Start handling the irq */
if (desc->chip->ack) 應答中斷
desc->chip->ack(irq);
/* Mark the IRQ currently in progress.*/
desc->status |= IRQ_INPROGRESS; 標記中斷狀態
do {
struct irqaction *action = desc->action;
irqreturn_t action_ret;
if (unlikely(!action)) {
desc->chip->mask(irq);
goto out_unlock;
}
/*
* When another irq arrived while we were handling
* one, we could have masked the irq.
* Renable it, if it was not disabled in meantime.
*/
if (unlikely((desc->status &
(IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
(IRQ_PENDING | IRQ_MASKED))) {
desc->chip->unmask(irq);
desc->status &= ~IRQ_MASKED;
}
desc->status &= ~IRQ_PENDING;
spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action); 處理中斷,最重要的函式,注意引數,action這個引數將聯絡到我們的使用者中斷處理函式
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
spin_lock(&desc->lock);
} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
desc->status &= ~IRQ_INPROGRESS;
out_unlock:
spin_unlock(&desc->lock);
}
進行追蹤handle_IRQ_event()
kernel/irq/handle.c
trace_irq_handler_entry(irq, action);
ret = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, ret);
呼叫action中的handler,我們註冊中斷的時候注意任務就是構造一個irqaction結構並新增到irq_desc中的irqaction連結串列中的指標action下面。現在處理中斷中我們就看到了呼叫了我們自己的中斷處理函式來處理中斷了。
至此中斷處理流程就結束了,如有不足之處,或者不對的地方,歡迎大家指出,轉載請標明出處。
2011-09-23 19:48
相關推薦
linux中斷流程詳解
異常體系比較複雜,但是linux已經準備了很多的函式和框架,但是因為中斷是和具體的開發板相關,所以中斷需要我們自己來處理一些方面,但是這也是很少的一部分,很多公用的處理函式核心已經實現,linux核心搭建了一個非常容易擴充的中斷處理體系。 中斷系統結構涉及的方面很多,而且分
Linux啟動流程詳解
linux 詳解 啟動流程 grub mbr 內核 linux啟動流程第一部分 Linux啟動基礎知識1.1 linux centos6.8啟動流程圖 BIOS加電自檢à加載MBRà加載啟動grubà加載內核à啟動/sbin/i
linux開機流程詳解
Linux作業系統的開機流程詳解 開機需要十步 第一步:開機自檢(BIOS)就是開始工作之前先對自己的工具進行檢查是否正常,如果正常那就可以進行接下來的步驟假如步正常就得檢測哪裡的問題進行處理。BIOS其實就是主機板上的一給自檢程式,開機先對主機板上自帶的和外接的一些開機必備的裝置進行檢測,像CPU,顯示
Linux 伺服器啟動流程詳解
啟動第一步--載入 BIOS 當你開啟計算機電源,計算機會首先載入 BIOS 資訊,BIOS 資訊是如此的重要,以至於計算機必須在最開始就找到它。這是因為 BIOS 中包含了 CPU 的相關資訊、裝置啟動順序信息、硬碟資訊、記憶體資訊、時鐘資訊、PnP 特性等等。在此之後,計算機心裡就有譜了,知道應該去讀取
Linux 日誌定時輪詢流程詳解
logrotate介紹 對於Linux系統安全來說,日誌檔案是極其重要的工具。日誌檔案包含了關於系統中發生的事件的有用資訊,在排障過程中或者系統性能分析時經常被用到。當日志文件不斷增長的時候,就需要定時切割,否則,寫日誌的速度和效能也會下降,更不便於我們歸檔,查詢。
Linux netstat命令詳解,高級面試必備
bytes tool head osi ngs 進行 pen 通信 詳細信息 簡介 Netstat 命令用於顯示各種網絡相關信息,如網絡連接,路由表,接口狀態 (Interface Statistics),masquerade 連接,多播成員 (Multicast Mem
linux top 命令詳解
ctrl+ 一次 所有 使用方法 ase 隱藏 統計 ini 前臺 top命令是Linux下常用的性能分析工具,能夠實時顯示系統中各個進程的資源占用狀況,類似於Windows的任務管理器。下面詳細介紹它的使用方法。top - 01:06:48 up 1:22, 1 user
【轉】linux awk命令詳解
column 環境變量 最後一行 工作流程 初始 文本文件 for循環 其中 cti 簡介 awk是一個強大的文本分析工具,相對於grep的查找,sed的編輯,awk在其對數據分析並生成報告時,顯得尤為強大。簡單來說awk就是把文件逐行的讀入,以空格為默認分隔符將每行切
Linux ls命令詳解
-c 目錄 輸出 限制 普通 排序 當前 ls -l sna ls 命令可以說是Linux下最常用的命令之一。 -a 列出目錄下的所有文件,包括以 . 開頭的隱含文件。(後有詳解)-b 把文件名中不可輸出的字符用反斜杠加字符編號(就象在c語言裏一樣)的形式列出。-c 輸出
linux lsof命令詳解
open 日誌 文件和目錄 delete 數據報 正在 某個文件 alt targe 簡介 lsof(list open files)是一個列出當前系統打開文件的工具。在linux環境下,任何事物都以文件的形式存在,通過文件不僅僅可以訪問常規數據,還可以訪問網絡連接和硬件
CentOS安裝流程詳解
菜鳥取經路之linux系統安裝 對於剛剛接觸Linux的人們來說,遇到的第一個問題便是如何快速安裝一個Linux系統。我初次接觸 Linux時也是摸索許久才安裝成功。鑒於此,今天就給大家帶來完整詳細的Linux安裝過程。一、準備安裝所需的軟件 1、VMwareWorkstation
linux tail 命令詳解
基本 linux中 file tail命令 方式 sed 缺省 顯示 有效 linux ---tail命令 linux中tail命令---用於查看文件內容 最基本的是cat、more和less。 1. 如果你只想看文件的前5行,可以使用head命令,如: head -5 /
linux 線程詳解
大於 linux下 blog 根據 影響 stack 復制代碼 系統資源 代碼 線程 是計算機中獨立運行的最小單位,運行時占用很少的系統資源。可以把線程看成是操作系統分配CPU時間的基本單元。一個進程可以擁有一個至多個線程。它線程在進程內部共享地址空間、打開的文件描述符等資
linux screen 命令詳解
文本 常用 小時 遠程終端 參考 編輯 load 調整 長時間 一、背景 系統管理員經常需要SSH 或者telent 遠程登錄到Linux 服務器,經常運行一些需要很長時間才能完成的任務,比如系統備份、ftp 傳輸等等。通常情況下我們都是為每一個這樣的任務開一個遠程終端窗口
linux服務ssh詳解
ssh服務ssh服務: 管理服務器的方式: 本地管理 (安裝系統、故障修復) SSH遠程連接的方式 Linux: ssh命令 Windows: Xshell; Xmanager SecureCRT Putty 提供ssh服務/ssh客戶端工具的軟件:[[email
SSL協議握手工作流程詳解(雙向HTTPS流程)
包含 style strong 雙向認證 包括 返回 情況 身份認證 ssl 參考學習文檔:http://www.cnblogs.com/jifeng/archive/2010/11/30/1891779.html SSL協議的工作流程: 服務器認證階段: 1)客戶端向服務
linux系統啟動詳解
oca 3.3 哪些 技術 針對 個性 桌面 inux 解壓縮 要學習linux的命令,我們需要先了解linux系統是如何工作的,這裏我們先了解linux是如何在一臺電腦上啟動加載的!! linux系統啟動過程 第一步、 BIOS初始化 1. BIOS檢測所有的外置
轉-Linux啟動過程詳解(inittab、rc.sysinit、rcX.d、rc.local)
dha mage 模塊 都是 交換 如何配置 mas 完全 打開 http://blog.chinaunix.net/space.php?uid=10167808&do=blog&id=26042 1)BIOS自檢2)啟動Grub/Lilo3)加載內
centos6啟動流程詳解
linux centos6 當我們在平常的工作學習的環境中使用linux時,我們只需要按一下開機鍵,系統就會自動為我們加載好相關配置,然後為我們打開操作界面,那麽在這個過程中究竟都發生了什麽,如果系統突然起不來了,那麽到底是啟動時的哪一部分發生了錯誤呢,下面,我們就來看看linux中的centos6啟動的
[轉載]linux awk命令詳解
基本 特定 收集 comm rip 解釋 文本 工作流程 復制代碼 簡介 awk是一個強大的文本分析工具,相對於grep的查找,sed的編輯,awk在其對數據分析並生成報告時,顯得尤為強大。簡單來說awk就是把文件逐行的讀入,以空格為默認分隔符將每行切片,切開的部分再進行各