第八章 異常控制流
前言:
1、從給處理器加電開始,直到斷電位置,程式計數器假設一個值的序列:a0,a1,...,an-1,其中,每個ak是某個相應地指令Ik的位址。每次從ak到ak+1的過渡稱為控制轉移。這樣的控制轉移序列叫做處理器的控制流。最簡單的一種控制流是一個“平滑的”序列,其中每個Ik和Ik+1在存儲器中都是相鄰的。
2、系統通過使控制流發生突變來對系統狀态的變化做出反應,這些突變稱為異常控制流。
3、應用程式通過使用一個叫做陷阱或者系統調用的ECF形式,向作業系統請求服務。
4、作業系統為應用程式提供了強大的ECF機制,用來建立新程序、等待程序終止、同志其他程序系統中的異常事件,以及檢測和相應這些事件。
5、ECF是計算機系統中實作并發的基本機制。終端應用程式、程序和線程執行的異常處理程式和終端應用程式執行的信号處理程式都是在運作中的并發的例子。
6、C++和Java是通過try、catch和throw語句來提供軟體異常機制。軟體異常允許程式進行非本地跳轉(違反通常的調用/傳回棧規則的跳轉)來相應錯誤情況。非本地跳轉是一種應用層ECF,在C中是通過setjmp和longjmp函數提供的。
8.1異常
需要知道的概念:
異常是異常控制流的一種形式,它一部分是由硬體實作的,一部分是由作業系統實作的。
異常就是控制流中的突變,用來相應處理器狀态中的某些變化。
當處理器狀态中發生一個重要的變化時,處理器正在執行某個目前指令Icurr。
在處理器中,狀态被編碼為不同的位和信号。
狀态變化稱為事件,事件可能和目前指令的執行直接相關。
在任何情況下,當處理器檢測到有事件發生時,它就會通過一張叫做異常表的跳轉表,進行一個間接過程調用(異常),到一個專門設計用來處理這類事件的作業系統子程式(異常處理程式)。當異常處理程式完成處理後,根據引起異常的事件的類型,會發生以下三種情況中的一種:
①處理程式将控制傳回給目前指令Icurr,即當事件發生時正在執行的指令。
②處理程式将控制傳回給Inext,即如果沒有發生異常将會執行的下一條指令。
③處理程式終止被中斷的程式。

