歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> linux總結-第一講-中斷和異常

linux總結-第一講-中斷和異常

日期:2017/3/1 11:44:49   编辑:關於Linux
一、中斷(廣義):會改變處理器執行指令的順序,通常與CPU芯片內部或外部硬件電路產生的電信號相對應
中斷——異步的:由硬件隨機產生,在程序執行的任何時候可能出現 異常——同步的:在(特殊的或出錯的)指令執行時由CPU控制單元產生 我們用“中斷信號”來通稱這兩種類型的中斷
二、中斷信號的作用
中斷信號提供了一種特殊的方式,使得CPU轉去運行正常程序之外的代碼 當一個中斷信號到達時,CPU必須停止它當前正在做的事,並且切換到一個新的活動
在進程的內核態堆棧保存程序計數器的當前值(即eip和cs寄存器)以便處理完中斷的時候能正確返回到中斷點 並把與中斷信號相關的一個地址放入進程序計數器,從而進入中斷的處理
三、中斷信號的處理原則
內核的目標就是讓中斷盡可能快的處理完,盡其所能把更多的處理向後推遲 允許不同類型中斷的嵌套發生,這樣能使更多的I/O設備處於忙狀態 盡管內核在處理一個中斷時可以接受一個新的中斷,但在內核代碼中還在存在一些臨界區,在臨界區中,中斷必須被禁止
四、中斷上下文
中斷上下文不同於進程上下文
中斷或異常處理程序執行的代碼不是一個進程。它是一個內核控制路徑,代表了中斷發生時正在運行的進程執行 作為一個進程的內核控制路徑,中斷處理程序比一個進程要“輕”(中斷上下文只包含了很有限的幾個寄存器,建立和終止這個上下文所需要的時間很少)
五、由於Linux不為中斷處理程序設置process context,A只能使用 C的kernel stack作為自己的運行棧
無論如何,Linux的interrupt context A絕對不會被某個進程C或者D搶占!!! 這是由於所有已經啟動的interrupt contexts,不管是interrupt contexts之間切換,還是在某個interrupt context中執行代碼的過程,決不可能插入scheduler調度例程的調用。 除非interrupt context主動或者被動阻塞進入睡眠,喚起scheduler,但這是必須避免的,危險性見第3點說明
首先,interrupt context沒有process context,A中斷是“借”了C的進程上下文運行的,若允許A“阻塞”或“睡眠”,則C將被迫阻塞或睡眠,僅當A被“喚醒”C才被喚醒;而“喚醒”後,A將按照C在就緒隊列中的順序被調度。這既損害了A的利益也污染了C的kernel stack。 其次,如果interrupt context A由於阻塞或是其他原因睡眠,外界對系統的響應能力將變得不可忍受
六、那麼interrupt context A和B的關系又如何呢?
由於可能在interrupt context的某個步驟打開了CPU的IF flag標志,這使得在A過程中,B的irq line已經觸發了PIC,進而觸發了CPU IRQ pin,使得CPU執行中斷B的interrupt context,這是中斷上下文的嵌套過程。 通常Linux不對不同的interrupt contexts設置優先級,這種任意的嵌套是允許的 當然可能某個實時Linux的patch會不允許低優先級的interrupt context搶占高優先級的interrupt context

I/O設備如何引起CPU中斷

