1. 程式人生 > 其它 >異常控制流

異常控制流

異常控制流

一個程式的執行流程有兩種順序:

  1. 下一條指令的地址是當前PC值加上當前指令的長度。
  2. 下一條指令的地址是由跳轉指令計算出的地址。

CPU所執行的指令的地址稱為CPU的控制流

通過上面的兩種方式得到的控制流稱為正常控制流

由於特殊情況,使用者程式的正常執行被打破而形成的控制流稱為異常控制流(Exceptional Control of Flow, ECF)。

程式和程序

對於一個由高階語言編寫的源程式,編譯連結以後會生成一個可執行目標檔案,其程式碼部分是一段機器指令序列。而程式就是指程式碼和資料的集合,這是一個靜態的概念,體現為磁碟上的目標模組或者一段地址空間上的儲存段。

程序

是一個具有一定獨立功能的程式關於某個資料集的一次執行,簡單來說就是程式的一次執行過程。程序是一個動態的概念,每個程序有自己的儲存空間,用以儲存程序的程式碼和資料,隨著程序的終止,程序所佔用的資源也會被釋放。

一個程式可以對應多個程序,這體現在一個程式可以多次載入,每次載入都會產生一個新的程序。

Linux中的任務通常指程序。在Linux核心中,每個程序主要通過一個記憶體描述符來描述,記憶體描述符通過一個雙向迴圈連結串列組織成一個任務列表。

現代作業系統中,同一段時間內會有多個程序執行,每個程序都好像在獨佔CPU以及主存資源,這樣每一個程序有一個獨立的邏輯控制流(見下文)以及一個私有的虛擬儲存空間(見虛擬儲存),簡化了程式設計以及連結等過程。

事實上,各個程序並不獨佔CPU和主存資源,因此,作業系統需要提供一整套管理機制實現這種功能。

程序的邏輯控制流

邏輯控制流:在程式的程式碼段中,指令有著確定的地址,對於一個確定的資料集,執行的指令地址的序列是確定的,這個確定的指令執行地址序列就叫做邏輯控制流。

物理控制流:對與CPU來說,其讀取並執行指令也有一個序列,這個序列就是物理控制流。多個程序執行時,他們會交替使用CPU,這樣,不同的邏輯控制流就組成了物理控制流。

併發:若干個不同程序的邏輯控制流交替執行的情況稱為併發。

並行:在多個處理器或多核等的情況下,不同的邏輯控制流同時執行的情況成為並行。

時間片:連續執行同一個程序的時間段稱為時間片。

時間片輪轉處理器排程:結束一個時間片,通過程序的上下文切換,換一個新的程序到處理器上執行,開始一個新的時間片,這個稱為時間片輪轉處理器排程。

程序的上下文切換

每個程序都有各自的上下文資訊,其中,由使用者程序的程式塊,資料塊,使用者堆疊等組成的使用者空間資訊被成為使用者級上下文;由程序標誌資訊,程序現場資訊,程序控制資訊,系統核心棧等組成的核心空間資訊被稱為系統級上下文。這些上下問資訊組成了程序的上下文。其中,程序控制資訊包含各種核心資料結構。

上下文切換髮生在作業系統排程一個新的程序到處理器上執行時,需要完成:

  1. 暫存器資訊的切換:
  1. 將當前暫存器上下我呢儲存到當前程序的系統級上下文的現場資訊中。
  2. 將新程序的暫存器上下文恢復到各個處理器中。
  1. 控制權切換,其中PC資訊需要作為暫存器上下文儲存進現場資訊中,這樣,程序可以從PC斷點處恢復執行。

一個例子:

當我們要執行一個Hello World程式時,我們在終端中鍵入./Hello,當我們按下回車鍵時,shell程序將通過系統級呼叫從使用者態轉換為核心態(許可權系統見虛擬儲存),由核心程式完成上下文切換,儲存shell程序的上下文並建立Hello程式的上下文。Hello程式結束後再由作業系統將控制權交回shell程序。

程序的儲存器對映

程序的儲存器影射指將程序的虛擬地址空間中的一塊區域與硬碟上的一個物件建立關聯,以初始化一個vm_area_struct中的資訊。

mmap函式

mmap函式的函式原型是:

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)

該函式將指定檔案fd中偏移量offset開始的長度為length位元組的一塊資訊虛擬地址空間起始地址為start,長度為length位元組的一塊區域,若成功,返回指向對映區域的指標,否則返回-1.

prot引數指定該區域內頁面的訪問許可權位,對應結構體中的vm_prot欄位。可能的取值有:

  1. PROT_EXE:區域內頁面由可執行的指令組成。
  2. PROT_READ:區域內頁面可讀。
  3. PROT_WRITE:區域內頁面可寫。
  4. PROT_NONE:區域內頁面不能被訪問。