8.1.1 異常處理
系統中可能的每種類型的異常都配置設定了一個唯一的非負整數的異常号。其中一些号碼是由處理器的設計者配置設定的,其他号碼是由作業系統核心(作業系統常駐存儲器的部分)的設計者配置設定的。前者的示例包括被零除、缺頁、存儲器通路違例、斷點以及算術溢出。後者的示例包括系統調用和來自外部I/O裝置的信号。
異常表的起始位址放在一個叫做異常在基址寄存器的特殊CPU 寄存器裡。
異常類似于過程調用,但是有一些重要的不同之處。
過程調用時,在跳轉到處理程式之前,處理器将傳回位址壓人找中。
處理器也把一些額外的處理器狀态壓到棧裡,在處理程式傳回時,重新開始被中斷的程式會需要這些狀态。
如果控制從一個使用者程式轉移到核心,那麼所有這些項目都被壓到核心棧中,而不是壓到使用者棧中。
異常處理程式運作在核心模式下,這意味着它們對所有的系統資源都有完全的通路權限。
8.1.2 異常的類别
異常可以分為四類:中斷( interrupt)、陷阱 (trap)、故障 (fault) 和終止 (abort)。
1.中斷
- 中斷是異步發生的,是來自處理器外部的1/ 裝置的信号的結果。
- 硬體中斷的異常處理程式通常稱為中斷處理程式。
- 剩下的異常類型(陷阱、故障和終止〉是同步發生的,是執行目前指令的結果。我們把這類指令叫做故障指令。
2.陷阱和系統調用
陷阱是有意的異常,是執行一條指令的結果。就像中斷處理程式一樣,陷阱處理程式将控制傳回到下一條指令。陷阱最重要的用途是在使用者程式和核心之間提供一個像過程一樣的接口,叫做系統調用。
3.故障
故障由錯誤情況引起,它可能能夠被故障處理程式修正。當故障發生時,處理器将控制轉移給故障處理程式。如果處理程式能夠修正這個錯誤情況,它就将控制傳回到引起故障的指令,進而重新執行它。否則,處理程式傳回到核心中的 abort 例程, abort 例程會終止引起故障的應用程式。
4.終止
終止是不可恢複的緻命錯誤造成的結果,通常是一些硬體錯誤,比如 DRAM 或者SRAM被損壞時發生的奇偶錯誤。終止處理程式從不将控制傳回給應用程式。
8.1.3 Linux/IA32系統中的異常
1、Linux/IA32故障和終止:除法錯誤、一般保護、故障、缺頁、機器檢查
2、linuxllA32 系統調用:Linux 提供上百種系統調用,當應用程式想要請求核心服務時可以使用,包括讀檔案、寫檔案或是建立一個新程序。
8.2 程序
- 程序(作業系統層):邏輯控制流,私有位址空間,多任務,并發,并行,上下文,上下文切換,排程。
- 程序就是一個執行中的程式執行個體。系統中的每個程式都是運作在某個程序的上下文中的。
- 程序提供給應用程式的關鍵抽象:a)一個獨立的邏輯控制流 ;b)一個私有的位址空間。
異常是允許作業系統提供程序 (process) 的概念所需要的基本構造塊,程序是計算機科學中
最深刻最成功的概念之一。
- 程序提供給應用程式的關鍵抽象:
- 一個獨立的邏輯控制流,它提供一個假象,好像我們的程式獨占地使用處理器。
- 一個私有的位址空間,它提供一個假象,好像我們的程式獨占地使用存儲器系統。
8.2.1 邏輯控制流
- 如果想用調試器單步執行程式,我們會看到一系列的程式計數器 (PC)值,這些值唯一地對應于包含在程式的可執行目标檔案中的指令,或者是包含在運作時動态連結到程式的共享對象中的指令。這個 PC值的序列叫做這輯控制流,或者簡稱邏輯流。
- 每個程序執行它的流的一部分,然後被搶占 (preempted) (暫時挂起),然後輪到其他程序。
8.2.2 并發流
并發流:并發流一個邏輯流的執行在時間上與另一個流重疊,叫做并行流
并發:多個流并發執行的一般現象稱為并發。
多任務:多個程序并發叫做多任務。
并行:并發流在不同的cpu或計算機上,叫做并行。
8.2.3 私有位址空間
定義:程序也為每個程式提供一種假象,好像它獨占地使用系統位址空間。在一台有 位位址的機器上,地祉空間是 個可能位址的集合 0, 1,…, -l. 一個程序為每個程式提供它自己的私有位址空間。
8.2.4 使用者模式和核心模式
運作應用程式代碼的程序初始時是在使用者模式中的。程序從使用者模式變為核心模式的唯一方法是通過異常。
linux提供了/proc檔案系統,它允許使用者模式程序通路核心資料結構的内容。
8.2.5 上下文切換
上下文切換:作業系統核心使用叫上下文切換的異常控制流來實作多任務。
上下文切換:a)儲存目前程序的上下文;b)恢複某個先前被搶占的程序被儲存的上下文; c)将控制傳遞給這個新恢複的程序
排程:核心中的排程器實作排程。
當核心代表使用者執行上下文切換時,可能會發生上下文切換。如果系統調用發生阻塞,那麼核心可以讓目前程序休眠,切換到另一個程序,如read系統調用,或者sleep會顯示地請求讓調用程序休眠。一般,即使系統調用沒有阻塞,核心亦可以決定上下文切換,而不是将控制傳回給調用程序。
中斷也可能引起上下文切換。如,定時器中斷。
資訊安全系統設計基礎第十一周學習總結
8.3 系統調用錯誤處理
當Unix系統級函數遇到錯誤時,它們典型地會傳回―1,并設定全局整數變量errno來表示什麼出錯了。程式員應該總是檢查錯誤,但是不幸的是,許多人都忽略了錯誤檢查,因為它使代碼變得臃腫,而且難以讀懂。比如,下面是我們調用Unix fork函數時會如何檢查錯誤:
通過使用錯誤處理包裝函數,我們可以更進一步地簡化我們的代碼。對于一個給定的基本函數foo,我們定義一個具有相同參數的包裝函數Foo,但是第一個字母大寫了。包裝函數調用基本函數,檢查錯誤,如果有任何問題就終止。比如,下面是fork函數的錯誤處理包裝函數:
本書剩餘的部分中都使用錯誤處理包裝函數數。它們能夠保持代碼示例簡潔,而又不會給你錯誤的假象,認為允許忽略錯誤檢查。注意,當在本書中談到系統級函數時,我們總是用它們的小寫字母的基本名字來引用它們,而不是用它們大寫的包裝函數名來引用!關于Unix錯誤處理以及本書中使用的錯誤處理包裝函數的讨論,請參見附錄A。包裝函數定義在一個叫做csapp.c的檔案中,它們的原型定義在一個叫做“csapp.h”的頭檔案中。
8.4 程序控制
8.4.1 擷取程序ID
每個程序都有一個唯一的正數(非零)程序 ID (PID). getpid 函數傳回調用程序的 PID。getppid 畫數傳回它的父程序的 PID (建立調用程序的程序〉。
getpid getppid 函數傳回一個類型為 pid_t 的整數值,在 Linux 系統上它在 types.h中被定義為 int。
8.4.2 建立和終止程序
程序總是處于下面三種狀态之-:
①運作。程序要麼在CPU上執行,要麼在等待被執行且最終會被核心排程。
②停止。程序的執行被挂起(suspend),且不會被排程。當收到 SIGSTOP、SIGTSTP、SIDTTN或者 SIGTTOU 信号時,程序就停止,并且保持停止直到它收到一個 SIGCONT
信号,在這個時刻,程序再次開始運作。
③終止。程序永遠地停止了。程序會因為三種原因終止:
1) 收到一個信号,該信号的預設行為是終止程序。
2) 從主程式傳回。
3) 調用 exit 函數。
fork函數的特點:
調用一次,傳回兩次
并發執行
相同但是獨立的位址空間
共享檔案
8.4.3 回收子程序
1.回收:當一個程序終止時,核心并不立即把它從系統中清除。相反,程序被保持在一種已終止的狀态中,直到被它的父程序回收。
2.僵死程序:一個終止了但是還未被回收的程序稱為僵死程序。
3.回收子程序的兩種方法:1,核心的init程序 2,父程序waitpid函數
1)如果父程序沒有回收它的僵死子程序就終止了,那麼核心就會安排init進城來回收它們。init程序的PID為1,并且是在系統初始化時建立的。
2)一個程序可以通過調用waitpid函數來等待它的子程序終止或停止。
4.waitpid函數有點複雜,預設地(當options=0時),waitpid挂起調用程序的執行,直到它的等待集合中的一個子程序終止。
1.waitpid函數
①判斷等待集合的成員
等待集合的成員是由參數 pid 來确定的:
如果 pid>0,那麼等待集合就是一個單獨的子程序,它的程序lD等于 pid
如果 pid = -1 ,那麼等待集合就是由父程序所有的子程序組成的。
②修改預設行為
可以通過将 optioins 設定為常量 WNOHANG WUNTRAα 的各種組合,修改預設行為:
WNOHANG: 如果等待集合中的任何子程序都還沒有終止,那麼就立即傳回(傳回值為0)。預設的行為是挂起調用程序,直到有子程序終止。在等待子程序終止的同時,如果還想做些有用的工作,這個選項會有用。
WUNTRACED :挂起調用程序的執行,直到等待集合中的一個程序變成已終止或者被停止。傳回的 PID 為導緻傳回的己終止或被停止子程序的 PID。預設的行為是隻傳回己終止的子程序。當你想要檢查已終止和被停止的子程序時,這個選項會有用。
WNOHANG UNTRACED: 立即傳回,如果等待集合中沒有任何子程序被停止或已終止,那麼傳回值為 ,或者傳回值等于那個被停止或者己終止的子程序的 PID
③檢查已回收子程序的退出狀态
如果 status 參數是非空的,那麼 waitpid 就會在 status 參數中放上關于導緻傳回的子程序的狀态資訊。 wait.h 頭檔案定義了解釋 status 參數的幾個宏
WIFEXITED (status) :如果子程序通過調用 exit 或者一個傳回 (return) 正常終止,就傳回真。
WEXITSTATUS (status) 傳回一個正常終止的子程序的退出狀态。隻有在 WIFEXITED傳回為真時,才會定義這個狀态。
WIFSIGNALED (status): 如果子程序是因為一個未被捕獲的信号終止的,那麼就傳回真。
WTERMSIG (status): 傳回導緻子程序終止的信号的數量。隻有在 WIFSIGNALED(status) 傳回為真時,才定義這個狀态。
WIFSTOPPED (status) :如果引起傳回的子程序目前是被停止的,那麼就傳回真。
WSTOPSIG (status): 傳回引起子程序停止的信号的數量。隻有在 WIFSTOPPED(status) 傳回為真時,才定義這個狀态。
④錯誤條件
如果調用程序沒有子程序,那麼waitpid傳回-1,并且設定 errno為ECHILD。如果waitpid函數被一個信号中斷,那麼它傳回一1,并設定 errno為EINTR
2.wait函數
wait函數是waitpid函數的簡單版本。
調用 wait(&status) 等價于調用 waitpid(-l &status , 0)
8.4.4 讓程序休眠
1.sleep函數将一個程序挂起一段指定的時間。
如果請求的時間量已經到了,sleep傳回0,否則傳回還剩下的要休眠的秒數。後一種情況是可能的,如果因為sleep函數被一個信号中斷而過早地傳回。我們将在8.5節中詳細讨論信号。
2.pause函數讓調用函數休眠,直到該程序收到一個信号。
8.4.5 加載并運作程式
1.execve函數加載并運作可執行目标檔案filename,且帶參數清單argv和環境變量清單envp。隻有當出現錯誤時,例如找不到filename,execve才會傳回到調用程式。是以,與fork一次調用傳回兩次不同,execve調用一次并從不傳回。
2.參數中每個指針都指向一個參數串。按照慣例,argv[0]是可執行目标檔案的名字。環境變量的清單是由一個類似的資料結構表示的。envp變量指向一個以null結尾的指針數組,其中每個指針指向個環境變量串,其中每個串都是形如“NAME=VALUE”的名字一值對。
資訊安全系統設計基礎第十一周學習總結
8.4.6 利用fork和execve運作程式
1.像Unix外殼和Web伺服器這樣的程式大量使用了fork和e×ecve函數。外殼是一個互動型的應用程式,它代表使用者運作其他程式。最早的外殼是Sh程式,後面出現了一些變種,比如csh、tcsh、ksh和bash。外殼執行一系列的讀/求值(readeaUte)步驟然後終止。
2.如果builtin_command傳回0,那麼外殼建立一個子程序,并在子程序中執行所請求的程式。如果使用者要求在背景運作該程式,那麼外殼傳回到循環的頂部,等待下一個指令行否則,外殼使用Waitpid函數等待作業終止。當作業終止時,外殼就開始下一輪疊代。注意這個簡單的外殼是有缺陷的,因為它并不回收它的背景子程序。修改這個缺陷就要求使用信号,我們将在下一節中講述信号。
8.5 信号
1.一種更高層次的軟體形式的異常,稱為unix信号,它允許程序中斷其他程序。
2.低層的硬體異常是由核心異常處理程式處理的,正常情況下,對使用者程序而言是不可見的。信号提供了一種機制,通知使用者程序發生了這些異常。
8.5.1 信号術語
傳送一個信号到目的程序是由兩個步驟組成的:
1.發送信号。核心通過更新目的程序上下文中的某個狀态,發送(遞送)一個信号給目的程序。
發送信号可以有如下兩種原因:
1)核心檢測到一個系統事件。
2)一個程序調用了kill函數,顯式地要求核心發送一個信号給目的程序,一個程序可以發送信号給它自己。
2.接收信号。當目的程序被核心強迫以某種方式的發送做出反應時,目的程序就接收了信号。程序可以忽略這個信号,終止或者通過執行一個稱為信号處理程式的使用者層函數不活這個信号。
一個隻發出而沒有被接收的信号叫做待處理信号。在任何時刻,一種類型至多隻會有一個待處理信号。
一個程序可以有選擇性地阻塞接收某種信号。當一種信号被阻塞時,他仍可以被發送,但是産生的待處理信号不會被接收,直到程序取消對這種信号的阻塞。
一個待處理信号最多隻能被接收一次。
8.5.2 發送信号
1.程序組:每個程序都隻屬于一個程序組,程序組是由一個正整數程序組ID來辨別的。 一個子程序和它的父程序同屬于一個程序組,一個程序組可以通過使用setpgid函數來改變自己或者其他程序的程序組。
2.用/bin/kill程式發送信号:用/bin/kill程式可以向另外的程序發送任意的信号。
3.從鍵盤發送信号:從鍵盤發送信号外殼為每個作業建立一個獨立的程序組。
4.用kill函數發送信号:程序通過調用kill函數發送信号給其他程序(包括它們自己)。
5.用alarm函數發送信号:程序可以通過調用alarm函數向他自己發送SIGALRM信号。
8.5.3 接收信号
1.當核心從一個異常處理程式傳回,準備将控制傳遞給程序P時,他會檢查程序P的未被阻塞的處理信号的集合。如果這個集合為空,那麼核心将控制傳遞到P的邏輯控制流中的下一條指令;如果集合是非空的,那麼核心選擇集合中的某個信号K(通常是最小的K0,并且強制P接收信号K。收到這個信号會觸發程序的某種行為。一旦程序完成了這個行為,那麼控制就傳遞回P的邏輯控制流中的下一條指令。
2.每個信号類型都有一個預定的預設行為:
(1)程序終止
(2)程序終止并轉儲存儲器
(3)程序停止直到被SIGCONT型号重新開機
(4)程序忽略該信号
3.signal函數可以通過下列三種方法之一來改變和信号signum相關聯的行為:
(1)如果handler是SIG_IGN,那麼忽略類型為signum的信号
(2)如果handler是SIG_DFL,那麼類型為signum的信号行為恢複為預設行為
(3)否則,handler就是使用者定義的函數的位址,這個函數成為信号處理程式,隻要程序接收到一個類型為signum的信号,就會調用這個程式,通過把處理程式的位址傳遞到signal函數進而改變預設行為,這叫做設定信号處理程式。
①當一個程序不活了一個類型為K的信号時,為信号K設定的處理程式被調用,一個整數參數被設定為K。這個參數允許同一個處理函數捕獲不同類型的信号。
②信号處理程式的執行中斷main C函數的執行,類似于底層異常處理程式中斷目前應用程式的控制流的方式,因為信号處理程式的邏輯控制流與主函數的邏輯控制流重疊,信号處理程式和主函數并發地運作。
8.5.4 信号處理問題
1.當一個程式要捕獲多個信号時,一些細微的問題就産生了。
(1)待處理信号被阻塞。Unix信号處理程式通常會阻塞目前處理程式正在處理的類型的待處理信号。
(2)待處理信号不會排隊等待。任意類型至多隻有一個待處理信号。是以,如果有兩個類型為K的信号傳送到一個目的程序,而由于目的程序目前正在執行信号K的處理程式,是以信号K時阻塞的,那麼第二和信号就簡單地被簡單的丢棄,他不會排隊等待。
(3)系統調用可以被中斷。像read、wait和accept這樣的系統調用潛在地會阻塞程序一段較長的時間,稱為慢速系統調用。在某些系統中,當處理程式捕獲到一個信号時,被中斷的慢速系統調用在信号處理程式傳回時不再繼續,而是立即傳回給使用者一個錯誤的條件,并将errno設定為EINTR。
2.不可以用信号來對其他程序中發生的事件計較。
8.5.5 可移植的信号處理
Signal包裝函數設定的信号處理程式的信号處理語義:
(1)隻有這個處理程式目前正在處理的那種類型的信号被阻塞
(2)和所有信号實作一樣,信号不會排隊等候
(3)隻要有可能,被中斷的系統調用會自動重新開機。
(4)一旦設定了信号處理程式,它就會一直保持,知道signal帶着handler參數為SIG_IGN或者SIG_DFL被調用。
8.5.6 顯式地阻塞和取消阻塞信号
8.5.7 同步流以避免讨厭的并發錯誤
1.一般而言,流可能交錯的數量是與指令的數量呈指數關系的。
2.以某種方式同步并交流,進而得到最大的可行的交錯的集合,每個可行的交錯都能得到正确的結果。
3.如何編寫讀寫相同存儲位置的并發流程式的問題,困擾着數代計算機科學家。比如,競争問題。
8.6 非本地跳轉
1.c語言提供了一種使用者級異常控制流形式,稱為本地跳轉。通過setjmp和longjmp函數來提供。
2.setjmp函數隻被調用一次,但傳回多次:一次是當第一次調用setjmp,而調用環境儲存在緩沖區env中時,一次是為每個相應的longjmp調用。另一方面,longjmp隻調用一次,但從不傳回。sig—函數是setjmp和longjmp函數的可以被信号處理程式使用的版本。
3.非本地跳轉的一個重要應用就是允許從一個深層嵌套的函數調用中立即傳回,通常是由檢測到某個錯誤情況引起的。
非本地跳轉的另一個重要應用是使一個信号處理程式分支到一個特殊的代碼位置,而不是傳回到達中斷了的指令位置。
程式輸出結果如下:
8.7 操作程序的工具
Linux系統提供了大量的監控和操作程序的有用工具:
①STRACE:列印一個正在運作的程式和它的子程序調用的每個系統調用的軌迹。對于好奇的的工具。用-StatiC編譯你的程式,能傳到一個更幹淨的、不帶學生而言,這是一個令人着迷有大量與共享庫相關的輸出的軌迹。
②PS:列出目前系統中的程序(包括僵死程序)
③TOP:列印出關于目前程序資源使用的資訊。
④PMAP:顯示程序的存儲器映射。proc:一個虛拟檔案系統,以ASCII文本格式輸出大量核心數資料結構的内容,使用者程式可 cat 2 / proc / load avg” , 觀察在Linux系統上的平均負載。
8.8 小結
1.異常控制流(ECF)發生在計算機系統的各個層次,是計算機系統中提供并發的基本機制在硬體層,異常是由處理器中的事件觸發的控制流中的突變。控制流傳遞給一個軟體處理程式,該處理程式進行一些處理,然後傳回控制給被中斷的控制流。
2.有四種不同類型的異常:中斷、故障、終止和陷阱。當一個外部舊裝置,例如定時器晶片或者一個磁盤控制器,設定了處理器晶片上的中斷引腳時(對于任意指令)中斷會異步地發生控制傳回到故障指令後面的那條指令。一條指令的執行可能導緻故障和終止同時發生故障處理程式會重新啟動故障指令,而終止處理程式從不将控制傳回給被中斷的流。最後,陷阱就像是用來實作向應用提供到作業系統代碼的受控的入口點的系統調用的函數調用。
3.在作業系統層,核心用ECF提供程序的基本概念。程序提供給應用兩個重要的抽象:(1)邏輯控制流,它提供給每個程式一個假象,好像它是在獨占地使用處理器(2)私有位址空間,它提供給每個程式一個假象,好像它是在獨占地使用主存。
4.在作業系統和應用程式之間的接口處,應用程式可以建立子程序,等待它們的子程序停止或者終止,運作新的程式,以及捕獲來自其他程序的信号。信号處理的語義是微妙的,并且随系統不同而不同。然而,在與POSIX相容的系統上存在着一些機制,允許程式清楚地指定期望的信号處理語義。
5.最後,在應用層,C程式可以使用非本地跳轉來規避正常的調用/傳回棧規則,并且直接從一個函數分支到另一個函數。
==========================================================================================
習題記錄:
練習題 8.6:編寫一個叫做myecho的程式,它列印出它的指令行參數和環境變量。
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[], char *envp[])
{
printf("Command line arguments:\n");
for (int i = 0; i < argc; ++ i)
printf(" argv[%d]: %s\n", i, argv[i]);
printf("Enviroment variables:\n");
for (int i = 0; envp[i]; ++ i)
printf(" envp[%d]: %s\n", i, envp[i]);
return 0;
}
練習題 8.7:編寫名為snooze的程式,有一個指令行參數,使用該參數調用練習題8.5中的snooze函數,然後終止。編寫程式,使得使用者可以通過在鍵盤上輸入 crtl-c
中斷snooze函數。
// snooze.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
extern int errno;
unsigned int snooze(unsigned int secs);
void handler(int sig);
int main(int argc, char *argv[])
{
unsigned int rest_seconds = 0;
unsigned int secs = argv[1][0] - '0';
if (signal(SIGINT, handler) == SIG_ERR)
{
fprintf(stderr, "signal error: %s\n", strerror(errno));
exit(0);
}
rest_seconds = snooze(secs);
printf("User hits crtl-c after %u seconds\n", secs - rest_seconds);
return 0;
}
unsigned int snooze(unsigned int secs)
{
int rest_seconds = sleep(secs);
printf("Sleep for %u of %u seconds\n", secs - rest_seconds, secs);
return rest_seconds;
}
void handler() {}
練習題8.20:使用execve編寫一個名為myls的程式,該程式的行為和 /bin/ls 程式一樣。
// myls.c
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
extern char **environ;
int main(int argc, char *argv[])
{
execve("/bin/ls", argv, environ);
exit(0);
}
練習題 8.22
// mysystem.c
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
extern int erron;
extern char **environ;
extern int EINTP;
int mysystem(char *command)
{
pid_t pid;
int status;
if (command == NULL)
return -1;
if ((pid = fork()) == -1)
return -1;
if (pid == 0)
{
char *argv[4];
argv[0] == "sh";
argv[1] == "-c";
argv[2] == command;
argv[3] == NULL;
execve("bin/sh", argv, environ);
exit(-1); // control should never come here
}
while (1)
{
if (waitpid(pid, &status, 0) == -1)
{
if (errno != EINTR)
exit(-1);
}
else
{
if (WIFEXITED(status))
return WEXITSTATUS(status);
else
return status;
}
}
}
習題8.24
// 8.24.h
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <error.h>
#include <signal.h>
extern int errno;
extern int ECHILD;
extern void psignal(int signal, const char *str);
#define NCHILDREN 2
#define MAXLINE 80
char buf[MAXLINE];
int main()
{
int status;
pid_t pid;
for (int i = 0; i < NCHILDREN; ++ i)
{
pid = fork();
if (pid == 0)
*(char *)main = 1;
}
while (pid = wait(&status) > 0)
{
if (WIFEXITED(status))
printf("child %d terminated normally with exit status = %d\n", pid, WEXITSTATUS(status));
else
if (WIFSIGNALED(status))
{
sprintf(buf, "child %d terminated by signal %d: ", pid, WTERMSIG(status));
psignal(WTERMSIG(status), buf);
}
}
if (errno != ECHILD)
{
fprintf(stderr, "%s: %s\n", "wait error", strerror(errno));
exit(0);
}
return 0;
}
習題8.25 編寫fgets函數的一個版本tfgets,他5秒中後就會逾時。tfgets 函數接收和 fgets 相同的參數。如果使用者在5秒内不鍵入一個輸入行,tfgets傳回NULL。
否則,傳回一個指向輸入行的指針。
// tfgets.c
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
static sigjmp_buf env;
void handler(int signal)
{
alarm(0);
longjmp(env, 1);
}
char *tfgets(char *buffer, int buffer_size, FILE *stream)
{
signal(SIGALRM, handler);
alarm(5);
if (!sigsetjmp(env, 1))
return fgets(buffer, buffer_size, stream);
else
return NULL;
}
int main()
{
char *str;
char buffer[100];
while (1)
{
if (tfgets(buffer, sizeof(buffer),stdin) != NULL)
printf("read: %s", buffer);
else
printf("time out\n");
}
exit(0);
}
遇到的問題及解決方法:
本周的代碼挺多的。還有些難。我遇到的問題就是根據代碼了解函數功能這一部分。還将繼續加強。
心得體會:
這次的内容就是第八章的内容。與課上老師說過的内容很多重複的内容。因為老師的強調,我就更加認真去看了這部分的内容,也接了一個和這一章内容有關的實踐項目。這學期還有一門作業系統的必修課,學到了很多與程序有關的知識。我想通過這一章的學習和實踐項目的學習鞏固這部分知識并且做到學科間融合。但是這一章的好幾個函數的運作和原理還是需要多多琢磨,期間遇到了很多的問題都是自己的了解不夠透徹。但是這些都是計算機作業系統的重點,部落格行文至此知識一個階段的學習,将來還會時常學習這部分,做到溫故而知新。
參考文獻:
1.《深入了解計算機系統》pdf
2.《作業系統》教材、PPT
3.習題解析:http://www.lxway.com/489660494.htm
4.内容總結:http://www.lxway.com/126892501.htm
5.課程資料:https://www.shiyanlou.com/courses/413 實驗十,課程邀請碼:W7FQKW4Y