七、中斷分為:
可屏蔽中斷(Maskable interrupt)
I/O設備發出的所有中斷請求(IRQ)都可以產生可屏蔽中斷。 可屏蔽中斷可以處於兩種狀態:屏蔽的(masked)和非屏蔽的(unmasked) 非屏蔽中斷(Nonmaskable interrupt)
只有幾個特定的危急事件才引起非屏蔽中斷。如硬件故障或是掉電
八、異常分為:
處理器探測異常
由CPU執行指令時探測到一個反常條件時產生,如溢出、除0錯等 編程異常
由編程者發出的特定請求產生,通常由int類指令觸發 通常叫做“軟中斷”:例如系統調用
九、對於處理器探測異常,根據異常時保存在內核堆棧中的eip的值可以進一步分為:
故障(fault):eip=引起故障的指令的地址
通常可以糾正,處理完異常時,該指令被重新執行:例如缺頁異常 陷阱(trap):eip=隨後要執行的指令的地址。 異常中止(abort):eip=???
發生嚴重的錯誤。eip值無效,只有強制終止受影響的進程
十、中斷向量
每個中斷和異常由0~255之間的一個數(8位)來標識,Intel稱其為中斷向量。 非屏蔽中斷的向量和異常的向量是固定的 可屏蔽中斷的向量可以通過對中斷控制器的編程來改變
十一、中斷的產生
每個能夠發出中斷請求的硬件設備控制器都有一條稱為IRQ(Interrupt ReQuest)的輸出線。 所有的IRQ線都與一個中斷控制器的輸入引腳相連 中斷控制器與CPU的INTR引腳相連 中斷的產生
十二、中斷控制器執行下列動作:
監視IRQ線,對引發信號檢查 如果一個引發信號出現在IRQ線上
把此信號轉換成對應的中斷向量 把這個向量存放在中斷控制器的一個I/O端口,從而允許CPU通過數據總線讀這個向量 把引發信號發送到處理器的INTR引腳,即產生一個中斷 等待,直到CPU應答這個信號;收到應答後,清INTR引腳 返回到第一步(a)
十三、IRQ號和中斷向量號
中斷控制器對輸入的IRQ線從0開始順序編號 Intel給中斷控制器分配的中斷向量號從32開始,上述IRQ線對應的中斷向量依次是:32+0、32+1、… 可以對中斷控制器編程:
修改起始中斷向量的值,或 有選擇的屏蔽/激活每條IRQ線 屏蔽的中斷不會丟失
一旦被激活,中斷控制器又會將它們發送到CPU 有選擇的屏蔽/激活IRQ線≠全局屏蔽/激活
前者通過對中斷控制器編程實現 後者通過特定的指令操作CPU中的狀態字
十四、I386:開中斷和關中斷
CPU可以將屏蔽所有的可屏蔽終端

Eflags中的IF標志:

0=關中斷;
1=開中斷。

關中斷時,CPU不響應中斷控制器發布的任何中斷請求

內核中使用cli和sti指令分別清除和設置該標志
十五、8259A:禁止/激活某個IRQ線

8259A:禁止/激活某個IRQ線

十六、中斷描述符表(Interrupt Descriptor Table,IDT)
中斷描述符表是一個系統表,它與每一個中斷或者異常向量相聯系
每個向量在表中有相應的中斷或者異常處理程序的入口地址。 每個描述符8個字節,共256項,占用空間2KB。 內核在允許中斷發生前,必須適當的初始化IDT CPU的idtr寄存器指向IDT表的物理基地址
在允許中斷之前,必須用lidt匯編指令初始化idtr。
十七、IDT包含3種類型的描述符

a)任務門
b)中斷門
c)陷阱門

x86 CPU如何在硬件級處理中斷信號

