2017-2018-1 20155336 《信息安全系統設計基礎》第十四周學習總結
找出全書你認爲學得最差的一章,深刻從新學習一下
- 總結新的收穫
- 給你的結對學習搭檔講解或請教,並獲取反饋
再次深刻學習併發編程
- 本週老師讓咱們學習本身認爲學的最差的一章,我學習的是第十二章。一方面,併發進場出如今計算機系統許多不一樣的層面上,使用很是普遍;另外一方面,這個知識背景的硬件異常處理程序,Linux信號處理程序很是常見。因此爲了更好的深刻理解計算機系統,必需要掌握好這一章的知識,因此藉此機會從新學習這一章的內容。
教材學習內容詳細總結
- 到目前爲止,咱們主要將併發看作是一種操做系統內核用來運行多個應用程序的機制。可是,併發不只僅侷限於內核。它也能夠在應用程序中扮演重要角色。例如,咱們已經看到Unix信號處理程序如何容許應用響應異步事件,例如用戶鍵入。或者程序訪問虛擬存儲器的個未定義的區域.應用級併發在其餘狀況下也是頗有用的,例如:
- 訪問慢速I/O設備
- 與人交互
- 過推遲工做以下降延遲
- 服務多個網絡客戶端
- 在多核機器上進行並行計算
- 現代操做系統提供了三基本的構造併發程序的方法:進程、I/O多路複用、線程。
12.1 基於進程的併發編程
12.1.1 基於進程的併發服務器
- 一個基於進程的併發的echo服務器的代碼,重要說明:
- 首先,一般服務器會運行很長時間,因此咱們必須包括一個SIGCHLD處理程序,來回收僵死子進程資源。
- 其次,父子進程必須關閉他們的connfd拷貝。
- 最後,由於套接字的文件表表項的引用計數,直到父子進程的connfd都關閉了,到客戶端的鏈接纔會終止。
12.1.2 關於進程的優劣
- 關於進程的優劣,對於在父、子進程間共享狀態信息,進程有一個很是清晰的模型:共享文件表,可是不共享用戶地址空間。進程有獨立的地址控件愛你既是優勢又是缺點。因爲獨立的地址空間,因此進程不會覆蓋另外一個進程的虛擬存儲器。可是另外一方面進程間通訊就比較麻煩,至少開銷很高。
12.2 基於i/o多路複用的併發編程
- 1.好比一個服務器,它有兩個I/O事件:1)網絡客戶端發起鏈接請求,2)用戶在鍵盤上鍵入命令行。咱們先等待那個事件呢?沒有那個選擇是理想的。若是accept中等待鏈接,那麼沒法相應輸入命令。若是在read中等待一個輸入命令,咱們就不能響應任何鏈接請求(這個前提是一個進程)。
- 2.針對這種困境的一個解決辦法就是I/O多路複用技術。基本思想是:使用select函數,要求內核掛起進程,只有在一個或者多個I/O事件發生後,纔將控制返給應用程序。如圖所示:橫向的方格能夠看做是一個n位的描述符向量。如今,咱們定義第0位描述是「標準輸入」,第3位描述符是「監聽描述符」。
12.2.1 基於i/o多路複用的併發事件驅動服務器
- I/O多路複用能夠用作併發事件驅動程序的基礎,在事件驅動程序中,流是由於某種事件而前進的,通常概念是將邏輯流模型化爲狀態機,不嚴格地說,一個狀態機就是一組狀態,輸入事件和轉移,其中轉移就是將狀態和輸入事件映射到狀態,每一個轉移都將一個(輸入狀態,輸入事件)對映射到一個輸出狀態,自循環是同一輸入和輸出狀態之間的轉移,一般把狀態機畫成有向圖,其中節點表示狀態,有向弧表示轉移,而弧上的標號表示輸人事件,一個狀態機從某種初始狀態開始執行,每一個輸入事件都會引起一個從當前狀態到下一狀態的轉移,對於每一個新的客戶端k,基於I/O多路複用的併發服務器會建立一個新的狀態機S,並將它和已鏈接描述符d聯繫起來。
12.2.2 i/o多路複用技術的優劣
- 1.事件驅動設計的一個優勢是,它比基於進程的設計給了程序員更多的對程序行爲的控制。例如咱們能夠設想編寫一個事件驅動的併發服務器,爲某些客戶提供他們須要的服務,而這對於新進程的併發服務器來講,是很困難的
- 2.另外一個優勢是,一個基於I/O多路複用的事件驅動器是運行在單一進程上下文中的,所以每一個邏輯流都能訪問該進程的所有地址空間。這使得在流之間共享數據變得很容易,一個與做爲單個進程運行相關的優勢是,你能夠利用熟悉的調試工具,例如GDB,來調試你的併發服務器,就像對順序程序那樣。最後,事件驅動設計經常比基於進利的設計要高效得多,由於它們不須要進程上下文切換來調度新的流。
- 3.事件驅動設計的一個明顯的缺點就是編碼複雜,咱們的事件驅動的併發服務器須要的代度是指每一個邏輯流每一個時間片執行的指令數量。基於事件的設計的另外一個重大缺點是它們不能充分利利用多核處理器。
12.3 基於線程的併發編程
- 每一個線程都有本身的線程上下文,包括一個線程ID、棧、棧指針、程序計數器、通用目的寄存器和條件碼。全部的運行在一個進程裏的線程共享該進程的整個虛擬地址空間。因爲線程運行在單一進程中,所以共享這個進程虛擬地址空間的整個內容,包括它的代碼、數據、堆、共享庫和打開的文件。
12.3.1 線程執行模型
- 線程執行的模型。線程和進程的執行模型有些類似。每一個進程的聲明週期都是一個線程,咱們稱之爲主線程。可是你們要有意識:線程是對等的,主線程跟其餘線程的區別就是它先執行。
12.3.2 posix線程
- POSIX線程是在C程序中處理線程的一個標準接口。它最先出如今1995年,並且在大多數Unix系統上均可用。Pthreads定義了大約60個函數,容許程序建立、殺死和回收線程,與對等線程安全地共享數據,還能夠通知對等線程系統狀態的變化。
12.3.3 建立線程
12.3.4 終止線程
- 一個線程是如下列方式之一來終止的。
- 經過調用
pthread_exit
函數,線程會顯它會等待全部其餘對等線程終止,而後再終止式地終止。
- 某個對等線程調用
Unix
的e×it
函數,該函數終止進程以及全部與該進程相關的線程
12.3.5 回收已終止線程的資源
12.3.6 分離線程
12.3.7 初始化線程
12.4 多線程程序中的共享變量
- 全局變量和static 變量 是存儲在數據段,因此,多線程共享之!
- 因爲線程的棧是獨立的,全部線程中的自動變量是獨立的。即便多個線程運行同一段代碼總的自動變量,那麼他們的值也是根據線程的不一樣而不一樣。
- 好比C++中,類屬性不是在用戶棧中的。因此線程共享之!
12.4.1 線程存儲器模型
- 1.一組併發線程運行在一個進程的上下文中。每一個線程都有它本身獨立的線程上下文,包括線程ID、棧、棧指針、程序計數器、條件碼和通用目的寄存器值。每一個線程和其餘線程一塊兒共享進程上下文的剩餘部分。這包括整個用戶虛擬地址空間,它是由只讀文本代碼、讀/寫數據、堆以及全部的共享庫代碼和數據區域組成的。線程也共享一樣的打開文件的集合。
- 2.從實際操做的角度來講,讓一個線程去讀或寫另外一個線程的寄存器值是不可能的。另外一方面,任何線程均可以訪問共享虛擬存儲器的任意位置。若是某個線程修改了一個存儲器位置,那麼其餘每一個線程最終都能在它讀這個位置時發現這個變化。所以,寄存器是從不共享的,而虛擬存儲器老是共享的。
- 3.各自獨立的線程棧的存儲器模型不是那麼整齊清楚的。這些棧被保存在虛擬地址空間的棧區域中,而且一般是被相應的線程獨立地訪問的。咱們說一般而不是老是,是由於不一樣的線程棧是不對其餘線程設防的因此,若是個線程以某種方式獲得個指向其餘線程棧的指慧:那麼它就能夠讀寫這個棧的任何部分。
12.4.2 將變量映射到存儲器
- 線程化的C程序中變量根據它們的存儲類型被映射到虛擬存儲器:
- 1.全局變量。全局變量是定義在函數以外的變量,在運行時,虛擬存儲器的讀/寫區域域只包含每一個全局變量的一個實例,任何線程均可以引用。例如第5行聲明的全局變量
ptr
在虛擬存儲器的讀/寫區域中有個運行時實例,咱們只用變量名(在這裏就是ptr
)來表示這個實例。
- 2.本地自動變量,本地自動變量就是定義在函數內部可是沒有
static
屬性的變量,在運行時,每一個線程的棧都包含它本身的全部本地自動變量的實例。即便當多個線程執行同一個線程例程時也是如此。例如,有個本地變量tid
的實例,它保存在主線程的棧中。咱們用tid.m
來表示這個實例
- 3.本地靜態變量
12.4.3 共享變量
- 咱們說一個變量V是共享的,當且僅當它的一個實例被一個以上的線程引用。例如,示例程序中的變量cnt就是共享的,由於它只有一個運行時實例,而且這個實例被兩個對等線程引用在另外一方面,myid不是共享的,由於它的兩個實例中每個都只被一個線程引用。然而,認識到像msgs這樣的本地自動變量也能被共享是很重要的。
12.5 用信號量同步線程
- 信號量一般稱之爲PV操做,雖然它的思想是將臨界代碼保護起來,達到互斥效果。這裏面操做系統使用到了線程掛起。
將線程i的循環代碼分解成五個部分:程序員
12.5.1 進度圖
- 進程圖將n個併發進程的執行模型化爲一條n維笛卡爾空間中的軌跡線。
12.5.2 信號量
- 信號量s是具備非負整數值的全局變量,只能由兩種特殊的操做來處理,這兩種操做稱爲P和V
- P(s):若是s是非零的,那麼P將s減1而且當即返回。若是s爲零,那麼就掛起這個線程,直到s變爲非零,而一個y操做會重啓這個線程。在重啓以後,P操做將s減1並將控制返回給調用者
- V(s):V操做將s加1。若是有任何線程阻塞在P操做等待s變成非零,那麼V操做會重啓這些線程中的一個,而後該線程將s減1,完成它的P操做,P中的測試和減1操做是不可分割的,也就是說,一旦預測信號量s變爲非零,就會將s減1,不能有中斷。V中的加1操做也是不可分割的,也就是加載、加和存儲信號量的過程當中沒有中斷。注意,V的定義中沒有定義等待線程被從新啓動的順序。惟—的要求是V必須只能重啓一個正在等待的進程。
12.5.3 使用信號量來實現互斥
- 信號量提供了一種很方便的方法來確保對共享變量的互斥訪問。基本思想是將每一個共享變量(或者一組相關的共享變量)與一個信號量聯繫起來 。以這種方式來保護共享變量的信號量叫作二元信號量,由於它的值老是0或者1。以提供互斥爲目的的二元信號量經常也稱爲互斥鎖。在一個互斥鎖上執行P操做稱爲對互斥鎖加鎖。相似地,執行V操做稱爲對互斥鎖解鎖。對一個互斥鎖加了鎖可是尚未解鎖的線程稱爲佔用這個互斥鎖。一個被用做一組可用資源的計數器的信號量稱爲計數信號量。關鍵思想是這種P和V操做的結合建立了一組狀態,叫作禁止區。由於信號量的不變性,沒有實際可行的軌跡線可以包含禁止區中的狀態。並且,由於禁止區徹底包括了不安全區,因此沒有實際可行的軌跡線可以接觸不安全區的任何部分。所以,每條實際可行的軌跡線都是安全的,並且無論運行時指令順序是怎樣的,程序都會正確地增長計數器的值。
12.5.4 利用信號量來調度共享資源
1.生產者-消費者問題。數據庫
- 2.讀者—寫者問題:
- 讀者優先,要求不讓讀者等待,除非已經把使用對象的權限賦予了一個寫者。
- 寫者優先,要求一旦一個寫者準備好能夠寫,它就會盡量地完成它的寫操做。
- 飢餓是一個線程無限期地阻塞,沒法進展。
12.5.5 綜合:基於預線程化的併發服務器
12.6 使用線程提升並行性
- 到目前爲止,在對併發的研究中,咱們都假設併發線程是在單處許多現代機器具備多核處理器。併發程序一般在這樣的機器上運理器系統上執行的。然而,在多個核上並行地調度這些併發線程,而不是在單個核順序地調度,在像繁忙的Web服務器、數據庫服務器和大型科學計算代碼這樣的應用中利用這種並行性是相當重要的。
12.7.1 線程安全
- 咱們編程過程當中,儘量編寫線程安全函數,即一個函數當且僅當被多個併發線程反覆調用時,它會一直產生正確的結果。若是作不到這個條件咱們稱之爲線程不安全函數。下面介紹四類線程不安全函數:
- 不保護共享變量的函數。解決辦法是PV操做。
- 保持跨越多個調用的狀態函數。好比使用靜態變量的函數。解決方法是不要使用靜態變量或者使用可讀靜態變量。
- 返回指向靜態變量的指針的函數。解決方法是lock-and-copy(枷鎖-拷貝)
- 調用線程不安全函數的函數
- 因爲PV操做不當,可能形成死鎖現象。這在程序中也會出現。
12.7.2 可重入性.
有一類重要的線程安全函數,叫作可重入函數。其特色在於他們具備這樣一種屬性:當它們被多個線程調用時,不會引用任何共享數據。儘管線程安全和可重入有時會(正確地)被用作同義詞,可是它們之間仍是有清晰的技術差異的,值得留意。圖展現了可重入函數、線程安全函數和線程不安全函數之間的集合關係。全部函數的集合被劃分紅不相交的線程安全和線程不安全函數集合。可重入函數集合是線程安全函數的一個真子集。安全
可重入函數一般要比不可重入的線程安全的函數高效一些,由於它們不須要同步操做。更進一步來講,將第2類線程不安全函數轉化爲線程安全函數的惟一方法就是重寫它,使之變爲可重入的。服務器
12.7.3 在線程化的程序中使用已存在的庫函數
12.7.4 競爭
- 當一個程序的正確性依賴於一個線程要在另外一個線程到達y點以前到達它的控制流中的X點時,就會發生競爭。一般發生競爭是由於程序員假定線程將按照某種特殊的軌跡正確工做忘記了另外一條準則規定:線程化的程序必須對任何可行的軌跡線都正確工做。
12.7.5 死鎖
- 1.信號量引入了一種潛在的使人厭惡的運行時錯誤,叫作死鎖。它指的是一組線程被阻塞了,等待一個永遠也不會爲真的條件。進度圖對於理解死鎖是一個無價的工具。
- 2.關於死鎖的重要知識:
- 3.程序員使用P和V操做漏序不當,以致於兩個信號量的禁止區域重疊。若是某個執行軌跡線碰巧到達了死鎖狀態d那麼就不可能有進一步的進展了,由於重疊的禁止區域阻塞了每一個合法方向上的進展。換句話說,程序死鎖是由於每一個線程在等待一個根本不可能發生的V操做
- 4.重疊的禁止區域引發了一組稱爲死鎖區域的狀態。軌跡線能夠進入死鎖區域,可是它們不可能離開
- 5.死鎖是個至關困難的問題,由於它不老是可預測的。一些幸運的執行軌跡線將繞開死鎖區域,而其餘的將會陷入這個區域。
小結
- 1.一個併發程是由在時間上重疊的一組邏輯流組成的。在這一章中,咱們學習了三種不一樣的應用程序程、I/O多路複用和線程。咱們以一個併發網絡服務器做爲貫穿全章的應用程序。
- 2.進程是由內核自動調度的,並且由於它們有各自獨立的虛擬地址空間,因此要實現共享數據,必需要有顯式的IPC機制。事件驅動程序建立它們本身的併發邏輯流,這些邏輯流被模型之間共享數據速度很快並且很容多路複用來顯式地調度這些流。
- 3.不管哪一種併發機制,同步對共享數據的併發訪問都是一個困難的問題。提出對信號量的P和V操做就是爲了幫助解決這個問題。信號量操做能夠用來提供對共享數據的互斥訪問,也對諸如生產者-消費者程序中有限緩衝區和讀者―寫者系統中的共享對象這樣的資源訪問進行調度。
- 4.併發也引入了其餘一些困難的問題。被線程調用的函數必須具備一種稱爲線程安全的屬性。咱們定義了四類線程不安全的函數,以及一些將它們變爲線程安全的建議。可重入函數是線程安全函數的一個真子集,它不訪問任何共享數據。可重入函數一般比不可重入函數更爲有效,由於它們不須要任何同步原語。競爭和死鎖是併發程序中出現的另外一些困難的問題。當程序員錯誤地假設邏輯流該如何調度時,就會發生競爭。當一個流等待一個永遠不會發生的事件時,時,就會產生死鎖。
教材習題學習
12.1
- 父進程關閉了已鏈接描述符後,子進程仍可以使用該描述符和客戶端通訊的緣由是?
- 解答:當父進程派生子進程是,它獲得一個已鏈接描述符的副本,並將相關文件表中的引用計數從1增長到2.當父進程關閉他的描述符副本時,引用計數從2減小到1.由於內核不會關閉一個文件,直到文件表中他的應用計數值變爲0,因此子進程這邊的鏈接端將保持打開
12.3
- 在Linux系統裏,在標準輸入上鍵入Ctrl+D表示EOF,若阻塞發生在對select的調用上,鍵入Ctrl+D會發生什麼?
- 解答:會致使select函數但會,準備好的集合中有描述符0
12.4
- 在服務器中,每次使用select前都初始化pool.ready_set變量的緣由?
- 解答:由於pool.ready_set即做爲輸入參數也做爲輸出參數,因此在每一次調用select前都從新初始化他。輸入時,他包含讀集合,在輸出,它包含準備好的集合
12.16
- 編寫hello.c一個版本,建立和回收n個可結合的對等線程,其中n是一個命令行參數。
代碼以下:網絡
#include <stdio.h>
#include "csapp.h"
void *thread(void *vargp);
#define DEFAULT 4
int main(int argc, char* argv[]) {
int N;
if (argc > 2)
unix_error("too many param");
else if (argc == 2)
N = atoi(argv[1]);
else
N = DEFAULT;
int i;
pthread_t tid;
for (i = 0; i < N; i++) {
Pthread_create(&tid, NULL, thread, NULL);
}
Pthread_exit(NULL);
}
void *thread(void *vargp) {
printf("Hello, world\n");
return NULL;
}
12.17
- 修改程序的bug,要求程序睡眠1秒鐘,而後輸出一個字符串
代碼以下:多線程
#include "csapp.h"
void *thread(void *vargp);
int main()
{
pthread_t tid;
Pthread_create(&tid, NULL, thread, NULL);
// exit(0);
Pthread_exit(NULL);
}
/* Thread routine */
void *thread(void *vargp)
{
Sleep(1);
printf("Hello, world!\n");
return NULL;
}
上週考試錯題總結
第3題
- 大多數計算機使用一樣的機器指令來執行無符號和有符號加法。(A)
- 解析:書上63頁,基礎知識。
第10題
- Y86-64中()指令沒有訪存操做.(A B E)
- A rrmovl
- B irmovq
- C rmmovq
- D pushq
- E jXX
- F ret
- 解析:在Y86-64中,只有
rmmovq
,pishq
,ret
有訪存操做。
第22題
- 針對以上代碼:gcc -c *.c 能夠獲得m.o,swap.o兩個模塊,哪些符號會出如今swap.o模塊的.symtab條目中( A C D)
- A buf
- B temp
- C swap
- D buffp0
- 解析:temp是局部變量,不出如今符號表中。
第29題
- Unix/Linux中經過調用( D )能夠獲取子進程PID。
- A getpid()
- B getppid()
- C getcpid()
- D fork()
- 解析:在fork的時候記錄下子進程的pid
第39題
有關echo服務器代碼,編譯後的可執行程序爲echoserv,下面說法正確的是(A C D )
併發
- A .
該echo服務器是迭代服務器
- B .
該echo服務器是併發服務器
- C .
echoserv應該先於eccho客戶端啓動
- D .
./echoserv 8089, 8089是服務器端的端口
- E .
./echoserv 8089, 8089是客戶端的端口
解析:./echoserv 8089, 8089是服務器端的端口而不是客戶端的端口!
本週代碼託管連接
代碼連接
結對及互評
本週結對學習狀況
- 20155315
- 同伴重點學習了第九章的教材內容,我也從新回顧了第十二章的知識重點。
其餘(感悟、思考等)
本週老師讓咱們學習本身認爲學的最差的一章,我學習的是第十二章。一方面,併發進場出如今計算機系統許多不一樣的層面上,使用很是普遍;另外一方面,這個知識背景的硬件異常處理程序,Linux信號處理程序很是常見。因此爲了更好的深刻理解計算機系統,必需要掌握好這一章的知識,因此藉此機會從新學習這一章的內容。
學習進度條
目標 |
5000行 |
30篇 |
400小時 |
|
第一週 |
200/200 |
2/2 |
20/20 |
|
第二週 |
300/500 |
2/4 |
18/38 |
|
第三週 |
500/1000 |
3/7 |
22/60 |
|
第四周 |
600/1300 |
4/9 |
30/90 |
|
第五週 |
650/1300 |
5/9 |
40/90 |
|
第六週 |
700/1300 |
6/9 |
50/90 |
|
第七週 |
800/1300 |
7/9 |
60/90 |
|
第八週 |
1200/1700 |
8/10 |
80/110 |
|
第九周 |
1800/2000 |
9/11 |
100/120 |
|
第十一週 |
2400/3000 |
10/15 |
120/140 |
|
第十三週 |
3000/3500 |
11/15 |
140/160 |
|
第十四周 |
3400/3800 |
12/15 |
155/170 |
|
嘗試一下記錄「計劃學習時間」和「實際學習時間」,到期末看看能不能改進本身的計劃能力。這個工做學習中很重要,也頗有用。
耗時估計的公式
:Y=X+X/N ,Y=X-X/N,訓練次數多了,X、Y就接近了。
參考:軟件工程軟件的估計爲何這麼難,軟件工程 估計方法
(有空多看看現代軟件工程 課件
軟件工程師能力自我評價表)
參考資料