天天看點

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

第十一周(11.16-11.22):

學習計時:共10小時

讀書:5

代碼:2

作業:2

部落格:1

一、學習目标

  1. 了解異常及其種類
  2. 了解程序和并發的概念
  3. 掌握程序建立和控制的系統調用及函數使用:fork,exec,wait,waitpid,exit,getpid,getppid,sleep,pause,setenv,unsetenv,
  4. 了解數組指針、指針數組、函數指針、指針函數的差別
  5. 了解信号機制:kill,alarm,signal,sigaction
  6. 掌握管道和I/O重定向:pipe, dup, dup2

第8章 異常控制流

從給處理器加電開始,直到斷電為止,程式計數器假設一個值的序列 。

a0,a1 …, an-1

其中,每個 ak 是某個相應的指令 ι 的位址。每次從 ak 到如1 的過渡稱為控制轉移 。這樣的控制轉移序列叫做處理器的控制流。

最簡單的一種控制流是一個"平滑的"序列,其中每個Ik 和 Ik+1 在存儲器中都是相鄰的。

現代系統通過使控制流發生突變來對這些情況做出反應,這些突變稱為異 常控制流 (Exceptional Control Flow, ECF)。異常控制流發生在計算機系統的各個層次。在硬體層,硬體檢測到的事件會觸發控制突然轉移到異常處理程式。在作業系統層,核心通過上 下文轉換将控制從一個使用者程序轉移到另一個使用者程序。在應用層,一個程序可以發送信号到另一個程序,而接收者會将控制突然轉移到它的一個信号處理程式。一個程式可以通過回避通常的棧規則,并執行到其他函數中任意位置的非本地跳轉來對錯誤做出反應。

  • ECF 是作業系統用來實作I/O、程序和虛拟存儲器的基本機制。
  • 應用程式通過使用一個叫做陷阱 (trap) 或者系統調用 (system call) 的 ECF 形式,向作業系統請求服務。
  • 作業系統為應用程式提供了強大的ECF機制,用來建立新程序、等待程序終止、通知其他程序系統中的異常事件,以及檢測和響應這些事件。
  • ECF 是計算機系統中實作并發的基本機制。
  • C++ 和 Java這樣的語言通過 try、 catch 以及 throw 語句來提供軟體異常機制。軟體異常允許程式進行非本地跳轉(違反通常的調用/傳回 樵規則的跳轉)來響應錯誤情況。非本地跳轉是一種應用層 ECF,在 C 中是通過 setjmp 和 longjmp 函數提供的。

異常位于硬體和作業系統交界的部分。系統調用,它們是為應用程式提供到作業系統的入口點的異常。抽象的層次,描述程序和信号,它們位于應用和作業系統的交界之處。非本地跳轉,這是 ECF 的一種應用層形式。

8.1 異常

異常是異常控制流的一種形式,它一部分是由硬體實作的,一部分是由作業系統實作的。有一部分是由硬體實作的,是以具體細節将随系統的不同而有所不同。

異常(exception )就是控制流中的突變,用來響應處理器狀态中的某些變化。

在處理器中,狀态被編碼為不同的位和信号。狀态變化稱為事件 (event)。 事件可能和目前指令的執行直接相關。 在任何情況下,當處理器檢測到有事 件發生時,它就會通過一張叫做異常在 (exception table)時的跳轉表,進行一個間接過程調用(異常),到一個專門設計用 來處理這類事件的作業系統子程式(異常處理程式 (exception handler))。

當異常處理程式完成處理後,根據引起異常的事件的類型,會發生以下三種情況中的一種:

  1. 處理程式将控制傳回給目前指令Icurr,即當事件發生時正在執行的指令。
  2. 處理程式将控制傳回給Inext,即如果沒有發生異常将會執行的下一條指令。
  3. 處理程式終止被中斷的程式。

異常的剖析:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

8.1.1 異常處理

系統中可能的每種類型的異常都配置設定了一個唯一的非負整數的異常号。

其中一些号碼是由處理器的設計者配置設定的:包括被零除、缺頁、存儲器通路違例、斷點以及算術溢出。

