深入理解計算機系統 --- 異常控制流

在這裏插入圖片描述
現代系統通過使控制流發生突變 來對這些情況做出反應
一般而言,我們把這些突變稱爲 異常控制流(Exception Control Flow, ECF)

異常控制流發生在計算機系統的各個層次
在硬件層,硬件檢測到的事件會出發控制突然轉移到異常處理程序
在操作系統層,內核通過上下文切換從一個用戶進程轉移到另一個用戶進程
在應用層,一個進程可以發送信號到另一個進程,而接收者會將控制突然轉移到它的一個信號處理程序
一個程序可以通過迴避通常的棧規則,並執行到其他函數中任意位置的非本地跳轉來對錯誤做出反應
在這裏插入圖片描述
在這裏插入圖片描述
本章的重要性在於你將開始學習應用是如何與操作系統交互的
這些交互都是圍繞着ECF的

8. 異常控制流

8.1 異常

本節目的:對異常和異常處理有一個一般性的瞭解,並揭示現代計算機系統的一個經常令人感到迷惑的方面

異常時一場控制流的一種形式,它一部分由硬件實現,一部分由操作系統實現
它們有一部分是由硬件實現的,所以具體細節將隨系統的不同而有所不同
然而,對於每個系統而言,基本的思想都是相同的

在這裏插入圖片描述
異常( exception )就是控制流中的突變,用來響應處理器狀態中的某些變化

當處理器狀態中發生了一個重要的變化時,處理器正在執行某個當前指令 I curr
在處理器中,狀態被編碼爲不同的位和信號
狀態變化稱爲事件( event )
事件可能和當前指令的執行直接相關
比如,發生虛擬內存缺頁,算術溢出,或一條指令試圖除以零 等等
另一方面,事件也可能和當前指令的執行沒有關係
比如,一個系統定時器產生信號或者一個I/O請求完成

在任何情況下,當處理器檢測到有事件發生時,它就會通過一張叫做 異常表(exception table)
的跳轉表,進行一個間接過程調用( 異常 ),到一個專門設計用來處理這類事件的操作系統子程序( 異常處理程序( exception handler ) )
當異常處理程序完成處理後,根據引起異常的事件類型,會發生一下3種情況:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

8.1.1 異常處理

處理異常需要硬件和軟件的緊密合作

系統中可能的每種類型的異常都分配了一個唯一的非負整數的異常號( exception number )
其中一些號碼是由處理器的設計者分配的,其他號碼是由操作系統內核( 操作系統常駐內存的部分 )的設計者分配的
前者包括的示例 被零除、缺頁、內存訪問違例、斷點以及算術運算溢出
後者的示例包括 系統調用和來自外部I/O設備的信號
在這裏插入圖片描述
系統啓動時( 當計算機重啓或者加電時 ),操作系統分配和初始化一張稱爲 異常表的跳轉表
使得表目K 包含 異常 K 的處理程序的地址

在運行時(當系統在執行某個程序時),處理器檢測到發生了一個事件,並且確定了相應的異常號 k
隨後,處理器出發異常,方法是執行間接過程調用,通過異常表的條目k,轉到相應的處理程序
在這裏插入圖片描述
上圖展示了處理器如何使用異常表來形成適當的異常處理程序的地址
異常號是到異常表中的索引,異常表的其實地址放在一個叫做 異常表基址寄存器( exception table base register ) 的特殊CPU寄存器中

在這裏插入圖片描述
一旦硬件觸發了異常,剩下的工作就是由異常處理程序在軟件中完成
在處理程序處理完事件之後,它通過執行一條特殊的從中斷返回指令
可選地返回到被中斷的程序,該指令將適當的狀態彈回到處理器的控制和數據寄存器中
如果異常中斷的是一個用戶程序,就將狀態恢復爲用戶模式,然後將控制返回給被中斷程序

8.1.2 異常的分類

異常可以分爲四類: 中斷( interrupt )、陷阱( trap )、故障( fault )和終止( abort )
在這裏插入圖片描述
1.中斷
中斷是異步發生的,是來自處理器外部的I/O設備的信號的結果
硬件中斷不是由任何一條專門的指令造成的,從這個意義上來說它是異步的
硬件中斷的異常處理程序常常稱爲 中斷處理程序( interrupt handler )
在這裏插入圖片描述

