20145227《資訊保安系統設計基礎》第十一週學習總結

20145227鄢曼君發表於2016-11-27

20145227《資訊保安系統設計基礎》第十一週學習總結

第八章 異常控制流

  • 控制轉移序列稱為控制流。
  • 從從一條指令到下一條指令稱為轉移控制。
  • 異常控制流:現代作業系統通過使控制流發生突變來對系統狀態做出反應,這些突變稱為異常控制流。
  • 異常控制流ECF:即這些突變。
1.ECF是作業系統用來實現I/O、程式和虛擬存器的基本機制
2.應用程式通過使用一個叫做陷阱或者系統呼叫的ECF形式,向作業系統請求服務
3.ECF是計算機系統中實現併發的基本機制
4.軟體異常機制——C++和Java有try,catch,和throw,C中非本地跳轉是setjmp和longjmp

異常

  • 異常是異常控制流的一種形式,一部分由由硬體實現,一部分由作業系統實現。由於系統的不同而有所不同。
  • 異常就是控制流的突變

1.異常處理

  • 異常號:系統為每種型別的異常分配的唯一的非負整數。
  • 異常表:系統啟動時作業系統就會初始化一張條轉變,使得條目k包含異常k的處理程式的地址。
  • 關係:異常號是到異常表中的索引,異常表的起始地址放在異常表基址暫存器。
  • 異常類似於過程呼叫,區別在:
1.處理器壓入棧的返回地址,是當前指令地址或者下一條指令地址。
2.處理器也把一些額外的處理器狀態壓到棧裡
3.如果控制一個使用者程式到核心,所有專案都壓到核心棧裡。
4.異常處理程式執行在核心模式下,對所有的系統資源都有完全的訪問許可權。

2.異常的類別

  • 分為四種:中斷、陷阱、故障和終止。

20145227《資訊保安系統設計基礎》第十一週學習總結

(1)中斷

  • 非同步發生
  • 來自處理器外部的I/O裝置的訊號的結果
  • 將控制返回給下一條指令

(2)陷阱和系統呼叫

  • 陷阱是有意的異常
  • 是執行一條指令的結果
  • 最重要的用途:在使用者和核心間提供一個像過程一樣的介面,叫系統呼叫

(3)故障

  • 由錯誤狀況引起,可能能夠被故障處理程式修正
  • 故障發生時,處理器將控制轉移給故障處理程式,如果能夠修正,返回引起故障的指令,重新執行指令,否則返回abort例程,終止

(4)終止

  • 是不可恢復的致命錯誤造成的結果
  • 通常是一些硬體錯誤
  • 終止示例:將控制返回abort例程,如圖:

20145227《資訊保安系統設計基礎》第十一週學習總結

3.Linux/IA32系統中的異常

高達256種異常型別,如圖:

20145227《資訊保安系統設計基礎》第十一週學習總結

(1)Linux/IA32故障和終止

  • 除法錯誤/浮點異常:異常0,選擇中止程式
  • 一般保護故障/段故障:異常13,選擇中止程式
  • 缺頁:異常14,重新執行產生故障的指令
  • 機器檢查:異常18,不返回控制給應用程式

(2)Linux/IA32系統呼叫

  • 系統呼叫示例,如圖9所示:

20145227《資訊保安系統設計基礎》第十一週學習總結

程式

  • 在作業系統層:邏輯控制流,私有地址空間,多工,併發,並行,上下文,上下文切換,排程。
  • 程式就是一個執行中的程式例項。系統中的每個程式都是執行在某個程式的上下文中的。

  • 程式提供給應用程式的關鍵抽象:a)一個獨立的邏輯控制流 ;b)一個私有的地址空間

1.邏輯控制流

  • 一系列的程式計數器(PC)的值,這些值唯一地對應於包含在程式的可執行目標檔案中的指令,或者是包含在執行時動態連結到程式的共享物件中的指令,這個PC值的序列就叫做邏輯控制流,或者簡稱邏輯流

  • 程式是輪流使用處理器的。每個程式執行它的流的一部分,然後被搶佔(暫時掛起),然後輪到其他程式。對於執行在改程式上下文的其他程式,它看上去在獨佔的使用處理器。