其他号碼是由作業系統核心(作業系統常駐存儲器的部分〉的設計者配置設定的:包括系統調用和來自外部I/O 裝置的信号。

在系統啟動時(當計算機重新開機或者加電時),作業系統配置設定和初始化一張稱為異常在的跳轉表,使得條目 k包含異常 k的處理程式的位址。

異常表:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

在運作時(當系統在執行某個程式時),處理器檢測到發生了一個事件,并且确定了相應的異常号k。随後,處理器觸發異常,方法是執行間接過程調用,通過異常表的條目 k轉到相應的處理程式。

異常号是到異常表中的索引,異常表的起始位址放在一個叫做異常在 基址寄存器 (exception table base register) 的特殊 CPU 寄存器裡。

異常類似于過程調用,但是有一些重要的不同。

生成異常處理程式的位址:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結
  • 過程調用時,在跳轉到處理程式之前,處理器将傳回位址壓人找中。然而,根據異常的類型,傳回位址要麼是目前指令(當事件發生時正在執行的指令),要麼是下一條指令(如果事件不發生,将會在目前指令後執行的指令〉。
  • 處理器也把一些額外的處理器狀态壓到棧裡,在處理程式傳回時,重新開始被中斷的程式 會需要這些狀态。
  • 如果控制從一個使用者程式轉移到核心,那麼所有這些項目都被壓到核心棧中,而不是壓到使用者棧中。
  • 異常處理程式運作在核心模式下,這意味着它們對所有的系統資源都有完全的通路權限。

一旦硬體觸發了異常,剩下的工作就是由異常處理程式在軟體中完成。在處理程式處理完事件之後,它通過執行一條特殊的"從中斷傳回"指令,可選地傳回到被中斷的程式,該指令将适當的狀态彈回到處理器的控制和資料寄存器中,如果異常中斷的是一個使用者程式,就将狀态恢複為使用者模式,然後将控制傳回給被中斷的程式。

8.1.2 異常的類别

異常可以分為四類:中斷(interrupt)、陷阱 (trap)、故障 (fault) 和終止 (abort)。

1.中斷

中斷是異步發生的,是來自處理器外部的I/O 裝置的信号的結果。

異常的類别:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

(陷阱、故障和終止)是同步發生的,是執行目前指令的結果。我們把這類 指令叫做故障指令 (faulting instructiott)。

2.陷阱和系統調用

陷阱是有意的異常,是執行一條指令的結果。

陷阱處理程式将控制傳回到下一條指令。陷阱最重要的用途是在使用者程式和核心之間提供一個像過程一樣的接口,叫做系統調用。

使用者程式經常需要向核心請求服務,比如讀一個檔案(read)、建立一個新的程序 任何k)、加載一個新的程式(execve)時,或者終止目前程序(exit)。為了允許對這些核心服務的受控的通路,處理器提供了一條特殊的 "syscall n" 指令,當使用者程式想要請求服務 n 時,可以執行這條指令。執行 syscall 指令會導緻一個到異常處理程式的陷阱,這個處理程式對參數解碼,并調用适當的核心程式。

從程式員的角度來看,系統調用和普通的函數調用是一樣的。然而,它們的實作是非常不同的。普通的函數運作在使用者模式 (user mode) 中,使用者模式限制了函數可以執行的指令的類型,而且它們隻能通路與調用函數相同的棧。系統調用運作在核心模式 (kernel mode) 中,核心模式允許系統調用執行指令,并通路定義在核心中的棧。

陷阱處理:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

3.故障

故障由錯誤情況引起,它可能能夠被故障處理程式修正。當故障發生時,處理器将控制轉移給故障處理程式。如果處理程式能夠修正這個錯誤情況,它就将控制傳回到引起故障的指令,進而重新執行它。否則,處理程式傳回到核心中的 abort 例程, abort 例程會終止引起故障的應用程式。

故障處理:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

4.終止

終止是不可恢複的緻命錯誤造成的結果,通常是一些硬體錯誤,終止處理程式從不将控制傳回給應用程式。

終止處理:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

8.1.3 Linux/IA32系統中的異常

1.Linux/IA32故障和終止

除法錯誤。當應用試圖除以零時,或者當一個除法指令的結果對于目标操作數來說太大了 的時候,就會發生除法錯誤(異常0)。Unix不會試圖從除法錯誤中恢複,而是選擇中止程式。Linux外殼通常會把除法錯誤報告為"浮點異常" (Floating exception)。

IA32 系統中的異常示例:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

一般保護故障。許多原因都會導緻不為人知的一般保護故障(異常13),通常是因為一個程式引用了一個未定義的虛拟存儲器區域,或者因為程式試圖寫一個隻讀的文本段。Linux不會嘗試恢複這類故障。Linux外殼通常會把這種一般保護故障報告為"段故障.. (segmentation fault).

缺頁(異常 14) 是會重新執行産生故障的指令的一個異常示例。處理程式将磁盤上實體存 儲器相應的頁面映射到虛拟存儲器的一個頁面,然後重新開始這條産生故障的指令。

機器檢查。機器檢查(異常 18) 是在導緻故障的指令執行中檢測到緻命的硬體錯誤時發生 的。機器檢查處理程式從不傳回控制給應用程式。

2. linux/IA32 系統調用

每個系統調用都有一個唯一的整數号,對應于一個到核心中跳轉表的偏移量。

Linux/IA32 系統中常用的系統調用示例:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

在IA32 系統上,系統調用是通過一條稱為 int n 的陷阱指令來提供的,其中 n 可能是 IA32 異常表中 256 個條目中任何一個的索引。

C 程式用 syscall 函數可以直接調用任何系統調用。然而,實際中幾乎沒必要這麼做。對 于大多數系統調用,标準 C 庫提供了一組友善的包裝函數。這些包裝函數将參數打包到一起, 以适當的系統調用号陷入核心,然後将系統調用的傳回狀态傳遞回調用程式。系統調用和與它們相關聯的包裝函數稱為系統級函數。

8.2 程序

異常是允許作業系統提供程序 (process) 的概念所需要的基本構造塊。

系統中的每個程式都是運作在某個程序的上 下文 (context) 中的。上下文是由程式正确運作所需的狀态組成的。這個狀态包括存放在存儲器 中的程式的代碼和資料,它的棧、通用目的寄存器的内容、程式計數器、環境變量以及打開檔案 描述符的集合。

應用程式的關鍵抽象:

  • 一個獨立的邏輯控制流,它提供一個假象,好像我們的程式獨占地使用處理器。
  • 一個私有的位址空間,它提供一個假象,好像我們的程式獨占地使用存儲器系統。

8.2.1邏輯控制流

程式計數器 (PC) 的 值,這些值唯一地對應于包含在程式的可執行目标檔案中的指令,或者是包含在運作時動态連結 到程式的共享對象中的指令。這個 PC 值的序列叫做邏輯控制流,或者簡稱邏輯流。

邏輯控制流:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

邏輯控制流關鍵點在于程序是輪流使用處 理器的。每個程序執行它的流的一部分,然 後被搶占 (preempted) (暫時挂起),然後輪 到其他程序。

程序為每個程式提供了一種假象,好像程式在獨占地使用處理器。每個豎直的條表示一個程序的邏輯控制流的一部分

8.2.2并發流

一個邏輯流的執行在時間上與另一個流重疊,稱為并發流 (concurrent fiow) ,這兩個流被稱 為并發地運作。

多個流并發地執行的一般現象稱為并發 (concurrency)。一個程序和其他程序輪流運作的 概念稱為多任務 (multitasking). 一個程序執行它的控制流的一部分的每一時間段叫做時間片 (time slice)。是以,多任務也叫做時間分片(time slicing)。

兩個流并發地運作在不同的處理器核或者計算機 上,那麼我們稱它們為并行流 (parallel flow),它們并行地運作 (running in parallel),且并行地 執行 (parallel execution)。

8.2.3 私有位址空間

n 位位址的機器上,地祉空間是 2n次方個可能位址的集合。一個程序為每個程式提供它自己的 私有位址空間。一般而言,和這個空間中某個位址相關聯的那個存儲器位元組是不能被其他程序讀 或者寫的,從這個意義上說,這個位址空間是私有的。 盡管和每個私有位址空間相關聯的存儲器的内容一般是不同的,但是每個這樣的空間都有相 同的通用結構。

8.2.4 使用者模式和核心模式

處理器通常是用某個控制寄存器中的一個模式位 (mode bit 來提供這種功能的,該寄存器 描述了程序目前享有的特權。當設定了模式位時,程序就運作在核心模式中(有時叫做超級使用者 模式)。一個運作在核心模式的程序可以執行指令集中的任何指令,并且可以通路系統中任何存 儲器位置。

程序位址空間:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

沒有設定模式位時,程序就運作在使用者模式中。

運作應用程式代碼的程序初始時是在使用者模式中的。程序從使用者模式變為核心模式的唯一方法是通過諸如中斷、故障或者陷入系統調用這樣的異常。當異常發生時,控制傳遞到異常處理程式,處理器将模式從使用者模式變為核心模式。處理程式運作在核心模式中,當它傳回到應用程式代碼時,處理器就把模式從核心模式改回到使用者模式。

/proc 檔案系統,它允許使用者模式程序通路核心資料結構的内容。 /proc 檔案系統将許多核心資料結構的内容輸出為一個使用者程式可以讀的文 本檔案的層次結構。

8.2.5上下文切換

作業系統核心使用一種稱為上下文切換 (context switch) 的較高層形式的異常控制流來實作多任務。

核心為每個程序維持一個上下文 (context)。上下文就是核心重新啟動一個被搶占的程序所需的狀态。它由一些對象的值組成,這些對象包括通用目的寄存器、浮點寄存器、程式計數器、 使用者棧、狀态寄存器、核心棧和各種核心資料結構,比如描繪位址空間的頁表、包含有關目前程序資訊的程序表,以及包含程序已打開檔案的資訊的檔案表。

在程序執行的某些時刻,核心可以決定搶占目前程序,并重新開始一個先前被搶占的程序。這種決定就叫做排程(schedule),是由核心中稱為排程器(scheduler )的代碼處理的。當核心選擇一個新的程序運作時,我們就說核心排程了這個程序。在核心排程了一個新的程序運作後,它就搶占目前程序,并使用一種稱為上下文切換的機制來将控制轉移到新的程序,上下文切換

1)儲存目前程序的上下文

2)恢複某個先前被搶占的程序被儲存的上下文