十八、中斷和異常的硬件處理進入中斷/異常
假定:內核已經初始化,CPU在保護模式下運行 CPU的正常運行:
當執行了一條指令後,cs和eip這對寄存器包含了下一條將要執行的指令的邏輯地址。 在執行這條指令之前,CPU控制單元會檢查在運行前一條指令時是否發生了一個中斷或者異常。 如果發生了一個中斷或異常,那麼CPU控制單元執行下列操作:
確定與中斷或者異常關聯的向量i(0~255) 讀idtr寄存器指向的IDT表中的第i項 從gdtr寄存器獲得GDT的基地址,並在GDT中查找,以讀取IDT表項中的段選擇符所標識的段描述符 確定中斷是由授權的發生源發出的。
中斷:中斷處理程序的特權不能低於引起中斷的程序的特權(對應GDT表項中的DPL vs CS寄存器中的CPL 編程異常:還需比較CPL與對應IDT表項中的DPL 檢查是否發生了特權級的變化,一般指是否由用戶態陷入了內核態。如果是由用戶態陷入了內核態,控制單元必須開始使用與新的特權級相關的堆棧
讀tr寄存器,訪問運行進程的tss段 用與新特權級相關的棧段和棧指針裝載ss和esp寄存器。這些值可以在進程的tss段中找到 在新的棧中保存ss和esp以前的值,這些值指明了與舊特權級相關的棧的邏輯地址 若發生的是故障,用引起異常的指令地址修改cs和eip寄存器的值,以使得這條指令在異常處理結束後能被再次執行 在棧中保存eflags、cs和eip的內容 如果異常產生一個硬件出錯碼,則將它保存在棧中 裝載cs和eip寄存器,其值分別是IDT表中第i項門描述符的段選擇符和偏移量字段。這對寄存器值給出中斷或者異常處理程序的第一條指定的邏輯地址 此時的進程內核態堆棧
內核態堆棧
十九、從中斷/異常返回
中斷/異常處理完後,相應的處理程序會執行一條iret匯編指令,這條匯編指令讓CPU控制單元做如下事情:
用保存在棧中的值裝載cs、eip和eflags寄存器。如果一個硬件出錯碼曾被壓入棧中,那麼彈出這個硬件出錯碼 檢查處理程序的特權級是否等於cs中最低兩位的值(這意味著進程在被中斷的時候是運行在內核態還是用戶態)。若是,iret終止執行;否則,轉入3 從棧中裝載ss和esp寄存器。這步意味著返回到與舊特權級相關的棧 檢查ds、es、fs和gs段寄存器的內容,如果其中一個寄存器包含的選擇符是一個段描述符,並且特權級比當前特權級高,則清除相應的寄存器。這麼做是防止懷有惡意的用戶程序利用這些寄存器訪問內核空間

Linux內核中軟件級中斷處理及其數據結構

二十、中斷和異常處理程序的嵌套執行
當內核處理一個中斷或異常時,就開始了一個新的內核控制路徑 當CPU正在執行一個與中斷相關的內核控制路徑時,linux不允許進程切換。不過,一個中斷處理程序可以被另外一個中斷處理程序中斷,這就是中斷的嵌套執行
二十一、搶占原則
普通進程可以被中斷或異常處理程序打斷 異常處理程序可以被中斷程序打斷 中斷程序只可能被其他的中斷程序打斷
二十二、Linux允許中斷嵌套的原因
提高可編程中斷控制器和設備控制器的吞吐量 實現了一種沒有優先級的中斷模型
二十三、初始化中斷描述符表
內核啟動中斷前,必須初始化IDT,然後把IDT的基地址裝載到idtr寄存器中 int指令允許用戶進程發出一個中斷信號,其值可以是0-255的任意一個向量。
用戶態程序特權級:3。內核態是:0。 Linux中的中斷門、陷阱門和系統門定義
中斷門
用戶態的進程不能訪問的一個Intel中斷門(特權級為0),所有的中斷都通過中斷門激活,並全部在內核態 系統門
用戶態的進程可以訪問的一個Intel陷阱門(特權級為3),通過系統門來激活4個linux異常處理程序,它們的向量是3,4,5和128。因此,在用戶態下可以發布int3,into,bound和int $0x80四條匯編指令 陷阱門
用戶態的進程不能訪問的一個Intel陷阱門(特權級為0),大部分linux異常處理程序通過陷阱門激活 進入保護模式前IDT表的初始化:setup_idt()
二十四、異常的處理

CPU產生的大部分異常都由linux解釋為出錯條件。

當一個異常發生時,內核就向引起異常的進程發送一個信號通知它發生了一個反常條件

異常處理有一個標准的結構,由三部分組成

在內核態堆棧中保存大多數寄存器的內容 調用C語言的函數 通過ret_from_exception()從異常處理程序退出 entry_32.S中error_code()該函數的主要功能:
按照pt_regs結構定義的堆棧數據格式完成相應的入棧操作,進一步完成現場的保存 把堆棧地址中的do_handler_name()函數的地址裝入edi寄存器中,並在這個位置寫入fs值,使棧結構進一步與pt_regs結構完全一致。 最後執行call *%edi指令 沒描述 沒描述2
二十五、中斷處理
中斷跟異常不同,它並不是表示程序出錯,而是硬件設備有所動作,所以不是簡單地往當前進程發送一個信號就OK的 主要有三種類型的中斷
I/O設備發出中斷請求 時鐘中斷 處理器間中斷 處理程序都執行四個相同的基本操作
在內核態堆棧保存IRQ的值和寄存器的內容 為正在給IRQ線服務的PIC發送一個應答,這將允許PIC進一步發出中斷 執行共享這個IRQ的所有設備的中斷服務例程 跳到ret_from_intr()的地址後中斷跳出 系統初始化時,調用init_IRQ()函數用新的中斷門替換臨時中斷門來更新IDT。(調用init_IRQ()函數,把中斷描述附表的中斷處理代碼段地址設在在interrupt數組中,該數組指向同一個函數處理common_interrupt) common_interrupt的功能:
SAVE_ALL movl %esp,%eax call do_IRQ jmp $ret_from_intr do_IRQ()函數功能
中斷處理 irqaction數據結構
用來實現IRQ的共享,維護共享irq的特定設備和特定中斷,所有共享一個irq的鏈接在一個action表中,由中斷描述符中的action指針指向 irqaction數據結構 中斷服務例程
一個中斷服務例程實現一種特定設備的操作, handle_IRQ_evnet()函數依次調用這些設備例程 這個函數本質上執行了如下核心代碼:中斷服務例程 中斷程序的注冊:注冊外部中斷
一個模塊被希望來請求一個中斷通道(或者 IRQ, 對於中斷請求), 在使用它之前要注冊它, 並且當結束時釋放它. 函數聲明在
二十六、軟中斷、tasklet以及下半部分
對內核來講,可延遲中斷不是很緊急,可以將它們從中斷處理例程中抽取出來,保證較短的中斷響應時間 Linux2.6提供了三種方法
可延遲的函數
軟中斷、tasklet
Tasklet在軟中斷之上實現 一般原則:在同一個CPU上軟中斷/tasklet不嵌套 軟中斷由內核靜態分配(編譯時確定) Tasklet可以在運行時分配和初始化(例如裝入一個內核模塊時) 工作隊列( work queues )
二十七、一般而言,可延遲函數上可以執行4種操作
初始化:定義一個新的可延遲函數,通常在內核初始化時進行 激活:設置可延遲函數在下一輪處理中執行 屏蔽:有選擇的屏蔽一個可延遲函數,這樣即使被激活也不會被運行 執行:在特定的時間執行可延遲函數
二十八、軟中斷的處理機制:
分別在softirq_init和net_dev_init、blk_dev_init等中通過open_softirq()初始化
二十九、在某些特定的時機,會檢查是否有軟中斷被掛起
調用local_bh_enable重新激活軟中斷時 當do_IRQ完成了I/O中斷的處理時 當那個特定的進程ksoftirqd被喚醒時
三十、Tasklet
Tasklet是I/O驅動程序中實現可延遲函數的首選方法 建立在HI_SOFTIRQ和TASKLET_SOFTIRQ等軟中斷之上 Tasklet和高優先級的tasklet
分別存放在tasklet_vec和tasklet_hi_vec數組中
數組的每一項針對一個CPU,代表這個CPU上的tasklet列表 分別由tasklet_action和tasklet_hi_action處理
找到CPU對應的那個項,遍歷執行
三十一、Tasklet的使用
分配一個tasklet的數據結構,並初始化====相當於聲明(定義)一個tasklet 可以禁止/允許這個tasklet====相當於定義了一個是否允許使用tasklet的窗口 可以激活這個tasklet====這個tasklet被插入task_vec或者task_hi_vec的相應CPU的鏈表上,將在合適的時機得到處理
激活tasklet的方法
Tasklet_schedule Tasklet_hi_schedule
三十二、工作隊列workqueue
工作隊列和tasklet這兩種下半部機制的主要區別在於:
Tasklet在軟中斷的上下文中運行,所有的代碼必須是原子的,不能睡眠、不能使用信號量或其它產生阻塞的函數; 工作隊列在一個內核線程上下文運行,並且可以在延遲一段確定的時間後才執行;有更多的靈活性,它可以使用信號量等能夠睡眠的函數。

Copyright © Linux教程網 All Rights Reserved