2.併發流

  • 併發流:併發流一個邏輯流的執行在時間上與另一個流重疊
  • 併發:多個流併發執行的一般現象稱為併發。
  • 多工:多個程式併發叫做多工。
  • 並行:併發流在不同的cpu或計算機上。

3.私有地址空間

  • 一個程式為每個程式提供它自己的私有地址空間。
  • 執行應用程式程式碼的程式初始時是在使用者模式中的。程式從使用者模式變為核心模式的唯一方法是通過異常。
  • 程式地址空間,如圖11所示

20145227《資訊保安系統設計基礎》第十一週學習總結

4.使用者模式和核心模式

  • 需要限制一個應用可以執行的指令以及可訪問的地址空間範圍來實現程式抽象,通過特定控制暫存器的一個模式位來提供這種機制。設定了模式位時,程式執行在核心模式中,程式可以執行任何指令和訪問任何儲存器位置。沒設定模式位時,程式執行在使用者模式中,程式不允許執行特權指令和訪問地址空間中核心區內的程式碼和資料。使用者程式必須通過系統呼叫介面間接地訪問核心程式碼和資料。

  • 使用者程式的程式初始是在使用者模式中的,必須通過中斷、故障或陷入系統呼叫這樣的異常來變為核心模式。
  • Linux的聰明機制——/proc檔案系統,包含核心資料結構的內容的可讀形式,執行使用者模式程式訪問。

5.上下文切換

  • 上下文切換:作業系統核心使用叫上下文切換的異常控制流來實現多工。
  • 上下文切換機制:
    (1)儲存當前程式的上下文;
    (2)恢復某個先前被搶佔的程式被儲存的上下文;
    (3)將控制傳遞給這個新恢復的程式

  • 排程:核心中的排程器實現排程。

  • 當核心代表使用者執行上下文切換時,可能會發生上下文切換。

  • 如果系統呼叫發生阻塞,那麼核心可以讓當前程式休眠,切換到另一個程式,如read系統呼叫,或者sleep會顯示地請求讓呼叫程式休眠。一般,即使系統呼叫沒有阻塞,核心亦可以決定上下文切換,而不是將控制返回給呼叫程式。

系統呼叫錯誤處理

  • 在Linux中,可以使用 man syscalls 檢視全部系統呼叫的列表。
  • 系統級函式遇到錯誤時,通常返回-1,並設定全域性變數 errno 。

程式控制

1.獲取程式ID

    #include <sys/types.h>
    #include <unistd.h>
    pid_t getpid(void); /*返回撥用程式的PID*/
    pid_t getppid(void);    /*返回它的父程式的PID(建立呼叫程式的程式)*/

2.建立和終止程式

  • 程式的三種狀態——執行、停止和終止。
  • 執行:要麼在CPU上執行,要麼在等待被執行,且最終被核心排程。
  • 停止:程式的執行被掛起,且不會被排程。收到 SIGSTOP 、 SIGTSTP 、 SIDTTIN 、 SIGTTOU 訊號,程式停止,收到 SIGCONT 訊號,程式再次開始執行。
  • 終止:永遠停止。原因可能是:收到終止程式的訊號,從主程式返回,呼叫 exit 函式。

建立程式

  • 父程式通過呼叫fork建立一個新的執行子程式:父程式與子程式有相同(但是獨立的)地址空間,有相同的檔案藐視符集合。

  • fork函式定義如下:

    #include <sys/types.h>
    #include <unistd.h>    
    pid_t fork(void);
  • fork 函式呼叫一次,返回兩次;父子程式是併發執行的,不能假設它們的執行順序;兩個程式的初始地址空間相同,但是是相互獨立的;它們還共享開啟的檔案。因為有相同的程式程式碼,所以如果呼叫 fork 三次,就會有八個程式。

3.回收子程式

  • 當一個程式終止時,核心並不立即把它從系統中清除。相反,程式被保持在一種已終止的狀態中,直到被它的父程式回收。

  • 僵死程式:一個終止了但是還未被回收的程式稱為僵死程式。父程式回收終止的子程式時,核心將子程式退出狀態傳給父程式,然後拋棄該程式。如果回收前父程式已經終止,那麼僵死程式由 init 程式回收。

  • 一個程式可以通過呼叫waitpid函式來等待它的子程式終止或停止。

  • waitpid函式的定義如下:

    #include <sys/types.h>
    #include <sys/wait.h>
    
    pid_t waitpid(pid_t pid, int *status, int options);