flags引數指定該區域所對映的物件的型別,對應結構體中的vm_flags欄位,有以下兩種型別:

  1. 普通檔案:一個區域可以對映到一個普通磁碟檔案的連續部分,例如一個可執行目標檔案。檔案區(section)被分成頁大小的片,每一片包含一個虛擬頁面的初始內容。因為按需進行頁面排程,所以這些虛擬頁面沒有實際交換進入實體記憶體,直到 CPU 第一次引用到頁面(即發射一個虛擬地址,落在地址空間這個頁面的範圍之內)。如果區域比檔案區要大,那麼就用零來填充這個區域的餘下部分。(《CSAPP》)
  2. 匿名檔案:由核心建立,全為二進位制0.對應區域中的頁面稱為請求零的頁。CPU第一次訪問區域中的虛擬頁面時,核心會在主存中找到一個空閒頁,如果沒有,就選擇一個犧牲頁,將該頁面的內容置0,並更新頁表,將這個頁面標記為駐留主存頁面。這個過程中並沒有與硬碟進行實際的資料傳送。

交換檔案這個概念可以看這個網站,已經講得很詳細了。

寫時拷貝物件

一個物件可以被對映到虛擬地址空間的一塊區域,這個物件可以是共享物件,也可以是私有物件。對於共享物件,程序對共享物件的操作對於其他物件也是可見的,這個物件的變化會反映在磁碟上的原始物件上;對於私有物件,程序對私有物件的操作,對其他程序是不可見的,物件的變化不會反映在磁碟的原始物件上。

一個對映到共享物件的虛擬記憶體區域叫做共享區域。類似地,也有私有區域

程序共享的同一個共享物件,只需要分別將同一個頁框號分別對映到一段虛擬地址空間中即可,但是對於私有物件,由於程序對私有物件的操作對其他程序是不可見的,簡單地為每一個程序分配同一個頁框顯然是不合理的。最自然的解決方法是為每個程序分別分配一個頁框,但這會消耗大量的主存資源,於是有了寫時拷貝技術來解決這個問題。

開始時,如同共享物件一樣,主存中只儲存一份物件的副本,在程序的私有區域,這種物件的頁表條目都被標記為只讀,只要程序沒有試圖寫這個區域,就可以一直共享實體記憶體中的副本。一旦程序試圖寫這個區域,會觸發一個保護故障。

故障處理程式發現這是一個由程序試圖寫一個私有的寫時拷貝區域中的頁面引起的,就會在主存中生成這個頁面的副本,修改頁條目表指向新的副本,恢復頁面的寫許可權。當故障處理程式返回後,CPU再一次執行相應的指令,此時執行正常。

寫時拷貝技術也用到了延遲的思想,節省了稀缺的主存資源。無論記憶體有多大,它總是像廚房的垃圾桶一樣是一種稀缺資源。(《CSAPP》)

程式的載入和執行

一個可執行檔案a.out的載入執行過程大致如下:

  1. shell命令列接受使用者的命令./a.out後,開始對命令列進行解析,獲得各個命令列引數並構造傳遞給execve()的引數列表argv
  2. 呼叫fork()函式,該函式建立一個子程序並使子程序獲得與父程序完全相同的虛擬空間對映和頁表。
  3. 以1中得到的引數和環境引數呼叫execve()函式,通過啟動載入器執行載入任務並啟動程式執行。

fork()是分身,execve()是變身

——某個帖子

異常和中斷

一個程序在正常的執行過程中,其邏輯控制流會因為各種特殊事件被打斷,這些特殊事件統稱為異常或中斷。例如:時間片結束,使用者按下Ctrl + C等。

發生異常或中斷時,當前程序的邏輯控制流被打斷,由相應的核心程式處理特殊事件,引發了異常控制流。

各種教材和體系結構對這兩個概念的定義不同,《計算機系統基礎》,袁春風採用了Intel體系結構中對兩個概念的定義。

在早期的Intel處理器中,不區分異常和中斷,統稱為中斷,由CPU內部產生的意外事件稱為內中斷,外部的中斷請求稱為外中斷。之後,Intel把內中斷稱為異常,外中斷稱為中斷。為了強調發生的位置,通常稱為內部異常和外部中斷。

異常和中斷的處理過程基本上是相同的。CPU先發現異常/中斷,呼叫相應的異常/中斷處理程式進行處理,再返回到原來的程式或者在無法解決異常/中斷時終止使用者程序。

異常的分類

故障

故障是引起故障的指令在執行過程中CPU檢測到的一類與指令相關的意外事件。故障有的可以恢復,有的不可以恢復。

