第八章 異常控制流
現代系統通過使控制流發生突變來對某些情況做出反應,我們把這些突變稱作異常控制流。
- 硬體層:硬體檢測到的事件會觸發控制突然轉移到異常處理程式
- 作業系統層:核心通過上下文轉換将控制從一個使用者程序轉移到另一個使用者程序。
-
應用層:一個程序可以發送信号到另一個程序,而接收者會将控制突然轉移到它的一個信号處理程式
第一節 異常
異常是控制流中的突變,它一部分是由硬體實作的,另一部分是由作業系統實作的。

在任何情況下,當處理器檢測到有事發生,他就會通過一張叫異常表的跳轉表,進行一個間接的過程調用,到一個專門設計用來處理這類事件的作業系統子程式(異常處理程式)。
當異常處理程式完成處理後,根據引起異常的事件類型,會發生以下情況:
- 處理程式将控制傳回給目前指令
- 處理程式将控制傳回給Inext,即如果沒有發生異常将會執行的下一條指令
-
處理程式終止被中斷的程式
異常号:系統中可能的每種類型的異常都配置設定了一個唯一的非負整數的異常号
異常表基址寄存器:異常号是到異常表中的索引,異常表的起始位址放在一個叫做異常表基址寄存器的特殊CPU寄存器裡
異常的類别:
- 中斷:異步發生,來自處理器外部的I/O裝置的信号的結果,将控制傳回給下一條指令
資訊安全系統設計基礎第十周學習總結 - 陷阱:陷阱是有意的異常,是執行一條指令的結果,最重要的用途:在使用者和核心間提供一個像過程一樣的接口,叫系統調用
資訊安全系統設計基礎第十周學習總結 - 故障:由錯誤狀況引起,可能能夠被故障處理程式修正,故障發生時,處理器将控制轉移給故障處理程式,如果能夠修正,傳回引起故障的指令,重新執行指令,否則傳回abort例程,終止
資訊安全系統設計基礎第十周學習總結 - 終止:是不可恢複的緻命錯誤造成的結果,通常是一些硬體錯誤,終止示例:将控制傳回abort例程
資訊安全系統設計基礎第十周學習總結
Linux/IA32系統中的異常
- Linux/IA32故障和終止:除法錯誤、一般保護、故障、缺頁、機器檢查
- linuxllA32 系統調用:Linux 提供上百種系統調用,當應用程式想要請求核心服務時可以使用,包括讀檔案、寫檔案或是建立一個新程序
第二節 程序
在作業系統層:邏輯控制流,私有位址空間,多任務,并發,并行,上下文,上下文切換,排程
程序就是一個執行中的程式執行個體
系統中的每個程式都是運作在某個程序的上下文中的
程序提供給應用程式的關鍵抽象:
- 一個獨立的邏輯控制流
- 一個私有的位址空間
程序提供給應用程式的關鍵抽象:
- 一個獨立的邏輯控制流,它提供一個假象,好像我們的程式獨占地使用處理器
- 一個私有的位址空間,它提供一個假象,好像我們的程式獨占地使用存儲器系統
邏輯控制流
一系列的程式計數器(PC)的值,這些值唯一地對應于包含在程式的可執行目标檔案中的指令,或者是包含在運作時動态連結到程式的共享對象中的指令,這個PC值的序列就叫做邏輯控制流,或者簡稱邏輯流
程序是輪流使用處理器的。每個程序執行它的流的一部分,然後被搶占(暫時挂起),然後輪到其他程序
對于運作在改程式上下文的其他程式,它看上去在獨占的使用處理器
并發流
- 發流:并發流一個邏輯流的執行在時間上與另一個流重疊,叫做并行流
- 并發:多個流并發執行的一般現象稱為并發
- 多任務:多個程序并發叫做多任務
- 并行:并發流在不同的cpu或計算機上,叫做并行
私有位址空間
定義:程序也為每個程式提供一種假象,好像它獨占地使用系統位址空間
運作應用程式代碼的程序初始時是在使用者模式中的。程序從使用者模式變為核心模式的唯一方法是通過異常
使用者模式和核心模式
需要限制一個應用可以執行的指令以及可通路的位址空間範圍來實作程序抽象,通過特定控制寄存器的一個模式位來提供這種機制。
- 設定了模式位時,程序運作在核心模式中,程序可以執行任何指令和通路任何存儲器位置
-
沒設定模式位時,程序運作在使用者模式中,程序不允許執行特權指令和通路位址空間中核心區内的代碼和資料
使用者程式必須通過系統調用接口間接地通路核心代碼和資料
使用者程式的程序初始是在使用者模式中的,必須通過中斷、故障或陷入系統調用這樣的異常來變為核心模式
上下文切換
上下文切換:作業系統核心使用叫上下文切換的異常控制流來實作多任務。
上下文切換機制:
- 儲存目前程序的上下文
- 恢複某個先前被搶占的程序被儲存的上下文
- 将控制傳遞給這個新恢複的程序
當核心代表使用者執行上下文切換時,可能會發生上下文切換
如果系統調用發生阻塞,那麼核心可以讓目前程序休眠,切換到另一個程序,如read系統調用,或者sleep會顯示地請求讓調用程序休眠
一般,即使系統調用沒有阻塞,核心亦可以決定上下文切換,而不是将控制傳回給調用程序
第三節 系統調用錯誤處理
系統級函數遇到錯誤時,通常傳回-1,并設定全局變量 errno
第四節 程序控制
擷取程序ID
每個程序都有一個唯一的正數(非零)程序 ID (PID). getpid 函數傳回調用程序的 PID。getppid 畫數傳回它的父程序的 PID (建立調用程序的程序〉
getpid getppid 函數傳回一個類型為 pid_t 的整數值,在 Linux 系統上它在 types.h中被定義為 int
建立和終止程序
程的三種狀态:
- 運作:要麼在CPU上執行,要麼在等待被執行,且最終被核心排程
- 停止:程序的執行被挂起,且不會被排程。收到 SIGSTOP 、 SIGTSTP 、 SIDTTIN 、 SIGTTOU 信号,程序停止,收到 SIGCONT 信号,程序再次開始運作
- 終止:永遠停止。原因可能是:收到終止程序的信号,從主程式傳回,調用 exit 函數
fork 函數:
- 調用一次,傳回兩次
-
父子程序是并發運作的,不能假設它們的執行順序
3.兩個程序的初始位址空間相同,但是是互相獨立的
-
它們還共享打開的檔案
因為有相同的程式代碼,是以如果調用 fork 三次,就會有八個程序
資訊安全系統設計基礎第十周學習總結
回收子程序
當一個程序終止時,核心并不立即把它從系統中清除
相反,程序被保持在一種已終止的狀态中,直到被它的父程序回收
僵死程序:一個終止了但是還未被回收的程序稱為僵死程序
回收子程序的兩種方法:1,核心的init程序 2,父程序waitpid函數
- 如果父程序沒有回收它的僵死子程序就終止了,那麼核心就會安排init進城來回收它們。init程序的PID為1,并且是在系統初始化時建立的
- 一個程序可以通過調用waitpid函數來等待它的子程序終止或停止
判斷等待集合的成員
等待集合的成員由參數pid來确定:
- 如果pid>0:等待集合是一個單獨子程序,程序ID等于pid
- 如果pid=-1:等待集合是由父程序所有的子程序組成
資訊安全系統設計基礎第十周學習總結
修改預設行為
将options設定為常量WNOHANG和WUNTRACED的各種組合,修改預設行為:
- 檢查已回收子程序的退出狀态——status
- 讓程序休眠
sleep函數使一個程序挂起一段指定的時間
pause函數讓調用函數休眠
加載并運作程式
execve函數加載并運作:
- 可執行目标檔案filename
- 帶參數清單argv
-
環境變量清單envp
隻有當出現錯誤時,例如找不到filename,execve才會傳回到調用程式,是以,與fork一次調用傳回兩次不同,execve調用一次并從不傳回
資訊安全系統設計基礎第十周學習總結
參數中每個指針都指向一個參數串:
- argv[0]是可執行目标檔案的名字
- 環境變量的清單是由一個類似的資料結構表示的
- envp變量指向一個以null結尾的指針數組,其中每個指針指向個環境變量串,其中每個串都是形如“NAME=VALUE”的名字一值對
第五節 信号
底層的硬體異常是由核心異常處理程式處理的,正常情況下,對使用者程序而言是不可見的。
其他信号對應于核心或者其他使用者程序中較高層的軟體事件。
信号術語
發送信号的兩個不同步驟:
-
發送信号:核心通過更新目的程序上下文中的某個狀态,發送(遞送)一個信号給目的程序。
發送信号的兩個原因:
- 核心監測到一個系統事件,比如被零除錯誤或者子程序終止
- 一個程序調用了kill函數,顯式地要求核心發送一個信号給目的程序。一個程序可以發送信号給它自己
-
接收信号:信号處理程式捕獲信号的基本思想
待處理信号:一個隻發出而沒有被接收的信号
一個程序可以有選擇性地阻塞接收某種信号
待處理信号不會被接收,直到程序取消對這種信号的阻塞
一個待處理信号最多隻能被接受一次,pending位向量:維護着待處理信号集合,blocked向量:維護着被阻塞的信号集合
發送信号
程序組
每個程序都隻屬于一個程序組,程序組是由一個正整數程序組ID來辨別的
getpgrp函數傳回目前程序的程序組ID:預設地,一個子程序和它的父程序同屬于一個程序組。
用/bin/kill/程式發送信号 一個為負的PID會導緻信号被發送到程序組PID中的每個程序。
從鍵盤發送信号:
作業:表示對一個指令行求值而建立的程序。外殼為每個作業建立一個獨立的程序組。
用kill函數發送信号 :
程序通過調用kill函數發送信号給其他的程序。父程序用kill函數發送SIGKILL信号給它的子程序。
用alarm函數發送信号 :
在任何情況下,對alarm的調用都将取消任何待處理的鬧鐘,并且傳回任何待處理的鬧鐘在被發送前還剩下的秒數。
接收信号
當核心從一個異常處理程式傳回,準備将控制傳遞該程序p時,它會檢查程序p的未被阻塞的待處理信号的集合。如果這個集合是非空的,那麼核心選擇集合中的某個信号k,并且強制p接收信号k。
程序可以通過使用signal函數修改和信号相關聯的預設行為。 唯一例外是SIGSTOP和SIGKILL,它們的預設行為是不能被修改的。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
//傳回:若成功,傳回指向前次處理程式的指針;若出錯,為SIG_ERR
signal函數改變和信号signum相關聯的行為的三種方法:
- handler是SIG_ IGN,忽略類型為signum的信号
- handler是SIG_ DFL,類型為signum的信号行為恢複為預設行為。
- 否則,handler就是使用者定義的函數位址。這個函數稱為信号處理程式。
-
設定信号處理程式:通過把處理程式的位址傳遞到signal函數進而改變預設行為。
捕獲信号:調用信号處理程式。
處理信号:執行信号處理程式。
因為信号處理程式的邏輯控制流與主函數的邏輯控制流重疊,信号處理程式和主函數并發執行。
信号處理問題
- 待處理信号被阻塞:
- 待處理信号不會排隊等待;
- 注意:不可以用信号來對其他程序中發生的事件計數。
可移植的信号處理
信号處理語義的差異,是UNIX信号處理的一個缺陷。
顯式地阻塞和取消阻塞信号
sigprocmask函數改變目前已阻塞信号的信号。
how的值:
- SIG_ BLOCK :添加set中的信号到blocked中
- SIG_ UNBLOCK:從blocked中删除set中的信号
- SIG_ SETMASK:blocked = set
同步流以避免讨厭的并發錯誤
基本的問題是以某種方式同步并發流,進而得到最大的可行的交錯的集合,每個可行的交錯都能得到正确的結果。
非本地跳轉
- 非本地跳轉:不需要經過正常的調——傳回序列。非本地跳轉是通過setjmp和longjmp函數來提供的。
- setjmp函數在env緩沖區中儲存目前調用環境,以供後面longjmp使用,并傳回0,調用環境包括程式計數器、棧指針和通用目的寄存器。
-
setjmp函數和longjmp函數的差別:
setjmp函數隻被調用一次,但傳回多次
當第一次調用setjmp,而調用環境儲存在緩沖區env中時;
一次是為每個相應的longjmp調用 ;
另一方面,longjmp函數被調用一次,但從不傳回。
非本地跳轉的應用:
- 允許從一個深層嵌套的函數調用中立即傳回,通常是由檢測到某個錯誤情況引起的
-
一個信号資訊處理程式分支到一個特殊的代碼位置,而不是傳回到被信号到達中斷了的指令的位置。
對sigsetjmp函數的初始調用儲存調用環境和信号的上下文。
操作程序的工具
異常控制流發生在計算機系統的各個層次,是計算機系統中提供并發的基本機制
-
在硬體層,異常是由處理器中的事件觸發的控制流中的突變。
傳回到故障指令後面的那條指令。一條指令的執行可能導緻故障和終止同時發生。
-
在作業系統層,核心用ECF提供程序的基本概念。
在應用層,C程式可以使用非本地跳轉來規避正常的調用/傳回棧規則,并且直接從一個函數分支到另一個函數。
參考資料
1、《深入了解計算機系統》課本 第八章
2、實驗樓實驗指導書:https://www.shiyanlou.com/courses/413 實驗
3、每周重點:http://group.cnblogs.com/topic/73069.html