在這裏插入圖片描述
在當前指令完成執行之後,處理器注意到中斷引腳的電壓變高了就從系統總線讀取異常號,然後調用適當的中斷處理程序
在處理程序返回時,它就將控制返回給下一條指令( 也即如果沒有發生中斷,在控制流中會在當前指令之後的那條指令 )

剩下的異常類型( 陷阱、故障、終止) 是同步發生的,是執行當前指令的結果
這類指令叫做故障指令( faulting instruction )

2.陷阱和系統調用
陷阱是有意的異常,是執行一條指令的結果
就像中斷處理程序一樣,陷阱處理程序將控制返回到下一條指令
陷阱最重要的用途是在程序和內核之間提供一個像過程一樣的接口,叫做系統調用

用戶程序經常需要向 內核請求服務,比如讀一個文件(read)、創建一個新的進程(fork)、加載一個新的程序(execve),或者終止當前進程(exit)
爲了允許對這些內核服務的受控的訪問,處理器提供了一條特殊的 “syscall n” 指令,當用戶進程想要請求服務 n 時,可以執行這條指令
執行syscall 指令會導致一個到異常處理程序的陷阱
這個處理程序解析參數,並調用適當的內核程序

在這裏插入圖片描述
從程序員的角度來看,系統調用和普通的函數調用是一樣的
然而,他們的實現非常不同
普通的函數運行在用戶模式中,用戶模式限制了函數可以執行的指令的類型
而且它們只能訪問與調用函數相同的棧

系統調用運行在內核模式中,內核模式允許系統調用執行特權指令,並訪問定義在內核中的棧

3.故障
故障由錯誤情況引起,它可能能夠被故障處理程序修正
當故障發生時,處理器將控制轉移給故障處理程序,如果處理程序能夠修正這個錯誤,它就將控制返回到引起故障的指令,從而重新執行它
否則,處理器返回到內核中的abort例程,abort例程會終止引起故障的應用程序
在這裏插入圖片描述
一個經典的故障示例就是缺頁異常,當指令引用一個虛擬地址,而該地址相對應的物理頁面不在內存中,因此必須從磁盤中取出來,就發生故障
缺頁處理程序從磁盤加載適當的頁面,然後將控制返回給引起故障的指令
當指令再次執行時,相應的物理頁面已經駐留在內存中了,指令就可以沒有故障地運行完成了

4.終止
終止是不可恢復的致命錯誤造成的結果,通常是一些硬件錯誤
在這裏插入圖片描述

8.1.3 Linux/x86-64系統中的異常
有高達256種不同的異常類型
0~31的號碼對應的是由Intel架構師定義的異常,因此對任何x86-64系統都是一樣的
32~255的號碼對應的是操作系統定義的中斷和陷阱
在這裏插入圖片描述
1.Linux/x86-64故障和終止
除法錯誤,當應用程序試圖除以零時,或者當一個除法指令的結果對於目標操作數來說太大了的時候,就會發生除法錯誤( 異常0 )
Unix不會試圖從除法錯誤中恢復,而是選擇終止程序

一般保護故障,許多原因都會導致不爲人知的一般保護故障( 異常13 )
通常是因爲一個程序引用了一個未定義的虛擬內存區域,或者因爲程序試圖寫一個只讀的文本段,Linux不會試圖恢復這類故障

缺頁( 異常14 )是會重新執行產生故障的指令的一個異常示例
處理程序將適當的磁盤上虛擬內存的一個頁面映射到物理內存的一個頁面,然後重新執行這條產生故障的指令

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

2.Linux/x86-64系統調用
在這裏插入圖片描述
Linux提供幾百種系統調用,當應用程序想要請求內核服務時可以使用
包括讀文件、寫文件、創建一個新進程