(1)判斷等待集合的成員

  • 等待集合的成員由引數pid來確定:
       如果pid>0:等待集合是一個單獨子程式,程式ID等於pid
       如果pid=-1:等待集合是由父程式所有的子程式組成

(2)修改預設行為

  • 將options設定為常量WNOHANG和WUNTRACED的各種組合,修改預設行為:

20145227《資訊保安系統設計基礎》第十一週學習總結

(3)檢查已回收子程式的退出狀態——status

20145227《資訊保安系統設計基礎》第十一週學習總結

(4)讓程式休眠

  • sleep函式使一個程式掛起一段指定的時間。
    #include <unistd.h>    
    unsigned int sleep(unsigned int secs);
  • pause函式讓呼叫函式休眠
    #include <unistd.h>    
    int pause(void);

(5)載入並執行程式

  • filename:可執行目標檔案

  • argv:引數列表

  • envp:環境列表

20145227《資訊保安系統設計基礎》第十一週學習總結

20145227《資訊保安系統設計基礎》第十一週學習總結

訊號

  • 在作業系統和應用程式之間:程式之間傳送訊號
  • 一種更高層次的軟體形式的異常,稱為unix訊號,它允許程式中斷其他程式。

1.訊號術語

(1)傳送訊號

  • 傳送訊號:/bin/kill , kill函式,鍵盤,alarm函式

  • 用kill函式傳送訊號:傳送SIGKILL訊號

  • 用alarm函式傳送訊號:傳送SOGALARM訊號

(2)接收訊號

  • 程式可以通過使用signal函式來修改和訊號相關的預設行為。唯一的例外是SIGSTOP和SIGKILL,它們的預設行為不能被修改。

2.傳送訊號

(1)程式組

  • 每個程式都只屬於一個程式組。
  • 程式組是由一個正整數程式組ID來標識的。
  • getpgrp函式返回當前程式組id
  • setpgid函式修改自己或其他程式組

(2)用/bin/kill程式傳送訊號

  • /bin/kill程式可以向另外的程式傳送任意的訊號,比如 /bin/kill -9 15213 即為:傳送訊號9給程式15213

(3)從鍵盤傳送訊號

  • 在任何時刻,至多隻有一個前臺作業和0個或多個後臺作業。外殼為每個作業建立一個獨立的程式組,一個作業對應一個程式組。

(4)用kill函式傳送訊號

  • 傳送SIGKILL訊號

(5)用alarm函式傳送訊號

  • 傳送SOGALARM訊號