3)将控制傳遞給這個新恢複的程序。

當核心代表使用者執行系統調用時,可能會發生上下文切換。如果系統調用因為等待某個事件發生而阻塞,那麼核心可以讓目前程序休眠,切換到另一個程序。

一般而言,即使系統調用沒有阻塞,核心也可以決定執行上下文切換,而不是将控制傳回給調用程序。

中斷也可能引發上下文切換

程序上下文切換的剖析:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

磁盤取資料要用一段相對較長的時間(數量級為幾十毫秒),是以核心執行從程序 A 到程序 B 的上下文切換,而不是在這個間歇時間内等待,什麼都不做。

8.3系統調用錯誤處理

當 Unix 系統級函數遇到錯誤時,它們典型地會傳回-1,并設定全局整數變量 errno 來表示什麼出錯了。

用錯誤處理包裝 (error-handling wrapper)函數更進一步地簡化我們的代碼。

8.4 程序控制

Unix提供了大量從 C 程式中操作程序的系統調用。

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、 SIDTTIN的或者 SIGTTOU 信号時,程序就停止,并且保持停止直到它收到一個 SIGCONT 信号,在這個時刻,程序再次開始運作。
  • 終止。程序永遠地停止了。程序會因為三種原因終止: 1) 收到一個信号,該信号的預設行為是終止程序, 2) 從主程式傳回, 3) 調用 exit 函數。

