20145216史婧瑤《資訊保安系統設計基礎》第十一週學習總結
教材內容總結
第八章 異常控制流
- 平滑:指在儲存器中指令都是相鄰的。
- 突變:出現不相鄰,通常由諸如跳轉、呼叫、和返回等指令造成。 異常控制流ECF:即這些突變。
關於ECF:
1.ECF是作業系統用來實現I/O、程式和虛擬存器的基本機制
2.應用程式通過使用一個叫做陷阱或者系統呼叫的ECF形式,向作業系統請求服務
3.ECF是計算機系統中實現併發的基本機制
4.軟體異常機制——C++和Java有try,catch,和throw,C中非本地跳轉是setjmp和longjmp
第一節 異常
異常是異常控制流的一種形式,由硬體和作業系統實現。簡單來說,就是控制流中的突變。
事件:即狀態變化,與當前指令的執行可能直接相關,也可能沒有關係。
出現異常的處理方式:
1.處理器檢測到有異常發生
2.通過異常表,進行間接過程呼叫,到達異常處理程式
3.完成處理後:返回給當前指令;返回給下一條指令;終止
1.異常處理
- 異常號:系統為每種型別的異常分配的唯一的非負整數。
- 異常表:系統啟動時作業系統就會初始化一張條轉變,使得條目k包含異常k的處理程式的地址。
- 關係:異常號是到異常表中的索引,異常表的起始地址放在異常表基址暫存器。
異常類似於過程呼叫,區別在:
1.處理器壓入棧的返回地址,是當前指令地址或者下一條指令地址。
2.處理器也把一些額外的處理器狀態壓到棧裡
3.如果控制一個使用者程式到核心,所有專案都壓到核心棧裡。
4.異常處理程式執行在核心模式下,對所有的系統資源都有完全的訪問許可權。
2.異常的類別
- 故障指令:執行當前指令導致異常
- 中斷處理程式:硬體中斷的異常處理程式。
(1)中斷
- 非同步發生
- 來自處理器外部的I/O裝置的訊號的結果
- 返回下一條指令
(2)陷阱
- 陷阱是有意的異常
- 是執行一條指令的結果
- 最重要的用途——系統呼叫
(3)故障
- 由錯誤狀況引起,可能能夠被故障處理程式修正
- 結果要麼重新執行指令(就是返回當前指令地址),要麼終止
- 典型示例:缺頁異常
(4)終止
- 是不可恢復的致命錯誤造成的結果
- 通常是一些硬體錯誤
3.Linux/IA32系統中的異常
一共有256種不同的異常型別。
(1)Linux/IA32故障和終止
- 除法錯誤/浮點異常 異常0 終止程式
- 一般保護故障/段故障 異常13 終止程式
- 缺頁 異常14 返回當前地址
- 機器檢查 異常18 終止程式
(2)Linux/IA32系統呼叫
每一個系統呼叫都有一個唯一的整數號,對應於一個到核心中跳轉表的偏移量。
系統呼叫的實現方法:
在IA32中,系統呼叫通過一條陷阱指令提供:
int n;//n為異常號
所有的到Linux系統呼叫的引數都是通過暫存器傳遞的。慣例如下:
- %eax:包含系統呼叫號
- %ebx,%ecx,%edx,%esi,%edi,%ebp:包含最多六個任意引數
- %esp:棧指標,不能使用
第二節 程式
- 程式的經典定義:一個執行中的程式的例項。
- 系統中的每個程式都是執行在某個程式的上下文中的。
- 上下文:由程式正確執行所需的狀態組成的。
-
程式提供給應用程式的關鍵抽象:
一個獨立的邏輯控制流:獨佔的使用處理器 一個私有的地址空間:獨佔的使用儲存器系統
1.邏輯控制流
(1)含義
一系列的程式計數器PC的值,分別唯一的對應於包含在程式的可執行目標檔案中的指令,或者是包含在執行時動態連結到程式的共享物件中的指令,這個PC值的序列就叫做邏輯控制流。
(2)關鍵點
程式是輪流使用處理器的。每個程式執行它的流的一部分,然後被搶佔,然後輪到其他程式。但是程式可以向每個程式提供一種假象,好像它在獨佔的使用處理器。
(3)邏輯流示例
異常處理程式、程式、訊號處理程式、執行緒、Java程式
2.併發流
(1)含義
一個邏輯流的執行在時間上與另一個流重疊。【與是否在同一處理器無關】
這兩個流併發的執行。
(2)概念
- 併發:多個流併發的執行
- 多工:一個程式和其他程式輪流執行(也叫時間分片)
- 時間片:一個程式執行它的控制流的一部分的每一時間段
(3)並行
兩個流併發的執行在不同的處理機核或者計算機上。
並行流並行的執行,並行的執行。
3.私有地址空間
程式為程式提供的假象,好像它獨佔的使用系統地址空間。一般而言,和這個空間中某個地址相關聯的那個儲存器位元組是不能被其他程式讀寫的。
4.使用者模式和核心模式
簡單的說,使用者模式和核心模式的區別就在於使用者的許可權上,許可權指的是對系統資源使用的許可權。
具體的區別是有無模式位,有的話就是核心模式,可以執行指令集中的所有指令,訪問系統中任何儲存器位置;沒有就是使用者模式。
程式從使用者模式變為核心模式的唯一方法是通過異常——中斷,故障,或者陷入系統呼叫。
Linux的聰明機制——/proc檔案系統,將許多核心資料結構的內容輸出為一個使用者程式可以讀的文字檔案的層次結構。
5.上下文切換
作業系統核心使用上下文切換這種較高層形式的異常控制流來實現多工。上下文切換機制建立在較底層異常機制之上。
(1)上下文:核心重新啟動一個被搶佔的程式所需的狀態。
由一些物件的值組成:
- 通用目的暫存器
- 浮點暫存器
- 程式計數器
- 使用者棧
- 狀態暫存器
- 核心棧
- 核心資料結構:頁表、程式表、檔案表
(2)排程和排程器
在程式執行的某些時刻,核心可以決定搶佔當前程式,並重新開始一個先前被搶佔的程式。這種決定叫做排程,是由核心中稱為排程器的程式碼處理的。
(3)上下文切換機制
1.儲存當前程式的上下文
2.恢復某個先前被搶佔的程式被儲存的上下文
3.將控制傳遞給這個新恢復的程式。
(4)可能發生上下文切換的原因:
- 核心代表使用者執行系統呼叫時
- 中斷
第三節 系統呼叫錯誤處理
這一節主要是對附錄A的內容的重複解釋,在第十週已經學習過。
簡單總結就是,系統會使用錯誤處理包裝函式,系統級函式是小寫,他們的包裝函式名大寫,包裝函式呼叫基本函式,有任何問題就終止,如果沒有問題和基本函式是一樣的。
第四節 程式控制
一、獲取程式ID
每個程式都有一個唯一的正數程式ID(PID)。
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回撥用程式的PID
pid_t getppid(void); 返回父程式的PID(建立呼叫程式的程式)
二、建立和終止程式
1.程式總是處於下面三種狀態之一
- 執行
- 停止:被掛起且不會被排程
-
終止:永遠停止。原因:
1.收到訊號,預設行為為終止程式 2.從主程式返回 3.呼叫exit函式 4.建立程式
父程式通過呼叫fork函式來建立一個新的執行子程式。fork函式定義如下:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
父程式返回子程式的PID,子程式返回0.如果失敗返回-1.
- 呼叫一次,返回兩次
- 併發執行,核心能夠以任何方式交替執行它們的邏輯控制流中的指令
- 相同點:使用者棧、本地變數值、堆、全域性變數值、程式碼
- 不同點:私有地址空間
- 共享檔案:子程式繼承了父程式所有的開啟檔案
- 呼叫fork函式n次,產生2的n次方個程式。
3.終止程式
exit函式:
#include <stdlib.h>
void exit(int status);
exit函式以status退出狀態來終止程式,該函式無返回值。
三、回收子程式
程式終止後還要被父程式回收,否則處於僵死狀態。
如果父程式沒有來得及回收,核心會安排init程式來回收他們。init程式的PID為1.
一個程式可以通過呼叫waitpid函式來等待它的子程式終止或停止。waitpid函式的定義如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
成功返回子程式PID,如果WNOHANG,返回0,其他錯誤返回-1.
1.判斷等待集合的成員——pid
- pid>0:等待集合是一個單獨子程式,程式ID等於pid
- pid=-1:等待集合是由父程式所有的子程式組成
- 其他
2.修改預設行為——options
設定為常量WNOHANG和WUNTRACED的各種組合:
3.檢查已回收子程式的退出狀態——status
在wait.h標頭檔案中定義瞭解釋status引數的幾個巨集:
- WIFEXITED:如果子程式通過呼叫exit或一個返回正常終止,就返回真
- WEXITSTATUS:返回一個正常終止的子程式的退出狀態。只有在WIFEXITED返回為真時,才會定義這個狀態
- WIFSIGNALED:如果子程式是因為一個未被捕獲的訊號終止的,那麼返回真
- WTERMSIG:返回導致子程式終止的訊號的編號。只有在WIFSIGNALED返回為真時才定義這個狀態
- WIFSTOPPED:如果引起返回的子程式當前是被停止的,那麼返回真
- WSTOPSIG:返回引起子程式停止的訊號的數量。只有在WIFSTOPPED返回為真時才定義這個狀態
4.錯誤條件
如果呼叫程式沒有子程式,那麼waitpid返回-1,並且設定errno為ECHILD。
如果waitpid被一個訊號中斷,那麼他返回-1,並且設定errno為EINTR。
5.wait函式
wait函式是waitpid函式的簡單版本,wait(&status)等價於waitpid(-1,&status,0).
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
成功返回子程式pid,出錯返回-1
四、讓程式休眠
1.sleep函式
sleep函式使一個程式掛起一段指定的時間。定義如下:
#include <unistd.h>
unsigned int sleep(unsigned int secs);
返回值是剩下還要休眠的秒數,如果到了返回0.
2.pause函式
#include <unistd.h>
int pause(void);
讓呼叫函式休眠,直到該程式收到一個訊號,總是返回-1。
五、載入並執行程式——execve函式
#include <unistd.h>
int execve(const char *filename, const char *argv[], const char *envp[]);
成功不返回,失敗返回-1.
execve函式呼叫一次,從不返回。
- filename:可執行目標檔案
- argv:引數列表
- envp:環境列表
操作環境陣列——getnev函式
#include <stdlib.h>
char *getenv(const char *name);
若存在則為指向name的指標,無匹配是null
在環境陣列中搜尋字串"name=value",如果找到了就返回一個指向value的指標,否則返回null。
操作環境陣列——setenv和unsetenv函式
#include <stdlib.h>
int setenv(const char *name, const char *newvalue, int overwrite);
若成功返回0,錯誤返回-1
void unsetenv(const char *name);
無返回值
如果環境陣列包含"name=oldvalue"的字串,unsetenv會刪除它,setenv會用newvalue代替oldvalue,只有在overwrite非零時成立。
如果name不存在,setenv會將"name=newvalue"寫進陣列。
fork函式和execve函式的區別:
fork函式是建立新的子程式,是父程式的複製體,在新的子程式中執行相同的程式,父程式和子程式有相同的檔案表
,但是不同的PID
execve函式在當前程式的上下文中載入並執行一個新的程式,會覆蓋當前程式的地址空間,但是沒有建立一個新程式
,有相同的PID,繼承檔案描述符。
第五節 訊號
1.基本概念
訊號是一種程式間通訊的方法,應用於非同步事件的處理,實質是軟中斷,在軟體層面。
2.kill -l 或者 man 7 signal——檢視訊號資訊
每個訊號都有一個編號和巨集定義名稱。
3.訊號生命週期
訊號產生、訊號註冊、訊號登出、訊號處理
(1)訊號產生——四種型別
-
使用者產生——Ctrl+C。 stty -a,檢視哪些按鍵可以產生訊號
-
硬體產生——除零錯誤
- 程式產生——kill指令
- 核心產生——鬧鐘超時
(2)訊號處理——三種方法
- 執行預設操作
- 忽略訊號
- 捕捉訊號:執行訊號處理函式,切換到使用者態。
(3) 多訊號處理
處理方法:
1.遞迴,呼叫同一個處理函式
2.忽略第二個訊號
3.阻塞第二個訊號直至第一個處理完畢
第六節 非本地跳轉
- c語言中,使用者級的異常控制流形式,通過setjmp和longjmp函式提供。
- setjump函式在env緩衝區中儲存當前呼叫環境,以供後面longjmp使用,並返回0.
- 呼叫環境:程式計數器,棧指標,通用目的暫存器
- longjmp函式從env緩衝區中恢復呼叫環境,然後觸發一個從最近一次初始化env的setjmp呼叫的返回。然後setjmp返回,並帶有非零的返回值retval。
注:
setjmp函式只被呼叫一次,但返回多次;
longjmp函式被呼叫一次,但從不返回。
第七節 操作程式的工具
- STRACE:列印一個正在執行的程式和他的子程式呼叫的每個系統呼叫的痕跡
- PS:列出當前系統中的程式,包括僵死程式
- TOP:列印出關於當前程式資源使用的資訊
- PMAP:顯示程式的儲存器對映
程式碼實踐
exec
1.exec1
- exec1.c中execvp()會從PATH 環境變數所指的目錄中查詢符合引數file 的檔名,找到後便執行該檔案,然後將第二個引數argv傳給該欲執行的檔案
- 如果執行成功則函式不會返回,執行失敗則直接返回-1,失敗原因存於errno中
- 編譯執行結果如下,沒有返回“* * * man is done. bye”
2.exec2
- 若將execvp函式傳入的arglist[0]改為arglist[1],此時execvp函式沒有呼叫成功,於是列印出“* * * ls is done. bye”這句話
3.exec3
- 函式中execlp()會從PATH 環境變數所指的目錄中查詢符合引數file的檔名,找到後便執行該檔案,然後將第二個以後的引數當做該檔案的argv[0]、argv[1]……最後一個引數必須用空指標(NULL)作結束
fork
1.forkdemo1
- 列印程式pid,然後呼叫fork函式生成子程式,休眠一秒後再次列印程式id,這時父程式列印子程式pid,子程式返回0。
2.forkdemo2
- 呼叫兩次fork,一共產生四個子程式,所以會列印四個aftre輸出.
3.forkdemo3
- fork產生子程式,父程式返回子程式pid,不為0,所以輸出父程式的那句話,子程式返回0,所以會輸出子程式那句話。
4.forkdemo4
- 先列印程式pid,然後fork建立子程式,父程式返回子程式pid,所以輸出parent一句,休眠十秒;子程式返回0,所以輸出child與之後一句。
5.forkgdb
- 父程式列印是先列印兩句,然後休眠一秒,然後列印一句,子程式先列印一句,然後休眠一秒,然後列印兩句。並且這兩個執行緒是併發的,所以可以看到在一個執行緒休眠的那一秒,另一個執行緒在執行,並且執行緒之間相互獨立互不干擾。
wait
1.waitdemo1
- waitdemo1.c的功能是如果有子程式,則終止子程式,成功返回子程式pid。
2.waitdemo2
- waitdemo2.c比起1來就是多了一個子程式的狀態區分,把狀態拆分成三塊,exit,sig和core
env
1.environ.c
-
environ.c程式碼執行如下
- 程式碼中涉及到getenv函式和setenv函式
- getenv函式在環境陣列中搜尋字串“name=value”。如果找到了,就返回一個指向value的指標,否則就返回NULL
-
setenv函式是修改或新增環境變數的函式
2.environvar.c
- environvar.c程式碼簡單列印環境變數表,執行結果如下
- 每個程式都有一個環境表,它是一個字元指標陣列,其中每個指標包含一個以NULL結尾的C字串的地址。
- 全域性變數environ則包含了該指標陣列的地址
fifo
- fifo是一種檔案型別,可以通過檢視檔案stat結構中的stmode成員的值來判斷檔案是否是FIFO檔案。fifo是用來在程式中使用檔案來傳輸資料的,也具有管道特性,可以在資料讀出的時候清除資料
1.consemer.c管道寫端
2.producer.c管道讀端
pipe
1.pipedemo.c
-
who把輸出送給stdout,sort從stdin中讀入資料,那也就是說who的stdout和sort的stdin連成了一個。
-
result=pipe(int array[2]);array[0]是讀端的檔案描述符,array[1]是寫端的檔案描述符。
-
pipe呼叫首先獲得兩個“最低可用檔案描述符”,賦給array[0]和array[1],然後再把這兩個檔案描述符連線起來。
2.pipedemo2.c
-
在程式中,顯示從鍵盤到程式,從程式到管道,再從管道到程式以及從程式回到終端的資料傳輸流。
signal
1.sigactdemo.c
2.sigactdemo2.c
-
休息seconds秒後返回;或者被訊號中斷且訊號處理函式返回後sleep()返回0。所以如果不計較返回值的話,pause()的功能相當於無限期的sleep()。
學習中遇到的問題和解決過程
問題:在虛擬機器中使用/proc檔案系統,結果顯示許可權不夠,如圖所示:
解決方法:通過最後一節內容得知,是由於輸入方法不對,應該輸入的是cat列印指令,再接後面的目錄,如下圖:
程式碼託管截圖
連結:https://git.oschina.net/sjy519/linux-program-C/tree/master
其他(感悟、思考等,可選)
本週學習了異常控制流,當學習教材上的內容時,我感到很吃力,真的很難理解,可能多看幾遍能稍微掌握一些,但是過了一段時間再看學過的知識點,又感覺很陌生,光是看書就看了很長時間,不知道這周真正學會的知識有多少。
學習進度條
程式碼行數(新增/累積) | 部落格量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 3000行 | 30篇 | 300小時 | |
第一週 | 0/0 | 1/2 | 25/40 | 學習了Linux基礎知識和核心命令 |
第二週 | 0/0 | 0/2 | 0/40 | |
第三週 | 300/300 | 3/5 | 40/80 |
學習了vim、gcc、gdb指令; 學習了資訊表示和處理
|
第五週 | 200/500 | 1/6 | 45/125 |
學習了程式的機器級表示 |
第六週 | 150/650 | 1/7 | 40/165 |
學習了處理器體系結構
|
第七週 | 100/750 | 1/8 | 40/205 |
學習了儲存器層次結構
|
第八週 | 46/796 | 2/10 | 40/245 |
複習了以前的知識點 |
第九周 | 124/920 | 1/11 | 40/285 |
學習了系統級I/O的相關內容 |
第十週 | 510/1430 | 3/14 | 32/317 |
重點學習了一些命令 |
第十週一 | 440/1870 | 3/17 | 35/352 |
學習了異常控制流的相關知識點 |