硬件處理
最近解決一個關(guān)于Linux中斷的問題,把相關(guān)機(jī)制整理了一遍,記錄在此。
不同的外部設(shè)備、不同的體系結(jié)構(gòu)、不同的OS其中斷實(shí)現(xiàn)機(jī)制都有差別,本文對應(yīng)的OS為linux3.4版本,外部設(shè)備為PCI設(shè)備、系統(tǒng)為X86。
概覽
中斷讓外設(shè)能夠通知CPU他需要獲得服務(wù)(讓CPU執(zhí)行指定的中斷服務(wù)例程ISR)。為了達(dá)到這個目的,首先要為中斷執(zhí)行做好準(zhǔn)備,完成初始化相關(guān)的操作。包括:1、 初始化中斷控制器等相關(guān)器件(OS初始化過程中完成);2、 配置并使能外部設(shè)備(比如使用pci_enable_msix),得到irq號;在這個操作過程中,內(nèi)核需要完成的大致操作是:
1、 確定該中斷的執(zhí)行CPU,并在對應(yīng)CPU上建立vector和irq號的對應(yīng)關(guān)系(利用全局per-cpu變量vector_irq),配置中斷控制器(I/OAPIC、PIR等),可能還需要設(shè)置外部設(shè)備(比如設(shè)置MSI
Capacity registers);
2、 為對應(yīng)的irq_desc初始化正確的handle_irq接口(通用邏輯接口);
3、 為對應(yīng)的irq_desc初始化正確的底層chip操作接口。
3、 使用request_irq號為該中斷號指定一個服務(wù)例程;
完成了以上的初始化操作,在外設(shè)中斷到來的時候,為該中斷指定的ISR(Interrupt Service Routines)就能得到執(zhí)行,這個執(zhí)行過程大致如下:
1、 外設(shè)根據(jù)各自的配置,產(chǎn)生中斷信號或者中斷消息(MSI,INT# message)。2、 中斷控制器從外設(shè)獲取中斷電信號或者中斷消息,把它翻譯為vector(CPU使用這個參數(shù)來決定是誰發(fā)生了中斷,要如何處理)并提交到CPU。3、 對X86系統(tǒng),CPU利用從中斷控制器獲取到的vector為索引,查詢IDT (interrupt descriptor table)得到該中斷的處理接口(對linux,是在entry_64.s中定義的函數(shù)common_interrupt接口)并執(zhí)行。4、 在linux定義的common_interrupt接口中,執(zhí)行完中斷執(zhí)行環(huán)境建立后,會進(jìn)入generic interrupt layer執(zhí)行,其首先通過vector查找到irq和對應(yīng)的irq_desc結(jié)構(gòu),并執(zhí)行該結(jié)構(gòu)的handle_irq接口,這個接口就是generic interrupt layer的通用邏輯接口,比如handle_edge_irq/handle_level_irq等;在中斷執(zhí)行的通用邏輯接口中,會通過irq_desc::action調(diào)用外設(shè)指定的ISR。在linux中可以通過/proc/interrupts查看當(dāng)前系統(tǒng)中所有中斷的統(tǒng)計信息,在/proc/irq/xxx(中斷號)下面,可以看到該中斷的詳細(xì)信息。
中斷相關(guān)硬件
這里的描述很多來自INTEL的文檔《Intel Software developer’s Manual, system programming guide》和《PCI Express System Architecture》
中斷控制器
中斷控制器的功能是:把外設(shè)的中斷信號,轉(zhuǎn)換成CPU能夠明白的vector,并完成中斷執(zhí)行控制,確保在合適的時機(jī)把中斷提交給CPU執(zhí)行。對這部分內(nèi)容,《interrupt in linux》有詳細(xì)的描述。1、 8259A:每個8259A有8個管腳,每個管腳對應(yīng)其連接的CPU的IDT中的一個vector,單獨(dú)使用8259A,其硬件連線就決定了對設(shè)備vector的使用。典型的場景是使用兩個8259A級聯(lián),理論最多16個中斷號(就是ISA IRQs),實(shí)際能提供對15個中斷線的處理(master的IRQ2用于連接slave),其具體的分配見下圖。2、 PIR:用于完成輸入的信號到輸出信號的映射。在下圖中PIR被用于完成多個PCI設(shè)備的INT#信號到8259A對應(yīng)引腳的路由。對應(yīng)這種連接方式,在PCI設(shè)備初始化的時候,OS會根據(jù)BISO提供的信息設(shè)置PIR,把INT#路由到O0-O3中正確的管腳,從而體現(xiàn)到8259A的正確管腳(對應(yīng)了vector),這樣INT#信號就被轉(zhuǎn)換為vector并提交到CPU。由于可能有較多的PCI設(shè)備,而PIR的輸入/出錯管腳有限,所以連接到相同輸入關(guān)鍵的INT#會共享一個中斷。
3、 I/O APIC每個I/O APIC提供24個管腳,能夠和外部設(shè)備的中斷線連接,每個管腳都可以通過配RTE(Redirection table entry)配置對應(yīng)的vector。其功能是:把外部設(shè)備的中斷請求,翻譯為local APIC的interrupt message,并按照配置的vector,發(fā)送給指定的local APIC處理(在SMP系統(tǒng),存在多個CPU,也就有多個local APIC)。通常的配置方式是:第一個I/O APIC的前16個管腳,配置來處理之前的ISA IRQs,其它外設(shè)比如PCI設(shè)備,則直接使用其他管腳連接。4、 local APIC其負(fù)責(zé)處理IPI(inter-process interrupt)、直接連接的中斷處理、接收和處理interrupt message,每個CPU有自己的local APIC。對應(yīng)I/O APIC和local APIC的組合,其連接方式見下圖
針對X86中斷控制器硬件和linux對這些硬件的初始化,在《interrupt in linux》中有很詳細(xì)的描述。
X86對中斷的處理
Local APIC的處理過程每個local APIC對應(yīng)了一個CPU。其處理interrupt message的過程如下:1、 判斷該中斷的destination是否為當(dāng)前APIC,如果不是則忽略,否則繼續(xù)處理2、 如果是SMI/NMI/INIT/ExtINT, or SIPI(這些中斷都負(fù)責(zé)特殊的系統(tǒng)管理任務(wù),外設(shè)一般不會使用)被直接送到CPU執(zhí)行,否則執(zhí)行下一步。3、 設(shè)置Local APIC 的IRR寄存器的對應(yīng)bit位。4、 如果該中斷優(yōu)先級高于當(dāng)前CPU正在執(zhí)行的中斷,且當(dāng)前CPU沒有屏蔽中斷(按照X86和LINUX的實(shí)現(xiàn),這時是屏蔽了中斷的),則該高優(yōu)先級中斷會中斷當(dāng)前正在執(zhí)行的中斷(置ISR位,并開始執(zhí)行),低優(yōu)先級中斷會在高優(yōu)先級中斷完成后繼續(xù)執(zhí)行,否則只有等到當(dāng)前中斷執(zhí)行完成(寫了EOI寄存器)后才能開始執(zhí)行下一個中斷。5、 在CPU可以處理下一個中斷的時候,從IRR中選取最高優(yōu)先級的中斷,清0 IRR中的對應(yīng)位,并設(shè)置ISR中的對應(yīng)位,然后ISR中最高優(yōu)先級的中斷被發(fā)送到CPU執(zhí)行(如果其它優(yōu)先級和屏蔽檢查通過)。6、 CPU執(zhí)行中斷處理例程,在合適的時機(jī)(在IRET指令前)通過寫EOI寄存器來確認(rèn)中斷處理已經(jīng)完成,寫EOI寄存器會導(dǎo)致local APIC清理ISR的對應(yīng)bit,對于level trigged中斷,還會向所有的I/O APIC發(fā)送EOI message,通告中斷處理已經(jīng)完成。
說明:1、 關(guān)于Local APIC的IRR和ISR寄存器interrupt request register (IRR) 和 in-service register (ISR),都是256bit寄存器,每個bit對應(yīng)一個中斷(其中[0-15]不能使用,SMI/NMI/INIT/ExtINT/SIPI的發(fā)送和執(zhí)行不經(jīng)過ISR和IRR) 。IRR中保存的是已經(jīng)被local APIC接納但是還沒有開始執(zhí)行的中斷;ISR中保持的是當(dāng)前正在執(zhí)行但是還沒有完成的中斷。2、 中斷優(yōu)先級對應(yīng)通過local APIC發(fā)送到CPU的中斷,按照其vector進(jìn)行優(yōu)先級排序:優(yōu)先級=vector/16數(shù)值越大,優(yōu)先級越高。由于local APIC允許的vector范圍為[16,255],而X86系統(tǒng)預(yù)留了[0,31]作為系統(tǒng)保留使用的vector,實(shí)際的用戶定義中斷的優(yōu)先級的取值范圍為[2,15],在每個優(yōu)先級內(nèi)部,vector的值越大,優(yōu)先級越高。Local APIC中還有一個關(guān)于中斷優(yōu)先級的寄存器TPR(task priority register)寄存器:用于確定打斷線程執(zhí)行需要的中斷優(yōu)先級級別,只有優(yōu)先級高于設(shè)置值的中斷才會被CPU執(zhí)行 (SMI/NMI/INIT/ExtINT, or SIPI不受限制),也就是除了特殊中斷外,優(yōu)先級低于TPR指定值的中斷將被忽略。3、 中斷的pending對于同一個vector,如果有多次中斷請求,可能IRR和ISR對應(yīng)的bit位都被置位,也就是對同一個vector,local APIC可以pending兩個中斷,其后的即使有多處,也會被合并為一個執(zhí)行。4、 中斷執(zhí)行時機(jī)中斷的執(zhí)行總是在指令邊界開始(只有一個特殊的exception:abort在外,出現(xiàn)了這個中斷,系統(tǒng)基本上也就完蛋了),也就是中斷不可能打斷指令的執(zhí)行。
CPU對中斷和異常的處理相關(guān)概念1、 vector(中斷向量)vector是一個整數(shù),在X86CPU上,使用vector對中斷(interrupt,外部設(shè)備產(chǎn)生)和異常(exception,CPU在程序執(zhí)行中產(chǎn)生)統(tǒng)一編號,每個CPU核心內(nèi)部,中斷/異常和vector所以一一對應(yīng)的;但是在各個不同的CPU核心上,相同的vector可以對應(yīng)不同的中斷(至少對于linux的設(shè)置,異常還是使用相同的vector)。vector的取值范圍為[0,255],其中[0,31]被系統(tǒng)保留使用(多數(shù)作為異常的vector),其余的可供外設(shè)中斷使用(系統(tǒng)設(shè)備比如local APIC也占用了部分[32,255]這個范圍的vector)。2、 IDT(interrupt descriptor table)X86 CPU采用一個有256個元素的數(shù)組來描述中斷/異常,該數(shù)組的index為vector;其內(nèi)容包括了三種gate descriptor,用于描述一個中斷/異常的處理接口;這個數(shù)組就是IDT,CPU在收到中斷請求的時候,就利用vector獲取到對應(yīng)的中斷處理接口描述并執(zhí)行。3、 可屏蔽中斷通過CPU INTR管腳/local APIC接收到的中斷是可屏蔽中斷,這些中斷能夠通過清零EFLAGS的IF來屏蔽(CLI指令)。通過INT n指令生成的中斷即使使用了和外部中斷一樣的vector,也是不可屏蔽的;同樣CPU運(yùn)行過程中同步產(chǎn)生的trap、fault、abort等異常也是不可屏蔽的。4、 NMINMI是不可屏蔽中斷(不可通過IF標(biāo)志屏蔽),是通過CPU的NMI管腳發(fā)出的中斷或者通過delivery mode為NMI的方式提交的中斷。NMI中斷在執(zhí)行前,CPU不僅會屏蔽其它中斷,也會屏蔽NMI中斷,直到NMI中斷處理執(zhí)行完成(IRET指令被執(zhí)行)。使用INT 2指令雖然能執(zhí)行NMI中斷處理函數(shù),但是相關(guān)硬件不會介入,也就是沒有相關(guān)的屏蔽NMI中斷的操作。
CPU執(zhí)行中斷的過程1、 利用vector,查IDT得到中斷描述符;2、 如果中斷發(fā)生在用戶態(tài),會首先執(zhí)行stack switch切換到內(nèi)核態(tài)執(zhí)行;3、 依次保存EFLAGS CS IP到當(dāng)前棧,如果需要(有error code的異常),把error code PUSH到當(dāng)前棧。并把IF/TF位清零屏蔽可屏蔽中斷;至此,CPU完成了中斷處理程序執(zhí)行環(huán)境的建立。4、 執(zhí)行中斷描述符定義的中斷處理入口(IDT中指定地址的代碼);5、 根據(jù)環(huán)境執(zhí)行不同的中斷退出方式,比如執(zhí)行現(xiàn)場調(diào)度操作(retint_careful和retint_kernel),最終都會執(zhí)行IRET指令;至此,中斷執(zhí)行完成。異常的執(zhí)行過程類似,只不過異常在執(zhí)行前不會把IF位清零,只清零TF位。
PCI設(shè)備的中斷
本部分的很多內(nèi)容來自《PCI Interrupts for x86 Machines under FreeBSD》和《PCI Express?Base Specification Revision 3.0》和《PCI Express System Architecture》。PCI設(shè)備的中斷有兩種模式:一種是INT#模式,一種是MSI模式。
INT#模式每個PCI設(shè)備用四個中斷信號,對應(yīng)INTA#、INTB# INTC#、INTD#,這些中斷信號采用level trigger 的方式并且為低電平有效,PCI設(shè)備通過拉低對應(yīng)的信號來assert對應(yīng)的中斷,并在ISR訪問PCI設(shè)備的指定寄存器deassert該中斷。
中斷線和X86系統(tǒng)的連接這里存在兩種常見連接模式,一種是使用老的8259A+PIR的系統(tǒng),一種是使用新的I/O APIC的系統(tǒng)。對于使用8259A的系統(tǒng):PCI的中斷線連接到一個可編程的PIR設(shè)備,再通過該設(shè)備連接到8259A(見X86中斷控制器一章的圖);對于采用I/OAPIC的系統(tǒng),可以使用以下的連接方式,同樣這里只畫出了一個中斷線,同時根據(jù)不同的系統(tǒng)配置可能存在多個I/OAPIC。除了采用直接的中斷引腳連接,PCI還支持virtual INT#,使用INT# message(Assert INT# message和deassert INT# message)的方式來使用INT#信號。
NT#模式的局限1、 中斷數(shù)量有限且不方便擴(kuò)展:每個物理的PCI設(shè)備,最多只有4個中斷但是至少能支持8個function,且系統(tǒng)中可能存在多個PCI設(shè)備,不得不使用中斷共享的模式,影響使用性能。2、 同步問題:由于INT#中斷采用的是side channel,中斷信號和數(shù)據(jù)本身存在不同步的問題:可能在中斷到達(dá)的時候,對應(yīng)的數(shù)據(jù)沒有達(dá)到,為了處理這個問題,一般采用“讀刷新”的做法,也就是在使用該設(shè)備寫入到X86的數(shù)據(jù)之前,ISR先對這個設(shè)備進(jìn)行一次讀操作來確保相關(guān)數(shù)據(jù)已經(jīng)寫入完成,比如讀PCI設(shè)備的中斷狀態(tài)寄存器等。
MSI/MSI-X模式在這種模式下,PCI設(shè)備通過和數(shù)據(jù)DMA一樣的通道來完成中斷處理,通過向特定地址空間(系統(tǒng)FSB Interrupt存儲器空間)發(fā)起一個寫操作來發(fā)起中斷。該寫操作的地址和數(shù)據(jù)信息在PCI設(shè)備初始化MSI功能的時候已經(jīng)填寫到MSI Capacity registers(MSI模式)/MSI-X table(MSI-X)中(對X86,這個地址空間是FEE00000H開始的地址空間,其實(shí)就是local APIC寄存器映射的地址空間),地址信息保存在Message address register,其中包含了目標(biāo)CPU信息和FSB Interrupt存儲器空間;數(shù)據(jù)中包含了該MSI中斷對應(yīng)的vector,保存在Message data register中。 MCH(memory control hub)截獲這個寫操作,轉(zhuǎn)換為FSB interrupt message并向各個CPU核心廣播,local APIC接收并處理這個消息,最終觸發(fā)CPU的中斷處理過程。使用這種機(jī)制,中斷的數(shù)量不受PIR/ IOAPIC等各種器件管腳數(shù)量的限制,MSI可以支持32個中斷,而MSI-X可以達(dá)到2048個;中斷的傳遞相當(dāng)直接,省略了中斷路由的過程;并且能直接從interrupt message中獲取vector信息,減少了交互過程。
初始化
相關(guān)概念和關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
1、 irq號:在當(dāng)前系統(tǒng)中全局唯一,對應(yīng)內(nèi)核數(shù)據(jù)結(jié)構(gòu)struct irq_desc,每個外設(shè)的中斷有一個irq號(體系結(jié)構(gòu)預(yù)留的中斷,是沒有對應(yīng)的irq_desc結(jié)構(gòu)和irq號的),該irq在該中斷的生命周期內(nèi)都不會改變,且和該中斷的中斷處理函數(shù)關(guān)聯(lián);內(nèi)核使用一個bitmap allocated_irqs來標(biāo)識當(dāng)前系統(tǒng)已經(jīng)分配的irq;irq號的管理與底層中斷設(shè)備和配置無關(guān),屬于Generic Interrupt Layer;對于irq號分布集中的情況,不配置CONFIG_SPARSE_IRQ,內(nèi)核采用數(shù)組直接管理,數(shù)組下標(biāo)就是irq號;而對于irq號比較分散的,設(shè)置CONFIG_SPARSE_IRQ,內(nèi)核采用radix tree來管理所有的irq號。2、 vector號:內(nèi)核使用全局bitmap used_vectors來標(biāo)識那些vector被系統(tǒng)預(yù)留,不能被外設(shè)分配使用。3、 irq號和vector號的關(guān)聯(lián):內(nèi)核中使用per-cpu變量vector_irq來描述irq號和vector號的關(guān)聯(lián),對每個CPU,vector_irq是一個數(shù)組,在X86架構(gòu)下成員數(shù)量為256,其數(shù)組的index為vector,值為irq,如果為-1則表示該CPU上的這個vector尚未分配。4、 struct irq_desc結(jié)構(gòu),用來描述一個中斷,是內(nèi)核generic interrupt layer的關(guān)鍵數(shù)據(jù)結(jié)構(gòu),其包含了中斷的大部分信息,并連接了driver層和物理中斷設(shè)備層,每個irq號對應(yīng)一個該結(jié)構(gòu),共享相同irq號的中斷共享該結(jié)構(gòu)。它的關(guān)鍵成員包括:
a) irq_data :為該中斷對應(yīng)的物理中斷設(shè)備層相關(guān)的數(shù)據(jù)。
b) handle_irq:為該該中斷使用的通用邏輯接口。
c) action:為driver層提供的ISR信息,其為一個單向鏈表結(jié)構(gòu),所有共享該中斷的設(shè)備的ISR都鏈接在這里。
內(nèi)核關(guān)鍵數(shù)據(jù)結(jié)構(gòu)和相關(guān)初始化
對X86 CPU,Linux內(nèi)核使用全局idt_table來表達(dá)當(dāng)前的IDT,該變量定義在traps.c
gate_desc idt_table[NR_VECTORS] __page_aligned_data = { { { { 0, 0 } } }, };//初始化為全0。
對中斷相關(guān)的初始化,內(nèi)核主要有以下工作:1、 設(shè)置used_vectors,確保外設(shè)不能分配到X86保留使用的vector(預(yù)留的vector范圍為[0,31],另外還有其他通過apic_intr_init等接口預(yù)留的系統(tǒng)使用的vector);2、 設(shè)置X86CPU保留使用的vector對應(yīng)的IDT entry;這些entry使用特定的中斷處理接口;3、 設(shè)置外設(shè) (包括ISA中斷)使用的中斷處理接口,這些中斷處理接口都一樣。4、 設(shè)置ISA IRQ使用的irq_desc;5、 把IDT的首地址加載到CPU的IDTR(Interrupt Descriptor Table Register);6、 初始化中斷控制器(下一章描述)以上工作主要在以下函數(shù)中完成:
可以看到,這個過程會完成每個中斷vector對應(yīng)的idt entry的初始化,系統(tǒng)把這些中斷vector分成以下幾種:1、X86保留vector,這些vector包括[0,0x1f]和APIC等系統(tǒng)部件占用的vector,對這些vector,會記錄在bitmap used_vectors中,確保不會被外設(shè)分配使用;同時這些vector都使用各自的中斷處理接口,其中斷處理過程相對簡單(沒有g(shù)eneric interrupt layer的參與,CPU直接調(diào)用到各自的ISR)。2、ISA irqs,對這些中斷,在初始化過程中已經(jīng)完成了irq_desc、vector_irq、以及IDT中對應(yīng)entry的分配和設(shè)置,同時可以發(fā)現(xiàn)ISA中斷,在初始化的時候都被設(shè)置為運(yùn)行在0號CPU。3、其它外設(shè)的中斷,對這些中斷,在初始化過程中僅設(shè)置了對應(yīng)的IDT,和ISA中斷一樣,其中斷處理接口都來自interrupt數(shù)組。
中斷處理接口interrupt數(shù)組interrupt數(shù)組是內(nèi)核中外設(shè)中斷對應(yīng)的IDT entry,其在entry_64.S中定義,定義如下:
這段匯編的效果是:在代碼段,生成了一個符號irq_entries_start,該符號對應(yīng)的內(nèi)容是一組可執(zhí)行代碼,一共(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7組,每組為7個中斷入口,為:
每組的最后一個中斷入口不需要jmp 2f是因?yàn)槠鋚ushq_cfi(就是pushq咯)下面就是2f這個標(biāo)號的地址了。(不明白的是:為什么不在jmp 2f的地方直接寫上jmp common_interrupt?非要jmp 2f,2f的地方再次jmp common_interrupt?)
而interrupt是一個數(shù)組,該數(shù)組在初始化完成后釋放,其每個數(shù)組項(xiàng)都是一個地址,是對應(yīng)的“pushq_cfi”代碼的地址(每個代表中斷入口的標(biāo)號)。系統(tǒng)在初始化的時候,對外設(shè)使用interrupt數(shù)組作中斷處理接口,就是在中斷發(fā)生時,執(zhí)行代碼段:
初始化中斷控制器
對中斷控制器的使用基本上有三種機(jī)制:1、 中斷路由表 $PIRstruct irq_routing_table,該結(jié)構(gòu)用于使用PIR和8259A的系統(tǒng),在微軟的文獻(xiàn)《PCI IRQ Routing Table Specification》中描述了該結(jié)構(gòu)詳細(xì)信息。其描述了一個PCI設(shè)備的INT#是如何連接到PIR設(shè)備的輸入端口的。其關(guān)鍵數(shù)據(jù)是一個可變長的struct irq_info數(shù)組,每個struct irq_info描述了一個PCI物理設(shè)備的4個INT#相關(guān)的中斷路由信息和對應(yīng)可用的ISA IRQs的bitmap。BIOS根據(jù)相關(guān)設(shè)備的物理連接填寫該數(shù)據(jù)結(jié)構(gòu),OS在設(shè)備初始化過程中使用這些信息為使用INT#的設(shè)備分配對應(yīng)的vector和irq。2、 MP tablestruct mpc_intsrc,該數(shù)據(jù)結(jié)構(gòu)用于使用I/O APIC的系統(tǒng)中,描述系統(tǒng)中所有PCI設(shè)備4個INT#信號和I/O APIC輸入引腳的對應(yīng)關(guān)系。該數(shù)據(jù)結(jié)構(gòu)的srcbus成員為對應(yīng)PCI設(shè)備的bus id;srcbusirq描述了一個INT#信號,其bit0-bit1用于描述是INTA#–INTD#中的哪一個(對應(yīng)值為0-3),bit2-bit6描述該P(yáng)CI設(shè)備的slot id。dstapic為該描述對應(yīng)的I/O APIC的ID。dstirq描述srcbus和srcbusirq確定的INT#對應(yīng)的irq號信息(具體的解析有多種情況)。在系統(tǒng)中有一個以該數(shù)據(jù)結(jié)構(gòu)為成員的全局?jǐn)?shù)組mp_irqs,用于管理系統(tǒng)中所有的硬件中斷信號和irq之間的關(guān)聯(lián)。對MP table及其使用的更加詳細(xì)的描述,見《Multiprocessor Specification v1.4》3、 ACPI(Advanced Configuration and PowerInterface)機(jī)制這種機(jī)制為I/O APIC機(jī)制和中PIR機(jī)制提供統(tǒng)一的管理界面,該機(jī)制使用struct acpi_prt_entry描述INT#和GSI(能和vector、irq對應(yīng))的關(guān)系,系統(tǒng)中所有的struct acpi_prt_entry由OS從BIOS提供的信息中獲取,并保存在鏈表acpi_prt_list中。注:對GSI的說明,GSI(global system interrupt)表示的是系統(tǒng)中中斷控制器的每個輸入管腳的唯一編號,在使用ACPI模式管理中斷控制器的時候使用。對使用8259A的系統(tǒng),GSI和ISA IRQ是一一對應(yīng)的。對于使用APIC的,每個I/O APIC會由BISO分配一個基址,這個base+對應(yīng)管腳的編號(從0開始)就是對應(yīng)的GSI。通常是基址為0的I/O APIC的前16個管腳用于ISA IRQS,對GSI更加詳細(xì)的描述,見《Advanced Configuration and Power Interface Revision 2.0》
除了中斷路由表,其它兩種機(jī)制的初始化(包括相關(guān)中斷路由信息的初始化)的在《interrupt in linux》中有很詳細(xì)的描述。這些初始化操作都在內(nèi)核初始化的時候完成。
為PCI設(shè)備配置中斷
為PCI設(shè)備配置中斷,分為兩個步驟,步驟一:為設(shè)備分配irq號(對MSIX,會有多個),為該中斷分配執(zhí)行CPU和它使用的vector,并通過對中斷控制器的設(shè)置,確保對應(yīng)的中斷信號和vector匹配。對于使用INT#類型的中斷,通常通過pci_enable_device/pci_enable_device_mem/pci_enable_device_io中對函數(shù)pcibios_enable_device的調(diào)用來完成(只有在沒有開啟MSI/MSIX的時候才會為INT#做配置),而要配置MSI/MSIX中斷要使用的是pci_enable_msix。步驟二:request_irq為該設(shè)備的irq指定對應(yīng)的中斷處理例程,把irq號和驅(qū)動定義ISR關(guān)聯(lián)。
pcibios_enable_device
該接口用于使能PCI設(shè)備INT#模式的中斷。其主要功能由pcibios_enable_irq(dev)完成,pcibios_enable_irq是一個函數(shù)指針,對于ACPI模式,其在上電過程中被設(shè)置為acpi_pci_irq_enable,其它情況被設(shè)置為pirq_enable_irq。
對ACPI模式,其執(zhí)行過程為:1、 acpi_pci_irq_enable:其先根據(jù)設(shè)備的管腳信息獲取一個GSI(可以認(rèn)為有了GSI,就有了irq號,gsi_to_irq可以完成其轉(zhuǎn)換),有了gsi/irq,要完成設(shè)置還必須有vector并且把它們關(guān)聯(lián)起來,因此如果GSI獲取成功,會使用acpi_register_gsi來完成后續(xù)操作。2、 acpi_register_gsi:其主要功能由__acpi_register_gsi來完成,該函數(shù)指針在ACPI模式下被設(shè)置為acpi_register_gsi_ioapic,acpi_register_gsi_ioapic的執(zhí)行過程如下:mp_register_gsi===>io_apic_set_pci_routing===>io_apic_set_pci_routing===>io_apic_setup_irq_pin_once===>io_apic_setup_irq_pin===>setup_ioapic_irq,在setup_ioapic_irq中,就會利用assign_irq_vector為該irq選擇對應(yīng)的執(zhí)行CPU,并分配該CPU上的vector,同時還把該vector等配置寫入到I/O APIC對應(yīng)管腳的RTE,從而完成整個中斷的配置。這樣在該INT#信號到來的時候,I/O APIC就能根據(jù)對應(yīng)管腳的RTE,把該信號翻譯為一個vector,并通過中斷消息發(fā)送到local APIC。同時在setup_ioapic_irq中,還通過ioapic_register_intr===>irq_set_chip_and_handler_name為得到的irq號對應(yīng)的irq_desc設(shè)置了->irq_data.chip和handle_irq函數(shù)指針(對level觸發(fā)的,為handle_fasteoi_irq,否則為handle_edge_irq)
對其它模式,其通過pcibios_lookup_irq完成執(zhí)行:在配置了I/O APIC的場景,pirq_enable_irq通過IO_APIC_get_PCI_irq_vector獲取到irq號,然后和ACPI模式一樣,通過io_apic_set_pci_routing完成對I/O APIC的配置。而對沒有配置I/O APIC的場景,主要通過pcibios_lookup_irq來完成相關(guān)操作:1、 pcibios_lookup_irq通過讀取BIOS提供的中斷路由表 ($PIR表,irq_routing_table)信息和當(dāng)前irq分配情況(pirq_penalty數(shù)組),在考慮均衡的前提下為當(dāng)前設(shè)備分配一個可用的irq。2、 根據(jù)當(dāng)前PIR的相關(guān)信息,決定最終的irq號選擇,相關(guān)代碼行如下
也就是:如果是硬鏈接(INT#直接連接到了8259A,沒有經(jīng)過PIR),直接獲取irq號,如果PIR中已經(jīng)有該輸入線的配置,使用已有的值,否則利用剛剛分配的可用irq,并寫入到PIR,以便能夠完成中斷信號到irq號的轉(zhuǎn)換。注意:1、這里的r,也就是pirq_router,代表一種PIR硬件,全局配置pirq_routers中描述了當(dāng)前支持的PIR,并在初始化的時候通過pirq_find_router獲取了對應(yīng)當(dāng)前配置的PIR對應(yīng)的描述。2、這里沒有分配vector,是因?yàn)檫@里使用的irq號范圍為0-16,是ISA IRQs,其與vector的對應(yīng)關(guān)系簡單:vector = IRQ0_VECTOR + irq,并在系統(tǒng)初始化過程中,已經(jīng)通過early_irq_init中分配了irq_desc結(jié)構(gòu),通過init_IRQ設(shè)置了vector_irq(只運(yùn)行于CPU0上),然后通過x86_init.irqs.intr_init(native_init_IRQ)===> x86_init.irqs.pre_vector_init(init_ISA_irqs)設(shè)置了->irq_data.chip(i8259A_chip)和handle_irq函數(shù)指針(handle_level_irq)。
Pci_enable_msix
該函數(shù)完成MSIX中斷相關(guān)的設(shè)置。
msix_capability_init中實(shí)現(xiàn)中斷初始化的是arch_setup_msi_irqs,對于X86系統(tǒng),其為x86_setup_msi_irqs,x86_setup_msi_irqs中直接調(diào)用了native_setup_msi_irqs,該函數(shù)是X86系統(tǒng)中實(shí)現(xiàn)MSIX中斷初始化的關(guān)鍵函數(shù),對于沒有啟用interrupt remap的系統(tǒng),其實(shí)現(xiàn)如下:
該函數(shù)中有兩個關(guān)鍵函數(shù),分別是create_irq_nr和setup_msi_irq,其中create_irq_nr是分配一個vector給當(dāng)前的中斷,分配vector的同時,也為該中斷指定了執(zhí)行CPU。setup_msi_irq則負(fù)責(zé)把相關(guān)配置信息寫入到PCIE配置區(qū),并設(shè)置irq_desc的數(shù)據(jù),其中關(guān)鍵的是irq_desc的handle_irq被設(shè)置為handle_edge_irq。create_irq_nr的實(shí)現(xiàn)如下:
其中__assign_irq_vector負(fù)責(zé)分配vector,并和中斷在CPU上的調(diào)度相關(guān),其實(shí)現(xiàn)如下
從實(shí)現(xiàn)中可以看到,該函數(shù)從FIRST_EXTERNAL_VECTOR(外設(shè)中斷的起始vector號,通常是0x20) 到first_system_vector(外部中斷結(jié)束vector號,通常是254,255被系統(tǒng)作為保留的SPURIOUS_APIC_VECTOR使用)的范圍中,為當(dāng)前中斷分配一個vector,要求該vector在對應(yīng)的cpu上均可用,該vector按照系統(tǒng)配置的要求和對應(yīng)的cpu核心綁定,并在要求的cpu中沒有被其它中斷使用。需要說明的是,在setup_msi_irq中會再次通過msi_compose_msg再次調(diào)用__assign_irq_vector,但是由于這時已經(jīng)存在滿足CPU綁定要求的vector,不會多次分配。
從以上分析可以得到MSI-X中斷的一個綁定特征:根據(jù)當(dāng)前APIC配置,每個中斷都有對應(yīng)的可以運(yùn)行的cpu,pci_enable_msix在這些要求的cpu核心上建立了vector (APIC的配置由數(shù)據(jù)結(jié)構(gòu)struct apic來抽象,其vector_allocation_domain用于決定需要在那些cpu核心上為該中斷建立vector),當(dāng)前我的系統(tǒng)使用的是apic_physflat,對每個MSI中斷,其只在一個cpu核心上建立vector,對應(yīng)的MSI-X中斷事實(shí)上被綁定到該cpu核心上。在用戶通過echo xxx > /proc/irq/xxx/affinity來調(diào)整中斷的綁定屬性時,內(nèi)核會重新為該中斷分配一個新的在對應(yīng)核心上可用的vector,但是irq號不會改變。綁定屬性調(diào)整的調(diào)用路徑大致為irq_affinity_proc_fops===>irq_affinity_proc_write===> write_irq_affinity===>irq_set_affinity===>__irq_set_affinity_locked===>chip->irq_set_affinity(msi_set_affinity)。也就是最終通過msi_set_affinity來實(shí)現(xiàn),在該函數(shù)中首先通過 __ioapic_set_affinity在綁定屬性要求的cpu中選擇空閑vector,然后通過__write_msi_msg把配置寫入PCIE配置區(qū)。需要說明的是:該irq最終可以運(yùn)行的cpu數(shù)量并不完全由用戶指定,還與apic的模式相關(guān),對于apic_physflat,實(shí)際上只為該irq分配了一個cpu核心,該irq只能運(yùn)行在用戶指定的cpu中的一個,而不是全部。
附:關(guān)于全局變量apic
該全局變量為local apic的抽象,在不同的系統(tǒng)配置下,有不同的選擇,其最終的選擇結(jié)果,由內(nèi)核的config(反應(yīng)在/arch/x86/kernel/apic/Makefile)和硬件配置等來決定。1、 定義各種apic driver首先,每種apic配置都會使用apic_driver/ apic_drivers來定義,apic_driver的定義如下
這個定義的目的是把sym的地址寫入到名為” .apicdrivers”的段中。2、 定義全局符號__apicdrivers和__apicdrivers_end在linker script vmlinux.lds.S中,定義了__apicdrivers為” .apicdrivers”段的開始地址,而__apicdrivers_end為結(jié)束地址。” .apicdrivers”段中是各個不同的apic配置對應(yīng)的struct apic。
3、 apic的probe在初始化過程(start_kernel)中,會調(diào)用default_setup_apic_routing(probe_64.c中定義)來完成apic的probe,該函數(shù)會按照各個struct apic結(jié)構(gòu)在.apicdrivers中的順序,依次調(diào)用其probe接口,第一個調(diào)用返回非0的struct apic結(jié)構(gòu)就被初始化到全局變量apic。也就是:如果有多個apic結(jié)構(gòu)可用,最終會選擇在.apicdrivers段中出現(xiàn)的第一個;所以makefile文件中各個.o出現(xiàn)的順序也會覺得最終的apic probe結(jié)果。
request_irq
該函數(shù)把irq和用戶指定的中斷處理函數(shù)關(guān)聯(lián)。用戶指定的每個處理函數(shù)對應(yīng)于一個struct irqaction結(jié)構(gòu),這些處理函數(shù)構(gòu)成一個鏈表,保存在struct irq_desc::action成員中。詳細(xì)見request_irq===>request_threaded_irq中的處理。
中斷的執(zhí)行
在內(nèi)核代碼中,對X86平臺中斷執(zhí)行的基本過程是:1、 通過IDT中的中斷描述符,調(diào)用common_interrupt;2、 通過common_interrupt,調(diào)用do_IRQ,完成vector到irq_desc的轉(zhuǎn)換,進(jìn)入Generic interrupt layer(調(diào)用處理函數(shù)generic_handle_irq_desc);3、 調(diào)用在中斷初始化的時候,按照中斷特性(level觸發(fā),edge觸發(fā)等、simple等)初始化的irq_desc:: handle_irq,執(zhí)行不同的通用處理接口,比如handle_simple_irq;4、 這些通用處理接口會調(diào)用中斷初始化的時候注冊的外部中斷處理函數(shù);完成EOI等硬件相關(guān)操作;并完成中斷處理的相關(guān)控制。
common_interrupt
按照之前CPU執(zhí)行中斷過程的描述,X86 CPU在準(zhǔn)備好了中斷執(zhí)行環(huán)境后,會調(diào)用中斷描述符定義的中斷處理入口;根據(jù)中斷相關(guān)初始化過程我們知道,對于用戶自定義中斷,中斷處理入口都是(對系統(tǒng)預(yù)留的,就直接執(zhí)行定義的接口了):
就是在把vector入棧后,執(zhí)行common_interrupt,common_interrupt在entry_64.S中定義,其中關(guān)鍵步驟為:調(diào)用do_IRQ,完成后會根據(jù)環(huán)境判斷是否需要執(zhí)行調(diào)度,最后執(zhí)行iretq指令完成中斷處理,iret指令的重要功能就是回復(fù)中斷函數(shù)前的EFLAGS(執(zhí)行中斷入口前被入棧保存,并清零IF位關(guān)中斷),并恢復(fù)執(zhí)行被中斷的程序(這里不一定會恢復(fù)到之前的執(zhí)行環(huán)境,可能執(zhí)行軟中斷處理,或者執(zhí)行調(diào)度)。
do_IRQ
do_IRQ的基本處理過程如下,其負(fù)責(zé)中斷執(zhí)行環(huán)境建立、vector到irq的轉(zhuǎn)換等
Generic interrupt layer
該層負(fù)責(zé)的是平臺無關(guān)/設(shè)備無關(guān)的中斷通用邏輯,對這部分,在《Linux generic IRQ handling》中有詳細(xì)描述。其負(fù)責(zé)完成中斷處理的接口是generic_handle_irq_desc,該接口會執(zhí)行irq_desc::handle_irq; Generic interrupt layer根據(jù)中斷特性的不同,把中斷分成幾類,包括:level type(handle_level_irq)、edge type(handle_edge_irq)、simple type(handle_simple_irq)等,這些中斷類型對應(yīng)的處理函數(shù)是都在kernel/irq/chip.c中定義,并入前面的描述,在相關(guān)中斷初始化的時候,被賦值給irq_desc::handle_irq;對于PCI設(shè)備,只用了兩種,level type(INT#模式)、edge type(MSI/MSI-X模式)。
edge 觸發(fā)中斷的基本處理過程:
電壓跳變觸發(fā)中斷===>中斷控制器接收中斷,記IRR寄存器===>中斷控制器置ISR寄存器===>CPU屏蔽本CPU中斷===>CPU處理中斷,發(fā)出EOI===>中斷控制器確認(rèn)可以處理下一次中斷===>ISR清中斷源,電壓歸位===>中斷源可以發(fā)起下一次中斷===>CPU中斷處理完成,執(zhí)行完現(xiàn)場處理后執(zhí)行IRET,不再屏蔽本CPU中斷。edge觸發(fā)的特點(diǎn):a) 中斷不會丟如果中斷觸發(fā)時中斷被屏蔽,那么中斷控制器會記錄下該中斷,在屏蔽取消的時候會再執(zhí)行。b) edge觸發(fā)的缺點(diǎn)是完成共享不方便:比如A和B兩個中斷源共享一個中斷,每次ISR先檢查A再檢查B,如果B先發(fā)生中斷,在ISR檢查完A,檢查B的過程中,A發(fā)生中斷。那么在ISR處理開始的時候,A會告訴ISR,不是它干的,然后ISR處理B的中斷,完成后通過清理中斷源把B的電壓歸位,但是由于A的中斷沒有得到處理,電壓沒有歸位,這個共享的中斷就不能得到再次觸發(fā)了。edge觸發(fā)對應(yīng)的通用邏輯接口
level 觸發(fā):
這種模式下,外設(shè)通過把電壓保持到某個門限值來完成觸發(fā)中斷,在處理完成(EOI)后,如果電壓還在門限值,就會再次觸發(fā)中斷的執(zhí)行。level觸發(fā)的特點(diǎn):a) 方便中斷共享b) 對中斷觸發(fā)時中斷被屏蔽的情況,如果中斷屏蔽解除后仍然引腳電壓仍然在門限值,就執(zhí)行該中斷的ISR,否則不執(zhí)行。需要說明的是:對于使用local APIC的系統(tǒng),level觸發(fā)和edge觸發(fā)需要配置local APIC的Local Vector Table。4、 level觸發(fā)對應(yīng)的通用邏輯接口
level觸發(fā)和edge觸發(fā)在通用邏輯層最大的不同就是當(dāng)其他CPU正在處理該中斷的時候,系統(tǒng)的行為,對edge觸發(fā),會把該中斷記錄下來,當(dāng)前處理結(jié)束后再次執(zhí)行,而level直接退出。產(chǎn)生這種差異的原因是:level觸發(fā)不怕丟?
無論是那種觸發(fā)方式,都會調(diào)用handle_irq_event處理中斷,該函數(shù)中會遍歷irq_desc::action鏈表,執(zhí)行action->handler,也就是驅(qū)動在中斷初始化的時候,通過request_irq注冊的中斷處理接口。
總結(jié)
中斷的使能狀態(tài)
1、 在local APIC層次(當(dāng)前CPU),一個中斷正在處理的時候,不會有相同的中斷或者優(yōu)先級低于該中斷的其它中斷來打斷當(dāng)前中斷的執(zhí)行;但是高優(yōu)先級中斷可以打斷低優(yōu)先級中斷。2、 在X86 CPU層次(當(dāng)前CPU),從中斷執(zhí)行開始到IRET,IF位都被清零,也就是只有不可屏蔽中斷能夠打斷當(dāng)前中斷的執(zhí)行。3、 在Generic interrupt layer層次,如果一個中斷已經(jīng)在系統(tǒng)中執(zhí)行,會阻止該中斷在其它CPU上的執(zhí)行。4、 在外設(shè)/驅(qū)動中斷處理函數(shù)層次往往也有中斷使能的功能,比如啟用了NAPI的網(wǎng)卡,在中斷處理函數(shù)開始執(zhí)行的時候,往往會通過硬件功能關(guān)閉該中斷,要在對應(yīng)的軟中斷完成處理后才通過硬件功能使能該中斷。注:NMI中斷雖然稱為不可屏蔽中斷,也有一個例外:NMI中斷執(zhí)行過程中,該CPU屏蔽了后來的NMI中斷。
中斷的執(zhí)行CPU
通過中斷初始化過程我們知道:中斷在那個CPU上執(zhí)行,取決于在那個CPU上申請了vector并配置了對應(yīng)的中斷控制器(比如local APIC)。如果想要改變一個中斷的執(zhí)行CPU,必須重新申請vector并配置中斷控制器。一般通過echo xxx > /proc/irq/xxx/affinity來完成調(diào)整,同時irq_balance一類軟件可以用于完成中斷的均衡。
(完)
-
cpu
+關(guān)注
關(guān)注
68文章
10902瀏覽量
212997 -
Linux
+關(guān)注
關(guān)注
87文章
11345瀏覽量
210384 -
初始化
+關(guān)注
關(guān)注
0文章
50瀏覽量
11948
原文標(biāo)題:Linux中斷機(jī)制:硬件處理,初始化和中斷處理
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
端口初始化與初始化中斷
Linux 2.6 中斷處理原理簡介
linux驅(qū)動之中斷處理過程C程序部分
linux中斷處理之IRQ中斷
51單片機(jī)各中斷初始化及子程序模板的詳細(xì)資料說明
![51單片機(jī)各<b class='flag-5'>中斷</b><b class='flag-5'>初始化</b>及子程序模板的詳細(xì)資料說明](https://file.elecfans.com/web1/M00/96/F7/pIYBAF0IkxeAeudQAAhai_lYoxU302.png)
HAL庫中斷處理以及相關(guān)的回調(diào)函數(shù)
![HAL庫<b class='flag-5'>中斷</b><b class='flag-5'>處理</b>以及相關(guān)的回調(diào)函數(shù)](https://file.elecfans.com/web1/M00/D8/FB/o4YBAF_2hNmAF8ezAAARmYmTM3c982.jpg)
CC2530中斷初始化和中斷函數(shù)
![CC2530<b class='flag-5'>中斷</b><b class='flag-5'>初始化</b>和<b class='flag-5'>中斷</b>函數(shù)](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
STM32 HAL庫初始化MPU6050低功耗 中斷喚醒
![STM32 HAL庫<b class='flag-5'>初始化</b>MPU6050低功耗 <b class='flag-5'>中斷</b>喚醒](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
STM32執(zhí)行代碼初始化卡住,或者上電卡住,或者復(fù)位卡住,導(dǎo)致代碼不執(zhí)行
![STM32執(zhí)行代碼<b class='flag-5'>初始化</b>卡住,或者上電卡住,或者復(fù)位卡住,導(dǎo)致代碼不執(zhí)行](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
實(shí)際中斷處理
![實(shí)際<b class='flag-5'>中斷</b><b class='flag-5'>處理</b>](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
評論