exit 函數以 status 迫出狀态來終止程序(另一種設定退出狀态的方法是從主程式中傳回 一個整數值〉。

父程序通過調用 fork 函數建立一個新的運作子程序 。

新建立的子程序幾乎但不完全與父程序相同。子程序得到與父程序使用者級虛拟位址空間相同 的(但是獨立的)一份拷貝,包括文本、資料和 bss 段、堆以及使用者棧。子程序還獲得與父程序任何打開檔案描述符相同的拷貝,這就意味着當父程序調用fork。此時,子程序可以讀寫父程序中打開的任何檔案。父程序和新建立的子程序之間最大的差別在于它們有不同的 PID。

fork隻被調用一次,卻會傳回兩次 : 一次是在調用程序(父程序〉中,一次是在新建立的子程序中。在父程序中, fork 傳回子程序的 PID。在子程序中, fork 傳回 0。因為子程序的 PID 總是非零的,傳回值就提供一個明确的方 法來分辨程式是在父程序還是在子程序中執行。

  • 調用一次,傳回兩次。 fork 函數被父程序調用一次,但是卻傳回兩次一一一次是傳回到 父程序,一次是傳回到新建立的子程序。
  • 并發執行。父程序和子程序是并發運作的獨立程序。核心能夠以任意方式交替執行它們的邏輯控制流中的指令。
  • 相同的但是獨立的位址空間。如果能夠在 fork 函數在父程序和子程序中傳回後立即暫停這兩個程序,我們會看到每個程序的位址空間都是相同的。每個程序有相同的使用者棧、相同的本地變量值、相同的堆、相同的全局變量值,以及相同的代碼。
  • 共享檔案。子程序繼承了父程序所有的打開檔案。當父程序調用 fork。此時, stdout 檔案是被打開的,并指向螢幕。子程序繼承了這個檔案,是以它的輸出也是指向螢幕的。

8.4.3回收子程序

當一個程序由于某種原因終止時,核心并不是立即把它從系統中清除。相反,程序被保持在一種己終止的狀态中,直到被它的父程序回收。

一個終止了但還未被回收的程序稱為僵死程序。

如果父程序沒有回收它的僵死子程序就終止了,那麼核心就會安排 init 程序來回收它們。 init 程序的 PID 為1,并且是在系統初始化時由核心建立的。

長時間運作的程式,總是應該回收它們的僵死子程序。即使僵死子程序沒有運作,它們仍然消耗系統的存儲器資源。

一個程序可以通過調用 waitpid 函數來等待它的子程序終止或者停止。

waitpid:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

預設地(當 options = 0 時), waitpid 挂起調用程序的執行, 直到它的等待集合中的一個子程序終止。如果等待集合中的一個程序在剛調用的時刻就已經終止了,那麼 waitpid 就立即傳回。在這兩種情況下, waitpid 傳回導緻 waitpid 傳回的已終止子程序的 PID,并且将這個已終止的子程序從系統中去除。

1.判定等待集合的成員

  • 如果 pid> 0,那麼等待集合就是一個單獨的子程序,它的程序ID等于 pid。
  • 如果 pid = -1 ,那麼等待集合就是由父程序所有的子程序組成的。

2. 修改預設行為

可以通過将 optioins 設定為常量 WNOHANG 和 WUNTRACED 的各種組合,修改預設行為:

  • WNOHANG: 如果等待集合中的任何子程序都還沒有終止,那麼就立即傳回(傳回值為0)。預設的行為是挂起調用程序,直到有子程序終止。在等待子程序終止的同時,如果還想做些有用的工作,這個選項會有用。
  • WUNTRACED :挂起調用程序的執行,直到等待集合中的一個程序變成已終止或者被停止。傳回的 PID 為導緻傳回的己終止或被停止子程序的 PID。預設的行為是隻傳回己終止的子程序。當你想要檢查已終止和被停止的子程序時,這個選項會有用。
  • WNOHANG|WUNTRACED: 立即傳回,如果等待集合中沒有任何子程序被停止或已終止, 那麼傳回值為 0,或者傳回值等于那個被停止或者己終止的子程序的 PID。

3. 檢查己回收子程序的退出狀态

如果 status 參數是非空的,那麼 waitpid 就會在 status 參數中放上關于導緻傳回的子程序的狀态資訊:

  • WIFEXITED (status) :如果子程序通過調用 exit 或者一個傳回 (return) 正常終止, 就傳回真。
  • WEXITSTATUS (status) : 傳回一個正常終止的子程序的退出狀态。隻有在 WIFEXITED 傳回為真時,才會定義這個狀态。
  • WIFSIGNALED (status): 如果子程序是因為一個未被捕獲的信号終止的,那麼就傳回真。
  • WTERMSIG (status): 傳回導緻子程序終止的信号的數量。隻有在 WIFSIGNALED (status) 傳回為真時,才定義這個狀态。
  • WIFSTOPPED (status) :如果引起傳回的子程序目前是被停止的,那麼就傳回真。
  • WSTOPSIG (status): 傳回引起子程序停止的信号的數量。隻有在 WIFSTOPPED (status) 傳回為真時,才定義這個狀态。

4.錯誤條件

如果調用程序沒有子程序,那麼 waitpid 傳回-1,并且設定 errno 為 ECHILD。如果 waitpid 函數被一個信号中斷,那麼它傳回一1,并設定 errno 為 EINTR。

5.wait函數

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

調用 wait(&status) 等價于調用 waitpid(-l, &status, 0) 。

6.使用waitpid的示例

程式不會按照特定的順序回收子程序。子程序回收的順序是這台特定的計算機的屬性。在另一個系統上,甚至在同一個系統上再執行一次,兩個子程序都可能以相反的順序被回收。這是非确定性的。

8.4.4 讓程序休眠

sleep 函數将一個程序挂起一段指定的時間。

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

如果請求的時間量已經到了, sleep 傳回 0,否則傳回還剩下的要休眠的秒數。

pause 函數,該函數讓調用函數休眠,直到該程序收到一個信号。

8.4.5 加載并運作程式

execve 函數在目前程序的上下文中加載并運作一個新程式。

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

execve 函數加載并運作可執行目标檔案 filename,且帶參數清單 argv 和環境變量清單 envp。隻有當出現錯誤時execve 才會傳回到調用程式。是以,與 fork 一次調用傳回兩次不同, execve 調用一次并從不傳回。

8.4.6 利用 fork 和 execve 運作程式

外殼先是一個互動型的應用級程式,它代表使用者運作其他程式,最早的外殼是 sh 程式,後面出現了一些變種,比如 csh、 tcsh、 ksh 和 bash. 外殼執行一系列的讀/求值(read/evaluate) 步驟,然後終止。讀步驟讀取來自使用者的一個指令行。求值步驟解析指令行,并代表使用者運作程式。

8.5 信号

Unix 信号,它允許程序中斷其他程序。

一個信号就是一條小消息,它通知程序系統中發生了一個某種類型的事件。

Linux信号:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

每種信号類型都對應于某種系統事件。低層的硬體異常是由核心異常處理程式處理的,正常情況下,對使用者程序而言是不可見的。信号提供了一種機制,通知使用者程序發生了這些異常。

8.5.1 信号術語

傳送一個信号到目的程序是由兩個不同步驟組成的 :

  • 發送信号。核心通過更新目的程序上下文中的某個狀态,發送(遞送)一個信号給目的程序。發送信号可以有如下兩個原因:1)核心檢測到一個系統事件,比如被零除錯誤或者子程序終止。 2) 一個程序調用了 kill 函數,顯式地要求核心發送一個信号給目的程序。一個程序可以發送信号給它自己。
  • 接收信号。當目的程序被核心強迫以某種方式對信号的發送做出反應時,目的程序就接收了信号。程序可以忽略這個信号,終止或者通過執行一個稱為信 号處理程式 (signal handler) 的使用者層函數捕獲這個信号

