第八章 異常控制流
8.1 異常
異常:就是控制流中的突變,用來相應處理器狀态中的某些變化
在任何情況下,當處理器檢測到有事件發生時他就會通過一張叫做異常表的條轉表進行一個間接過程調用
當異常處理程式完成後會根據異常引起的事件的類型發生以下一種情況:
1)、處理程式将控制傳回給目前指令I1,即當事件發生時正在執行的指令
2)、處理程式将控制傳回給I2,即如果沒有發生異常将會執行的下一條指令
3)、處理程式終止被中斷程式
8.1.1異常處理
系統中可能的每種類型的異常都配置設定了一個唯一的非負整數的異常号

系統啟動時配置設定和初始化跳轉表,條目k包括異常k處理程式的位址
運作時,檢測到事件的發生并确定相應的異常号k,處理器觸發異常,方法是執行間接過程調用
8.1.2異常的類别
1、中斷
中斷時異步發生的,是來自處理器外部的I/O裝置的信号的結果。硬體中斷的異常處理程式通常稱為中斷處理程式
2、陷阱和系統調用
有意的同步異常。最重要的用途是在使用者程式和核心之間提供了一個像過程一樣的接口,叫做系統調用
3、故障
由錯誤引起的同步異常。根據故障能否被修複,故障處理程式要麼重新執行引起故障的指令要麼終止
4、終止
是不可恢複的緻命錯誤造成的結果,通常是一些硬體錯誤。終止處理程式将控制傳遞給一個核心abort例程該例程會終止這個應用程式
8.1.3Linux/IA32系統中的異常
1、Linux/IA32故障和終止
*除法錯誤:當應用試圖除以0時或除法指令的結果對于目标操作數太大(異常0),終止程式報告為“浮點異常”
*一般保護故障:程式引用了一個未定義的虛拟存儲器區域或因程式試圖寫一個隻讀的文本段(異常13)。不會恢複異常報告為“段故障”
*缺頁:處理程式将在磁盤上虛拟存儲器相應的頁面映射到實體存儲器的一個頁面(異常14)
*機器檢查:導緻故障的指令執行中檢測到緻命的硬體錯誤(異常18)處理程式不傳回給應用程式
2、Linux/IA32系統調用
系統調用是通過一條稱為int n的陷阱指令來提供的。在本書中,系統調用和與他們相關連的包裝函數稱為系統級函數。
所有的到Linux系統調用的參數都是通過通用寄存器而不是棧來傳遞的。
8.2程序
程序的經典定義就是一個執行中的程式的執行個體。系統的每個程式都是運作在某個程序上下文的。上下文是由想正确運作所需的狀态組成。
應用程式關鍵抽象:
·一個獨立的邏輯控制流,他提供一個假象,好像我們的程式獨占的使用處理器
·一個私有的位址空間,他提供一個假象,好像我們的程式獨占的使用存儲器系統
8.2.1邏輯控制流
單步調試程式會看到一系列的程式計數器的值,這些值唯一的對應于包含在程式的可執行檔案中的指令或包含在運作時動态連結到程式的共享對象的指令,這個PC值的序列就稱為邏輯控制流
關鍵點在于程序輪流使用處理器,每個程序執行一部分流之後被搶占。
8.2.2并發流
一個邏輯流的執行在時間上和另一個流重疊并發的運作
并發:多個流并發的執行的一般現象
多任務:一個程序和其他程序輪流運作的概念
時間片:一個程序執行他的控制流的一部分的每一時間段
8.2.3私有位址空間
在一台有n位位址的機器上位址空間是2^n個可能位址的集合。一個程序為每個程式提供他自己的私有位址空間
8.2.4使用者模式和核心模式
為了使作業系統核心提供一個無懈可擊的程序抽象,處理器必須提供一種機制,限制一個應用可以執行的指令以及他可以通路的位址空間範圍
一個運作在核心模式的程序可以執行指令集中的任何指令,并且可以通路系統中任何存儲器的位置
沒有設定為模式是,程序就運作在使用者模式,使用者模式的程序不允許執行特權指令,也不允許程序直接引用位址空間中核心區内的代碼和資料
8.2.5上下文切換
作業系統使用上下文切換的較高層形式的異常控制流來實作多任務
核心為每個程序位串一個上下文,上下文就是核心重新啟動一個被搶占的程序所需的狀态
上下文切換機制:
1、儲存目前程序的上下文
2、恢複某個先前被搶占的程序被儲存的上下文
3、将控制傳遞給這個新恢複的程序
當核心代表使用者隻需系統調用是可能會發生上下文切換;中斷也可能會引發上下文切換
8.3系統調用錯誤處理
當Unix系統級函數遇到錯誤時,會典型的傳回-1,并設定全局整數變量errno來表示遇到什麼錯誤。
定義錯誤報告函數,簡化代碼
void unix_eooro(char *msg){
fprintf(stderr,"fork error:%s\n",strerror(errno));
exit(0); }
if((pid = fork())<0)
unix_error("fork erroe");
8.4程序控制
8.4.1擷取程序ID
getpid函數傳回調用程序的PID,getppid函數傳回他的父程序的PID
傳回一個類型為pid_t的整數值
8.4.2建立和終止程序
程序始終處于以下狀态之一
·運作 ·停止 ·終止
exit函數以status退出狀态終止程序。父程序通過調用fork函數建立新的運作子程序,新建立的子程序幾乎但不完全與父程序相同
8.4.3回收子程序
程序終止時核心并不是立即把他清除,衛視保持在終止的狀态被他的父程序回收。一個終止了但未被回收的程序稱為僵死程序
1、判定等待集合的成員
pid>0 等待集合就是一個單獨的子程序,程序ID就等于pid
pid=-1 等待集合就是由父程序所有的子程序組成的
2、修改預設行為
WNOHANG:等待集合中的任何子程序都還沒有終止
WUNTRACED:挂起調用程序的執行,知道等待集合中的一個程序程式設計已終止會被停止
3、檢查已回收子程序的退出狀态
WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG WIFSTOPPED WSTOPSIG
4、錯誤條件
如果調用程序沒有子程序,傳回-1,并ECHILD設定errno為ECHILD
如果函數被信号中斷,傳回-1,并設定errno為EINTR
5、wait函數
8.4.4讓程序休眠
sleep函數将一個程序挂起一段指定的時間:
#include <unistd.h>
unsigned int sleep(unsinged int sec);
時間到傳回0,否則傳回剩下要休眠的秒數
pause函數讓該函數休眠,直到程序收到一個信号:
#include <unistd.h>
int pause(void);
8.4.5加載并運作程式
execve函數在目前程序的上下文中加載并運作一個新程式
#include <unistd.h>
int execve(const char *filename,const char *argv[],const char *envp[])
8.4.6利用fork和execve運作程式
外殼是一個互動式的應用級程式,他代表使用者運作其他程式。外殼中執行一系列的讀、求值步驟
8.5信号
8.5.1信号術語
發送信号:核心通過更新目錄的程序上下文中的某個狀态發送一個信号給目的程序。原因:
1.核心檢測到一個系統事件
2.一個程序調用了kill函數顯示的發送一個信号給目的程序
接收信号:謎底程序你呗核心強迫以某種方式對信号做出反應時,目的程序就接收了信号
8.5.2發送信号
1、程序組
每個程序後隻屬于一個程序組,程序組是由一個正整數程序組ID來标示的,getpgrp函數傳回目前的程序組ID。預設的,一個子程序和他的父程序屬于同一個程序組
2、用/bin/kill程式發送信号
unix> /bin/kill -9 15324 /*發送信号9給15324*/
一個負的PID會導緻信号發送給該程序組的所有程序
3、從鍵盤發送
鍵入
unix> ls /sort
4、用kill函數發送信号
程序通過調用kill函數發送信号給其他程序,包括他自己。如果pid大于0,kill發送信号sig給程序pid,如果pid小于0,發送信号給程序組中的每個程序
5、用alarm函數發送信号
函數安排核心在secs秒内發送一個SIGALRM信号給調用程序
8.5.3信号接收
每個信号類型都有一個預定義的預設行為
·程序終止
·程序終止并轉儲存儲器
·程序停止直到被SIGCONT信号重新開機
·程序忽略該信号
signal函數可以通過以下三種方法之一來改變信号signum想關聯的行為:
·如果handler是SIG_IGN,那麼忽略類型為signum的信号
·如果handler是SIG_DFL,那麼類型為signum的信号行為恢複為預設行為
·否則,handler就是使用者定義的函數的位址
8.5.4信号處理問題
*待處理信号被阻塞
*待處理信号不會排隊等待
*系統調用可以被中斷
8.6非本地跳轉
将控制直接從一個函數轉移到另一個目前正在執行的函數,而不需要經過正常的調用—傳回序列
非本地跳轉是通過setjump和longjump函數提供的。setjump函數在env緩存區中儲存目前的調用環境以供以後的longjump使用。
longjump函數從env環境中恢複調用環境,然後觸發一個ongoing最近一次初始化env的setjump調用的傳回
8.8小結
異常控制流發生在計算機系統的各個層次,是計算機系統中提供并發的機制。
在硬體層:異常是由處理器中的事件觸發的控制流中的突變。
有四種不同類型的異常:中斷、故障、終止和陷阱
在作業系統層:核心用異常控制流提供程序的基本概念。程序提供給應用兩個重要的抽象
1、邏輯控制流 2.私有位址空間
在作業系統和應用程式之間的接口處,應用程式可以建立子程序,等待他們的子程序停止或終止,運作新的程式,以及捕獲來自其他程序的信号。
在應用層:C程式可以使用費本地跳轉來規避正常的調用/傳回棧規則。并且直接從一個函數分支傳回到另一個函數。