3.接收訊號

  • 當核心從一個異常處理程式返回,準備將控制傳遞給程式P時,他會檢查程式P的未被阻塞的處理訊號的集合。如果這個集合為空,那麼核心將控制傳遞到P的邏輯控制流中的下一條指令;如果集合是非空的,那麼核心選擇集合中的某個訊號K(通常是最小的K0,並且強制P接收訊號K。收到這個訊號會觸發程式的某種行為。一旦程式完成了這個行為,那麼控制就傳遞迴P的邏輯控制流中的下一條指令。

每個訊號型別都有一個預定的預設行為:

  • 程式終止

  • 程式終止並轉儲儲存器

  • 程式停止直到被SIGCONT型號重啟

  • 程式忽略該訊號

4.訊號處理問題

當一個程式要捕獲多個訊號時,一些細微的問題就產生了。

  • 待處理訊號被阻塞。Unix訊號處理程式通常會阻塞當前處理程式正在處理的型別的待處理訊號。

  • 待處理訊號不會排隊等待。任意型別至多隻有一個待處理訊號。因此,如果有兩個型別為K的訊號傳送到一個目的程式,而由於目的程式當前正在執行訊號K的處理程式,所以訊號K時阻塞的,那麼第二和訊號就簡單地被簡單的丟棄,他不會排隊等待。

  • 系統呼叫可以被中斷。像read、wait和accept這樣的系統呼叫潛在地會阻塞程式一段較長的時間,稱為慢速系統呼叫。在某些系統中,當處理程式捕獲到一個訊號時,被中斷的慢速系統呼叫在訊號處理程式返回時不再繼續,而是立即返回給使用者一個錯誤的條件,並將errno設定為EINTR。

5.可移植的訊號處理

Signal包裝函式設定的訊號處理程式的訊號處理語義:

  • 只有這個處理程式當前正在處理的那種型別的訊號被阻塞

  • 和所有訊號實現一樣,訊號不會排隊等候

  • 只要有可能,被中斷的系統呼叫會自動重啟。

  • 一旦設定了訊號處理程式,它就會一直保持,知道signal帶著handler引數為SIG_IGN或者SIG_DFL被呼叫。

6.顯式地阻塞和取消阻塞訊號

7.同步流以避免討厭的併發錯誤

  • 以某種方式同步並交流,從而得到最大的可行的交錯的集合,每個可行的交錯都能得到正確的結果。

非本地跳轉

  • c語言中,使用者級的異常控制流形式,通過setjmp和longjmp函式提供。

  • setjump函式在env緩衝區中儲存當前呼叫環境,以供後面longjmp使用,並返回0.
  • longjmp函式從env緩衝區中恢復呼叫環境,然後觸發一個從最近一次初始化env的setjmp呼叫的返回。然後setjmp返回,並帶有非零的返回值retval。

       setjmp函式只被呼叫一次,但返回多次;
       longjmp函式被呼叫一次,但從不返回。

操作程式的工具

Linux系統提供了大量的監控和操作程式的有用工具:

  • STRACE:列印一個正在執行的程式和它的子程式呼叫的每個系統呼叫的軌跡。對於好奇的的工具。用-StatiC編譯你的程式,能傳到一個更乾淨的、不帶學生而言,這是一個令人著迷有大量與共享庫相關的輸出的軌跡。

  • PS:列出當前系統中的程式(包括僵死程式)

  • TOP:列印出關於當前程式資源使用的資訊。

  • PMAP:顯示程式的儲存器對映。proc:一個虛擬檔案系統,以ASCII文字格式輸出大量核心數資料結構的內容,使用者程式可 cat 2 / proc / load avg” , 觀察在Linux系統上的平均負載。

程式碼的除錯和分析

exec1

  • 程式碼如下:
#include <stdio.h>
#include <unistd.h>

int main()
{
    char    *arglist[3];

    arglist[0] = "ls";
    arglist[1] = "-l";
    arglist[2] = 0 ;//NULL
    printf("* * * About to exec ls -l\n");
    execvp( "ls" , arglist );
    printf("* * * ls is done. bye");

    return 0;
}
  • 可以看到這個程式碼中用了execvp函式。

  • 表標頭檔案:#include<unistd.h>

  • 定義函式:int execvp(const char file ,char const argv []);

  • execvp()會從PATH 環境變數所指的目錄中查詢符合引數file 的檔名,找到後便執行該檔案,然後將第二個引數argv傳給該欲執行的檔案。
  • 如果執行成功則函式不會返回,執行失敗則直接返回-1,失敗原因存於errno中。

  • 執行結果如下:

20145227《資訊保安系統設計基礎》第十一週學習總結

  • 從執行結果可以看到,exevp函式呼叫成功沒有返回,所以沒有列印出“* * * ls is done. bye”這句話。

exec2

  • 它與exec1的區別就在於exevp函式的第一個引數,exec1傳的是ls,exec2直接用的arglist[0],不過由定義可得這兩個等價,所以執行結果是相同的。

exec3

  • 這個程式碼裡使用了execlp函式,用法如下:
  • 標頭檔案:#include<unistd.h>

  • 定義函式:int execlp(const char * file,const char * arg,....);

  • 函式說明:execlp()會從PATH 環境變數所指的目錄中查詢符合引數file的檔名,找到後便執行該檔案,然後將第二個以後的引數當做該檔案的argv[0]、argv[1]……,最後一個引數必須用空指標(NULL)作結束。如果用常數0來表示一個空指標,則必須將它強制轉換為一個字元指標,否則將它解釋為整形引數,如果一個整形數的長度與char * 的長度不同,那麼exec函式的實際引數就將出錯。如果函式呼叫成功,程式自己的執行程式碼就會變成載入程式的程式碼,execlp()後邊的程式碼也就不會執行了.

  • 返回值:如果執行成功則函式不會返回,執行失敗則直接返回-1,失敗原因存於errno 中。也就是說,這個程式碼指定了環境變數,然後依然執行了ls -l指令,成功後沒有返回,所以最後一句話不會輸出。執行結果同exec1.

forkdemo1

  • 程式碼分析:這個程式碼先是列印程式pid,然後呼叫fork函式生成子程式,休眠一秒後再次列印程式id,這時父程式列印子程式pid,子程式返回0.
  • 執行結果如下:

20145227《資訊保安系統設計基礎》第十一週學習總結

forkdemo2

  • 程式碼分析:這個程式碼呼叫兩次fork,一共產生四個子程式,所以會列印四個aftre輸出。

  • 結果如圖:

20145227《資訊保安系統設計基礎》第十一週學習總結

forkdemo3

  • 程式碼分析:fork產生子程式,父程式返回子程式pid,不為0,所以輸出父程式的那句話,子程式返回0,所以會輸出子程式那句話。
  • 結果如下:

20145227《資訊保安系統設計基礎》第十一週學習總結

forkdemo4

  • 程式碼分析:先列印程式pid,然後fork建立子程式,父程式返回子程式pid,所以輸出parent一句,休眠十秒;子程式返回0,所以輸出child與之後一句。

  • 執行結果如下:

20145227《資訊保安系統設計基礎》第十一週學習總結

forkgdb

  • 程式碼分析:這個的主要區別是在,父程式列印是先列印兩句,然後休眠一秒,然後列印一句,子程式先列印一句,然後休眠一秒,然後列印兩句。並且這兩個執行緒是併發的,所以可以看到在一個執行緒休眠的那一秒,另一個執行緒在執行,並且執行緒之間相互獨立互不干擾。
  • 執行結果:

20145227《資訊保安系統設計基礎》第十一週學習總結

psh1

  • 程式碼分析:這個程式碼就相當於你輸入要執行的指令,回車表示輸入結束,然後輸入的每個引數對應到函式中,再呼叫對應的指令。
  • 執行結果:

20145227《資訊保安系統設計基礎》第十一週學習總結

psh2

  • 比起psh1來,多了迴圈判斷,不退出的話就會一直要你輸入指令。
  • 執行結果如下:

20145227《資訊保安系統設計基礎》第十一週學習總結

testbuf1:

  • 效果是先輸出hello,然後換行。之後不退出。
  • 執行結果:

20145227《資訊保安系統設計基礎》第十一週學習總結

testbuf2

  • 效果同上。
  • 由此可知:fflush(stdout)的效果和換行符\n是一樣的。

testbuf3

  • 程式碼分析:將內容格式化輸出到標準錯誤、輸出流中。
  • 結果如圖:

20145227《資訊保安系統設計基礎》第十一週學習總結

testpid

  • 程式碼分析:輸出當前程式pid和當前程式的父程式的pid。
  • 執行結果:

20145227《資訊保安系統設計基礎》第十一週學習總結

testsystem

  • 程式碼分析:system()——執行shell命令,也就是向dos傳送一條指令。這裡是後面可以跟兩個引數,然後向dos傳送這兩個命令,分別執行。如下圖,輸入ls和dir兩個指令後,可以看到分別執行了。
  • 執行結果:

20145227《資訊保安系統設計基礎》第十一週學習總結

waitdemo1

  • 程式碼分析:如果有子程式,則終止子程式,成功返回子程式pid。
  • 執行結果:

20145227《資訊保安系統設計基礎》第十一週學習總結

waitdemo2

  • 程式碼分析:這個比起waitdemo1來就是多了一個子程式的狀態區分,把狀態拆分成三塊,exit,sig和core。
  • 執行結果如下:

20145227《資訊保安系統設計基礎》第十一週學習總結

本週程式碼託管連結

https://git.oschina.net/20145227/IS-Design-20145227/tree/master/ch11

20145227《資訊保安系統設計基礎》第十一週學習總結

本週程式碼總數

20145227《資訊保安系統設計基礎》第十一週學習總結

學習進度條

程式碼行數(新增/累積) 部落格量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 0 2/2 20/20
第二週 100/100 1/3 20/40
第三週 200/300 1/4 22/62
第五週 200/500 1/5 22/84
第六週 274/774 1/6 22/106
第七週 127/901 2/8 22/128
第八週 50/951 2/10 22/150
第九周 418/1369 2/12 22/172
第十週 485/1854 2/14 22/194
第十一週 628/2482 3/17 32/226

參考資料

相關文章