一個隻發出而沒有被接收的信号叫做待處理信号 (pending signal)。 在任何時刻,一種類型至多隻會有一個待處理信号。

一個程序可以有 選擇性地阻塞接收某種信号。當一 種信号被阻塞時,它仍可以被發送, 但是産生的待處理信号不會被接收, 直到程序取消對這種信号的阻塞。

一個待處理信号最多隻能被接收一次。

8.5.2 發送信号

1.程序組

Unix 系統提供了大量向程序發送信号的機制。所有這些機制都是基于程序紐 (process group) 這個概念的。

getpgrp:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

一個子程序和它的父程序同屬于一個程序組。一個程序可以通過使用 setpgid 函數來改變自己或者其他程序的程序組:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

setpgid 函數将程序 pid 的程序組改為 pgid。如果 pid 是 0,那麼就使用目前程序的 PID。 如果 pgid 是 0,那麼就用 pid 指定的程序的 PID 作為程序組 ID。

2. 用 /bin/kill 程式發送信号

/bin/kill 程式可以向另外的程序發送任意的信号。

3. 從鍵盤發送信号

Unix 外殼使用作業 (job) 這個抽象概念來表示為對一個指令行求值而建立的程序。在任何 時刻,至多隻有一個前台作業和 0 個或多個背景作業。