上圖展示了一些常見的Linux系統調用,每個系統調用都有一個唯一的整數號,對應於一個到內核中跳轉表的偏移量( 這個跳轉表和異常表不一樣
在這裏插入圖片描述
在x86-64系統上,系統調用時通過一條稱爲 syscall陷阱指令來提供的

研究程序能夠 如何 使用這條指令來直接調用Linux系統調用 是很有趣的

所有到Linux系統調用的參數是通過通用寄存器而不是棧傳遞的
按照慣例,寄存器 %rax 包含系統調用號
寄存器 %rdi %rsi %rdx %r10 %r8 %r9 包含最多6個參數
從系統調用返回時,寄存器%rcx %r11 都會被破壞
%rax 包含返回值, -4095 到 -1 之間的負數返回值表明發生了錯誤,對應於負的error

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

8.2 進程

異常是允許操作系統內核提供進程( process )概念的基本構造塊

在現代系統上運行一個程序時,會得到一個假象
好像我們的程序是系統中當前運行的唯一的程序一樣
我們的程序好像獨佔地使用處理器和內存
處理器就好像無間斷地一條接一條地執行我們程序中的指令
最後,我們程序中的代碼和數據好像是系統內存中唯一的對象
這些假象都是通過進程的概念提供給我們的

進程的經典定義就是一個執行中程序的實例
系統中每個程序都運行在某個進程的 上下文( context ) 中
上下文是由程序正確運行所需的狀態組成
這個狀態包括放在內存中的程序的代碼和數據、棧、通用目的寄存器的內容、程序計數器、環境變量以及打開文件描述符的集合

每次用戶通過向shell輸入一個可執行目標文件的名字,運行程序時,shell就會創建一個新的進程,然後在這個新進程的上下文中運行這個可執行目標文件
應用程序也能夠創建新進程,並且在這個新進程的上下文中運行它們自己的代碼或其他程序
在這裏插入圖片描述

8.2.1 邏輯控制流

即使在系統中通常有許多其他程序在運行,進程也可以向每個程序提供一種假象
好像它在獨佔地使用處理器

如果想調試單步執行程序,我們會看到一系列的程序計數器(PC)的值
這些值唯一地對應於包含在程序的可執行目標文件中的指令
或是包含在運行時動態鏈接到程序共享對象中的指令
這個PC值的序列叫做邏輯控制流,簡稱 邏輯流
在這裏插入圖片描述
上圖,考慮一個運行着三個進程的系統

處理器的一個物理控制流被分成了三個邏輯流,每個進程一個
每個豎直的條表示一個進程邏輯流的一部分

在這個例子中,三個邏輯流的執行是交錯
進程A執行了一會兒,然後是進程B開始運行到完成,然後進程C執行了一會兒,進程A接着運行到完成,最後進程C可以運行到結束了

上圖的關鍵點在於進程是輪流使用處理器的
每個進程執行它的流的一部分,然後被搶佔( preempted )( 暫時掛起 ),然後輪到其他進程
對於一個運行在這些進程之一的上下文中的程序,它看上去就像是在獨佔地使用處理器

如果我們精確地測量每條指令使用的時間,會發現在程序中一些指令的執行之間
CPU好像會週期性地停頓,然而,每次處理器停頓,它隨後會繼續執行我們的程序,並不改變程序內存位置或寄存器的內容

8.2.2 併發流

計算機系統中邏輯流有許多不同的形式,異常處理程序、進程、信號處理程序、線程、java進程等 都是邏輯流的例子

一個邏輯流在時間上與另一個流重疊,稱爲 併發流( concurrent flow )
這兩個流被稱爲 併發地運行,流X和Y互相併發,僅當X在Y開始之後和Y結束之前開始
或者Y在X開始之後和X結束之前開始

多個流併發地執行的一般現象稱爲 併發( concurrency ),一個進程和其他進程輪流運行的概念稱爲 多任務( multitasking )
一個進程執行它的控制流的一部分的每一時間段叫做 時間片( time slice )
因此,多任務也叫做時間分片( time slicing ),例如上圖進程A由兩個時間片組成
在這裏插入圖片描述

8.2.3 私有地址空間

進程也爲每個程序提供一種假象,好像它獨佔地使用系統地址空間

進程爲每個程序提供它自己的私有地址空間,和這個空間中某個地址相關聯的那個內存字節不能被其他進程讀或者寫,這個地址空間是私有的

在這裏插入圖片描述
儘管和每個私有地址空間相關聯的內存的內容一般是不同的,但每個這樣的空間都有相同的通用結構

上圖展示了一個x86-64Linux進程的地址空間的組織結構

在這裏插入圖片描述

8.2.4 用戶模式和內核模式

爲了使操作系統內核提供一個無懈可擊的進程抽象,處理器必須提供一種機制
限制一個應用可以執行的指令以及訪問的地址空間範圍

處理器通常是用**某個控制寄存器中的一個 模式位( mode bit )**來提供這種功能,該寄存器描述了進程當前享有的特權,當設置了模式位時,進程就運行在內核模式中( 超級用戶模式 )
一個運行在內核模式的進程可以執行指令集中的任何指令,並且可以訪問系統中的任何內存位置

沒有設置模式位時,進程就運行在用戶模式中
用戶模式中的進程不允許執行特權指令( privileged instruction )
比如停止處理器、改變模式位、發起一個I/O操作
也不允許用戶模式中的進程直接引用地址空間中內核區內的代碼和數據
任何這樣的嘗試,都會導致致命的保護故障
反之,用戶程序必須通過系統調用接口間接地訪問內核代碼和數據

Linux提供了一種機制,叫做 /proc 文件系統,它允許用戶模式進程訪問內核數據結構的內容
/porc 文件系統將許多內核數據結構的內容輸出爲一個用戶程序可以讀的文本文件層次結構
2.6版本的Linux內核引入 /sys 文件系統,它輸出關於系統總線和設備的額外底層信息

8.2.5 上下文切換

操作系統內核使用一種稱爲上下文切換( context switch )的較高層形式的異常控制流來實現多任務
上下文切換機制是建立在 8.1 節討論過的那些較低層異常機制上的

內核爲每個進程維持一個上下文( context )
上下文就是內核重新啓動一個被搶佔式的進程所需的狀態
它由一些對象的值組成,這些對象包括通用目的寄存器、浮點寄存器、程序計數器、用戶棧、狀態寄存器、內核棧和各種內核數據結構,比如描述地址空間的頁表、包含有關當前進程信息的進程表,以及包含進程已打開文件的信息的文件表

在進程執行的某些時刻,內核可以決定搶佔當前進程,並重新開始先前被搶佔了的進程
這種決策就叫做調度( scheduling ),是由內核中稱爲調度器( scheduler )的代碼處理的

當內核選擇一個新的進程運行時,我們說內核調度了這個進程
在內核調度了一個新的進程後,它就搶佔當前進程,並使用一種稱爲上下文切換的機制來將控制轉移到新的進程

上下文切換
1.保存當前進程的上下文
2.恢復某個先前被搶佔的進程被保存的上下文
3.將控制傳遞給這個新恢復的進程

在這裏插入圖片描述
在這裏插入圖片描述
上圖展示了一對進程A和B上下文切換的示例

在這裏插入圖片描述

8.4 進程控制

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

8.4.1 獲取進程ID

每個進程都有一個唯一的正數( 非零 )進程ID( PID )
Getpid 函數返回調用進程的PID
Getppid 函數返回它的父進程PID ( 創建調用進程的進程 )
在這裏插入圖片描述
上面兩個函數返回一個類型爲pid_t 的整數值,Linux系統上它在 types.h 中被定義爲int

8.4.2 創建和終止進程

在這裏插入圖片描述
在這裏插入圖片描述
Exit函數以status退出狀態來終止進程 ( 另一種設置退出狀態的方法是從主程序中返回一個整數值 )

父進程通過調用fork函數創建一個新的運行的子進程
在這裏插入圖片描述
新創建的子進程幾乎但不完全與父進程相同
子進程得到與父進程用戶級虛擬地址空間相同的(但是獨立的)一份副本
包括代碼和數據、堆、共享庫以及用戶棧
子進程還獲得與父進程任何打開文件描述符相同的副本,意味着當父進程調用fork時,子進程可以讀寫父進程中打開的任何文件
父進程和新創建的子進程之間最大的區別在於他們有不同的PID

在這裏插入圖片描述
在這裏插入圖片描述
上圖展示了一個使用fork創建子進程的父進程

當fork調用在第6行返回時,在父進程和子進程中x的值都爲1
子進程在第8行加一併輸出它的x副本
父進程在第13行減一併輸出它的x的副本
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
上圖展示了前面示例程序的進程圖

初始時,父進程將變量x設置爲1,父進程調用fork 創建一個子進程,他在自己的私有空間中與父進程併發執行

嵌套:
在這裏插入圖片描述

8.4.3 回收子進程

當一個進程由於某種原因終止時,內核並不是立即把它從系統中清除
相反,進程在一種已終止的狀態中,直到被它的父進程 回收( reaped )
當父進程回收已終止的子進程時,內核將子進程的退出狀態傳遞給父進程,然後拋棄已終止的進程,從此刻開始,該進程就不存在了

一個終止了但還未被回收的進程稱爲 僵死進程( zombie )
在這裏插入圖片描述
如果一個父進程終止了,內核會安排init進程成爲它的孤兒進程的養父
Init進程的PID爲1,是在系統啓動時由內核創建的,它不會終止,是所有進程的養父
即使僵死子進程沒有運行,他們仍然消耗系統的內存資源

一個進程可以通過調用waitpid函數來等待它的子進程終止或者停止
在這裏插入圖片描述
在這裏插入圖片描述
1.判定等待集合的成員
等待集合的成員是由參數pid來確定的
在這裏插入圖片描述
2.修改默認行爲
可以通過將options設置爲常量 WNOHANG、WUNTRACED、WCONTINUED
各種組合來修改默認行爲
在這裏插入圖片描述
3.檢查已回收子進程的退出狀態
如果statusp參數是非空的,那麼waitpid就會在status中放上關於導致返回的子程序的狀態信息,status是statusp指向的值,wait.h 頭文件定義瞭解釋status參數的幾個宏
在這裏插入圖片描述
4.錯誤條件
如果調用進程沒有子進程,那麼waitpid返回-1,並且設置errno爲ECHILD
如果waitpid函數被一個信號中斷,那麼它返回-1,並設置errno爲EINTR
在這裏插入圖片描述
在這裏插入圖片描述
5.wait函數
Wit函數是waitoid函數的簡單版本
在這裏插入圖片描述
調用wiat(&status)等價於調用waitpid(-1, &status, 0)

8.4.4 讓進程休眠

Sleep函數將一個進程掛起一段時間
在這裏插入圖片描述
如果請求的時間量已經到了,sleep返回0,否則返回還剩下的要休眠的秒數
後一種情況是可能的,如果因爲sleep函數被一個信號中斷而過早地返回

另一個很有用的函數是pause函數,該函數讓調用函數休眠,直到該進程收到一個信號
在這裏插入圖片描述

8.4.5 加載並運行程序

Execve函數在當前進程的上下文中加載並運行一個新程序
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
參數列表由上圖的數據結構表示的
Argv[0]變量指向一個以null結尾的指針數組,其中每個指針都指向一個參數字符串
按照慣例,argv[0]是可執行目標文件的名字

在這裏插入圖片描述
環境變量的列表是由上圖數據結構表示的,和參數列表類似
其中每個指針指向一個環境變量字符串,每個串都是形如 “name=value” 的 名字-值

在execve加載了filename之後,啓動代碼 (__libc_start_main)(7.9章節描述)設置棧,並將控制傳遞給新程序的主函數,該主函數有如下形式的原型:

在這裏插入圖片描述
在這裏插入圖片描述
Main函數執行時,用戶棧的組織結構 上圖展示

從 棧底(高地址) 往 棧頂(低地址) 依次看一看
首先是參數和環境字符串,棧往上緊隨其後是以null結尾的指針數組
其中每個指針都指向棧中的一個環境變量字符串
全局變量 environ 指向這些指針中的第一個 envp[0]
緊隨環境變量數組之後的是以null結尾的argv[ ] 數組,其中每個元素都指向棧中的一個參數字符串,在棧的頂部是系統啓動函數 libc_start_main的棧幀

Main函數有3個參數:

  1. argc,它給出argv[ ]數組中非空指針的數量
    2.argv,指向argv[ ]數組中的第一個條目
    3.envp,指向envp[ ]數組中的第一個條目

Linux提供了幾個函數來操作環境數組:
在這裏插入圖片描述
在這裏插入圖片描述

8.4.6 利用fork和execve運行程序

Shell是一個交互型的應用級程序,它代表用戶運行其他程序

展示一個簡單shell的main例程
Shell打印一個命令行提示符,等待用戶在stdin上輸入命令行,然後對這個命令行求值
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

8.5 信號

到目前爲止,已經看到了軟件和硬件是如何合作以及提供基本的底層異常機制的
也看到了操作系統如何利用異常來支持上下文切換的異常控制流形式

Linux信號,它允許進程和內核中斷其他進程

在這裏插入圖片描述
上圖展示了Linux系統上支持的30種不同類型的信號

一個信號就是一條小消息它通知進程系統中發生了一個某種類型的事件
每種信號類型對應於某種系統事件,底層的硬件異常是由內核異常處理程序處理的
正常情況下,對用戶進程而言是不可見的

信號提供了一種機制,通知用戶進程發生了這些異常
在這裏插入圖片描述

8.5.1 信號術語

傳送一個信號到目的的進程由兩種不同步驟組成的:
在這裏插入圖片描述
在這裏插入圖片描述

一個發出而沒有被接收的信號叫做 待處理信號( pending signal )
在任何時刻,一種類型至多隻會有一個待處理信號
如果一個進程有一個類型爲k的待處理信號,那麼任何接下來發送到這個進程的類型爲k的信號都不會排隊等待,它們只是被簡單的丟棄

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

一個待處理信號最多隻能被接收一次。內核爲每個進程在pending位向量中維護着待處理信號的集合,而在blocked位向量中維護着被阻塞的信號集合
只要傳送了一個類型爲k的信號,內核就會設置pending中的第k位,而只要接收了一個類型爲k的信號,內核就會清除pending中的第k位

##8.5.2 發送信號
Unix系統提供了大量向進程發送信號的機制,所有這些機制都是基於進程組( process group )
這個概念的

1.進程組
每個進程都只屬於一個進程組,進程組是由一個正整數進程組ID來標識的
Getpgrp函數返回當前進程的進程組ID
在這裏插入圖片描述
默認地,一個子進程和它的父進程同屬於一個進程組

一個進程可以通過使用 setpgid函數來改變自己或者其他進程的進程組
在這裏插入圖片描述
Setpgid函數將進程pid的進程組改爲pgid,如果pid是0,那麼久使用當前進程的PID,如果pgid是0,那麼就用pid指定的進程的pid做爲進程組ID

2.用 /bin/kill 程序發送信號
在這裏插入圖片描述

3.從鍵盤發送信號

Unix shell 使用作業( job) 這個抽象概念來表示爲 對一條命令行求值而創建的進程
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

4.用kill函數發送信號
進程通過調用kill函數發送信號給其他進程( 包括它們自己 )
在這裏插入圖片描述
如果pid大於零,那麼kill函數發送信號號碼sig給進程pid
如果pid等於零,那麼kill發送信號sig給調用進程所在進程組中的每個進程,包括自己
如果pid小於零,kill發送信號sig給進程組 pid的絕對值 中的每個進程
在這裏插入圖片描述
上圖展示了 父進程用kill函數發送SIGKILL信號給它的子進程

5.用alarm函數發送信號
進程可以通過調用alarm函數向它自己發送SIGALRM信號
在這裏插入圖片描述
Alarm函數安排內核在secs秒後發送一個SIGALRM信號給調用進程

8.5.3 接收信號

在這裏插入圖片描述
進程可以通過使用signal函數修改和信號相關聯的默認行爲,唯一的例外就是SIGSTOP SIGKILL 它們的默認行爲是不能修改的
在這裏插入圖片描述
在這裏插入圖片描述
當一個進程捕獲了一個類型爲k的信號時,會調用爲信號k設置的處理程序,一個整數參數被設置爲k,這個參數允許同一處理函數捕獲不同類型的信號

在這裏插入圖片描述
上圖展示了 捕獲用戶在鍵盤上輸入Ctrl + C時發送的SIGINT信號
默認行爲是立即終止該進程,我們將默認行爲修改爲捕獲信號,輸出一條信息,然後終止

信號處理程序可以被其他信號處理程序中斷
在這裏插入圖片描述

8.5.4 阻塞和接觸阻塞信號

Linux 提供阻塞信號的隱式和顯式的機制
在這裏插入圖片描述
在這裏插入圖片描述

8.6 非本地跳轉

C語言提供了一種用戶級異常控制流形式,稱爲非本地跳轉( nonlocal jump )
它將控制直接從一個函數轉移到另一個當前正在執行的函數,而不需要經過正常的調用-返回序列

非本地跳轉是通過setjmp和longjmp函數來提供的
在這裏插入圖片描述
Setjmp函數在env緩衝區中保存當前調用環境,以供後面的longjmp使用,並返回0
調用環境包括程序計數器、棧指針和通用目的寄存器
在這裏插入圖片描述
Longjmp函數從env緩衝區中恢復調用環境

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

8.7 操作進程的工具

在這裏插入圖片描述

小結

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章