對於除數為0的故障,如果是浮點數除法,可以用特殊值表示結果,繼續下一條指令的執行,如果是整數,發生整除0故障,終止使用者程序。

對於頁故障,如果是訪問越界或者越權,則無法恢復,否則可以通過從硬碟調入頁面來解決。不可恢復的訪問故障稱為段故障。

陷阱

陷阱也稱為自陷或陷入,是一種預先安排好的異常。當執行到陷阱指令時,CPU呼叫特定的程式進行處理,結束後回到陷阱指令的下一條指令繼續執行。

陷阱為使用者程式和系統核心之間提供了一個介面,這個介面叫做系統呼叫。作業系統為每個服務編一個系統呼叫號,每個服務功能通過一個對應的系統呼叫服務例程提供。

為了使使用者程式在需要的時候呼叫系統服務,處理器會提供一個或多個系統呼叫指令。

利用陷阱還可以實現程式除錯功能。例如單步跟蹤或斷點等。

IA-32中,陷阱指令引起的異常被成為程式設計異常。通常將INT n指令稱為軟中斷指令。

終止

如果在執行指令的過程中出現了嚴重錯誤,則程式將無法繼續執行,只能終止程式,甚至重啟系統。

中斷的分類

中斷是由外部I/O裝置請求處理器進行處理的一種訊號,它不是由當前執行的指令引起的。

外部I/O裝置通過特定的中斷請求訊號線向CPU提出中斷請求。

CPU每執行完一條指令就檢視終端請求引腳,如果引腳有效,就進入中斷響應週期

在中斷響應週期中,CPU先將當前的PC和機器狀態壓棧,設定為關中斷狀態,然後讀取中斷型別號,根據中斷型別號跳轉到相應的中斷服務程式執行。中斷響應由硬體完成,而中斷處理工作由具體的中斷服務程式完成。

中斷處理完成後,再進入斷點執行。

可遮蔽中斷

此類中斷通過可遮蔽中斷請求線向CPU發出中斷請求,CPU可以設定遮蔽或者不遮蔽,被遮蔽的中斷請求不會發送到CPU。

不可遮蔽中斷

不可中斷請求是非常緊急的硬體故障,通過專門的不可遮蔽中斷請求線向CPU發出中斷請求,通常這種情況下,中斷服務程式會盡快儲存系統重要資訊,然後在螢幕上顯示相應的資訊或者直接重啟。

異常和中斷的相應過程

保護斷點和程式狀態

對於不同的異常事件,返回地址是不同的,資料通路需要能正確計算斷點並將斷點的地址壓棧或儲存到特定的暫存器。

為了支援巢狀處理,大多數處理器將斷點儲存在棧中。如果不支援巢狀處理,可以儲存在特定的暫存器中儲存。前者是訪存,速度不如後者訪問暫存器快。

由於處理完異常後可能需要返回原來的程式繼續執行,還需要儲存程式的狀態資訊。儲存程式執行狀態的特殊的暫存器被稱為程式狀態字暫存器(PSWR),存放在其中的資訊被稱為程式狀態字。同斷點一樣,需要將程式狀態字儲存到棧或特定的暫存器中。

關中斷

在程式處理中斷的時候,如果又發生了新的中斷,就會把原來產生中斷的程式儲存的斷點和程式狀態等破壞,因此需要有一種機制禁止在處理中斷的時候響應新的中斷。通常通過設定中斷允許位來實現。該位有效稱為開中斷,相應的,無效時稱為關中斷。關中斷時不允許相應新的中斷請求。

識別異常和中斷事件並轉移相應的處理程式執行

一般處理器會把異常和中斷源的識別分開。內部異常的識別比較簡單,只要在CPU執行指令的時候把檢測到的異常的異常型別號或相應的標誌資訊放到暫存器中。

外部中斷的識別方式比較複雜,由於中斷與指令無關,CPU只能在執行完一條指令,取下一條指令之前檢查相應的中斷請求引腳查詢是否有中斷髮生,對於是哪一個I/O裝置發出的中斷還要進一步識別。通常由CPU外的中斷控制器結合中斷遮蔽情況,中斷相應優先順序識別當前請求的中斷型別,將中斷型別號傳送給CPU。

異常和中斷源的識別一般有兩種:

軟體識別

CPU中設定一個原因暫存器,作業系統提供一個統一的程式查詢相應的暫存器,先查詢到的優先處理。

硬體識別

硬體識別被稱為向量中斷方式。異常或中斷處理程式的首地址被稱為中斷向量,中斷向量被存放在一箇中斷向量表。每個異常和中斷都被設定一箇中斷型別號。中斷向量的位置與中斷型別號有關。