外殼為每個作業建立一個獨立的程序組。

4. 用 kill 函數發送信号

程序通過調用 kill 函數發送信号給其他程序(包括它們自己)。

如果 pid 大于零,那麼 kill 函數發送信号 sig 給程序 pid。如果 pid 小于零,那麼 kill 發送信号 sig 給程序組 abs (pid) 中的每個程序。

5. 用 alarm 函數發送信号

程序可以通過調用 alarm 函數向它自己發送 SIGALRM 信号。

alarm:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

alarm 函數安排核心在 secs 秒内發送一個 SIGALRM 信号給調用程序。如果 secs 是零,那麼不會排程新的鬧鐘 (alarm)。在任何情況下,對 alarm 的調用都将取消任何待處理的 (pending) 鬧鐘,并且傳回任何待處理的鬧鐘在被發送前還剩下的秒數〈如果這次對 alarm 的 調用沒有取消它的話),如果沒有任何待處理的鬧鐘,就傳回零。

8.5.3接收信号

當核心從一個異常處理程式傳回,準備将控制傳遞給程序p 時,它會檢查程序p 的未被阻塞 的待處理信号的集合 (pending&-blocked)。如果這個集合為空(通常情況下),那麼核心将 控制傳遞到p 的邏輯控制流中的下一條指令。

然而,如果集合是非空的,那麼核心選擇集合中的某個信号 k (通常是最小的 k), 并且強制 p接收信号 k.

每個信号類型都有一個預定義的預設行為,是下面中的一種:

  • 程序終止。
  • 程序終止并轉儲存儲器
  • 程序停止直到被 SIGCONT 信号重新開機.
  • 程序忽略該信号。

signal函數:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

signal 函數可以通過下列三種方法之一來改變和信号 signum 相關聯的行為:

  • 如果 handler 是 SIG_IGN. 那麼忽略類型為 signum 的信号。
  • ·如果 handler 是 SIG_DFL,那麼類型為 signum 的信号行為恢複為預設行為。
  • 否則, handler 就是使用者定義的函數的位址,這個函數稱為信号處理程式 (signal handler),隻要程序接收到一個類型為 signum 的信号,就會調用這個程式。通過把處理程式的位址傳遞到 signal 函數進而改變預設行為,這叫做設定信号處理程式(installing the handler)。調用信号處理程式稱為捕獲信号。執行信号處理程式稱為處理信号。

8.5.4 信号處理問題

  • 待處理信号被阻塞
  • 待處理信号不會排隊等待。
  • 系統調用可以被中斷。

    不可以用信号來對其他程序中發生的事件計數。

8.5.5 可移植的信号處理

不同系統之間,信号處理語義的差異〈比如一個被中斷的慢速系統調用是重新開機還是永久放棄) 是 Unix 信号處理的一個缺陷。為了處理這個問題, Posix 标準定義了 sigaction 函數,它允許像 Linux 和 Solaris 這樣與 Posix 相容的系統上的使用者,明确地指定他們想要的信号處理語義。

sigaction 函數:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

Signal 包裝函數設定了一個信号處理程式,其信号處理語義如下:

  • 隻有這個處理程式目前正在處理的那種類型的信号被阻塞。
  • 和所有信号實作一樣,信号不會排隊等待。
  • 隻要可能,被中斷的系統調用會自動重新開機。
  • 一旦設定了信号處理程式,它就會一直保持,直到 Signal 帶着 handler 參數為 SIG_ IGN 或者 SIG_DFL 被調用。

8.5.6 顯式地阻塞和取消阻塞信号

sigprocmask 函數:

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

sigprocmask 函數改變目前已阻塞信号的集合,具體的行為依賴于 how 的值:

  • SIG_BLOCK: 添加 set 中的信号到 blocked 中 (blocked = blocked | set).
  • SIG_UNBLOCK: 從 blocked 中删除 set 中的信号 (blocked = blocked & ~set)。
  • SIG_SETMASK:blocked=set
  • 如果oldset 非空, blocked 位向量以前的值會儲存在oldset 中。

sigemptyset 初始化 set 為空集。 sigfillset 函數将每個信号添加到 set 中。 sigaddset 函數添加 signum 到 set, sigdelset 從 set 中删除 signum,如果 signum 是 set 的成員,那麼 sigismember 傳回 1,否則傳回 0。

8.5.7 同步流以避免讨厭的并發錯誤

addjob 和 deletejob 函數分别向這個作業清單添加和從中删除作業。

8.6 非本地跳轉

C 語言提供了一種使用者級異常控制流形式,稱為非本地跳轉 (nonlocal jump),它将控制直 接從一個函數轉移到另一個目前正在執行的函數,而不需要經過正常的調用-傳回序列。非本 地跳轉是通過 setjmp和 longjmp 函數來提供的。

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

setjmp 函數在 env 緩沖區中儲存目前調用壞境,以供後面 longjmp 使用,并傳回 o. 調 用環境包括程式計數器、樵指針和通用目的寄存器。

20135321餘佳源——資訊安全系統設計基礎第十一周學習總結

longjmp 函數從 env 緩沖區中恢複調用環境,然後觸發一個從最近一次初始化 env 的 setjmp調用的傳回。然後 setjmp傳回,并帶有非黴的傳回值 retval.

setjmp 函數隻被調用一次, 但傳回多次:一次是當第一次調用 setjmp,而調用環境儲存在緩沖區 env 中時:一次是為每個相應的 longjmp 調用。另一方面, longjmp 函數被調用一次,但從不傳回。

非本地跳轉的另一個重要應用是使一個信号處理程式分支到一個特殊的代碼位置,而不是 傳回到被信号到達中斷了的指令的位置。

在程式第一次啟動時,對 sigsetjmp 函數的初始調用儲存調用環境和信号的上下文(包 括待處理的和被阻塞的信号向量)。随後,主函數進入一個無限處理循環。當使用者鍵人 ctrl-c 時,外殼發送一個 SIGINT 信号給這個程序,該程序捕獲這個信号。不是從信号處理程式傳回, 如果是這樣信号處理程式會将控制傳回給被中斷的處理循環,反之,處理程式執行一個非本地跳轉,回到主函數的開始處。

8.7 操作程序的工具

  • STRACE: 列印一個正在運作的程式和它的子程序調用的每個系統調用的軌迹。
  • PS: 列出目前系統中的程序(包括僵死程序)。
  • TOP: 列印出關于目前程序資源使用的資訊。
  • PMAP: 顯示程序的存儲器映射。
  • /proc: 一個虛拟檔案系統,以 ASCII 文本格式輸出大量核心資料結構的内容,使用者程式可以讀取這些内容。

8.8 小結

異常控制流 (ECF) 發生在計算機系統的各個層次,是計算機系統中提供并發的基本機制。

在硬體層,異常是由處理器中的事件觸發的控制流中的突變。控制流傳遞給一個軟體處理程 序,該處理程式進行一些處理,然後傳回控制給被中斷的控制流。

有四種不同類型的異常 : 中斷、故障、終止和陷阱。

中斷會異步地發生。控制傳回到故障指令後面的那條指令。一條指令的執行可能導緻故障和終止同時發生。故障處理程式會重新啟動故障指令,而終止處理程式從不将控制傳回給被中斷的流。最後,陷阱就像是用來實作向應用提供到作業系統代碼的受控的人口點的系統調用的函數調用。

作業系統層,核心用 ECF 提供程序的基本概念。程序提供給應用兩個重要的抽象;1)邏 輯控制流,它提供給每個程式一個假象,好像它是在獨占地使用處理器, 2) 私有位址空間,它提供給每個程式一個假象,好像它是在獨占地使用主存。

作業系統和應用程式之間的接口處,應用程式可以建立子程序,等待它們的子程序停止或者終止,運作新的程式,以及捕獲來自其他程序的信号。

信号處理的語義是微妙的,并且随系統不同而不同。然而,在與 Posix 相容的系統上存在着一些機制,允許程式清楚地指定期望的信号處理語義。

應用層, C 程式可以使用非本地跳轉來規避正常的調用/傳回棧規則,并且直接從一個函數分支到另一個函數。