2017-2018-1 20155323 《信息安全系統設計基礎》第十三週學習總結

2017-2018-1 20155323 《信息安全系統設計基礎》第十三週學習總結

找出全書你認爲最重要的一章,深刻從新學習一下,要求:html

完成這一章全部習題程序員

詳細總結本章要點算法

給你的結對學習搭檔講解你的總結並獲取反饋編程

參考上面的學習總結模板,把學習過程經過博客(隨筆)發表,博客標題「學號 《信息安全系統設計基礎》第十三週學習總結」,博客(隨筆)要經過做業提交。安全

併發編程

我認爲十二章併發編程時全書最重要的一章,由於整本書從程序的結構和執行,講到如何在系統上運行程序,再到如何實現程序間的交互和通訊,併發編程毫無疑問是最後的重中之重。服務器

12.1 基於進程的併發編程

  • 父子進程的已鏈接描述符都指向同一個文件表項,父進程關閉它的鏈接描述符是相當重要的。
  • 父進程派生子進程爲客戶端提供服務,而父進程本身用來等待下一個鏈接請求。
  • 父進程派生一個子進程來處理每一個新的鏈接請求
  • 經常使用函數:

fork網絡

exec多線程

waitpid併發

  • 須要說明的內容:

必需要包括一個SIGCHLD處理程序來回收僵死子進程的資源。函數

父進程須要關閉它的已鏈接描述符的拷貝以免內存泄漏。

父子進程之間共享文件表,可是不共享用戶地址空間。

12.1.2 進程的優劣

  • 注意:進程的模型:共享文件表,但不是共享用戶地址空間。
  • 優勢:一個進程不可能不當心覆蓋兩一個進程的虛擬存儲器。
  • 缺點:獨立的地址空間使得進程共享狀態信息變得更加困難。進程控制和IPC的開銷很高。
  • Unix IPC是指全部容許進程和同一臺主機上其餘進程進行通訊的技術,包括管道、先進先出(FIFO)、系統V共享存儲器,以及系統V信號量。

12.2 基於I/O多路複用的併發編程

  • echo服務器必須響應兩個相互獨立的I/O時間:

1.網絡客戶端發起鏈接請求

2.用戶在鍵盤上鍵入命令行。

  • I/O多路複用技術的基本思路:使用select函數,要求內核掛起進程,只有在一個或多個I/O事件發生後,纔將控制返回給應用程序。
  • echo函數:未來自科幻段的每一行回送回去,直到客戶端關閉這個連接。
  • 只容許對描述符作的三件事:

1.分配他們。

2.將一個此種類型的變量賦值給另外一個變量。

3.用FD_ZERO、FD_SET、FD_CLR和FD_ISSET宏指令來修改和檢查它們。

12.2.1 基於I/O多路複用的併發事件驅動服務器

狀態機就是一組狀態、輸入事件和轉移,轉移就是將狀態和輸入時間映射到狀態,自循環是同一輸入和輸出狀態之間的轉移。

  • 流程:

select函數檢測到輸入事件

add_client函數建立新狀態機

check_clients函數執行狀態轉移(在課本的例題中是回送輸入行),而且完成時刪除該狀態機。

  • 須要注意的函數:

init_pool:初始化客戶端池

add_client:添加一個新的客戶端到活動客戶端池中

check_clients:回送來自每一個準備好的已鏈接描述符的一個文本行

12.2.2 I/O多路複用技術的優點

  • 優勢:

(1)比基於進程的設計給了程序員更多的對程序行爲的控制

(2)運行在單一進程上下文中,所以,每一個邏輯流都能訪問該進程的所有地址空間,使得流之間共享數據變得很容易。

(3)不須要進程上下文切換來調度新的流。

  • 缺點:

(1)編碼複雜

(2)不能充分利用多核處理器

粒度:每一個邏輯流每一個時間片執行的指令數量。併發粒度就是讀一個完整的文本行所須要的指令數量。

12.3 基於線程的併發編程

  • 這是前兩種建立併發邏輯流方法的混合。
  • 簡單的說線程就是運行在進程上下文中的邏輯流。
  • 線程有本身的線程上下文,包括一個惟一的整數線程ID、棧、棧指針、程序計數器、通用目的寄存器和條件碼。全部運行在一個進程裏的線程共享該進程的整個虛擬地址空間。

12.3.1線程執行模型

  • 主線程:每一個進程開始生命週期時都是單一線程。
  • 對等線程:某一時刻,主線程建立的對等線程
  • 線程與進程的不一樣:

線程的上下文切換要比進程的上下文切換快得多;

和一個進程相關的線程組成一個對等池,獨立於其餘線程建立的線程。

主線程和其餘線程的區別僅在於它老是進程中第一個運行的線程。

  • 對等池的影響

一個線程能夠殺死它的任何對等線程;

等待它的任意對等線程終止;

每一個對等線程都能讀寫相同的共享資源。

12.3.2 Posix線程

  • 線程的代碼和本地數據被封裝在一個線程例程中。每個線程例程都以一個通用指針做爲輸入,並返回一個通用指針。

12.3.3 建立線程

  • 經過調用pthread create函數建立一個新的線程,並帶着一個輸入變量arg,在新線程的上下文中運行線程例程f。新線程能夠經過調用pthread _self函數來得到本身的線程ID。
#include <pthread.h>
typedef void *(func)(void *);
 
int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg);//成功返回0,出錯返回非0


#include <pthread.h>
 
pthread_t pthread_self(void);//查看線程

12.3.4 終止線程

-一個線程的終止方式:

(1)當頂層的線程例程返回時,線程會隱式的終止;

(2)經過調用pthread _exit函數,線程會顯示地終止。若是主線程調用pthread _exit,它會等待全部其餘對等線程終止,而後再終止主線程和整個進程。

#include <pthread.h>
 
void pthread_exit(void *thread_return);//從不返回


#include <pthread.h>
 
void pthread_cancle(pthread_t tid);//若成功返回0,出錯爲非0

12.3.5 回收已終止線程的資源

  • 經過調用pthread _join函數等待其餘線程終止,回收已終止線程佔用的全部存儲器資源。pthread _join函數只能等待一個指定的線程終止。
#include <pthread.h>
 
int pthread_join(pthread_t tid,void **thrad_return);

12.3.6 分離線程

  • 在任何一個時間點上,線程是可結合的,或是分離的。一個可結合的線程可以被其餘線程收回其資源和殺死;一個可分離的線程是不能被其餘線程回收或殺死的。它的存儲器資源在它終止時有系統自動釋放。
  • 經過調用pthread _detach函數被分離。
#include <pthread.h>
 
void pthread_detach(pthread_t tid);//若成功返回0,出錯爲非0

12.3.7 初始化線程

  • 經過調用pthread_once函數初始化與線程例程相關的狀態。
#include <pthread.h>
pthread_once_t once_control = PTHREAD_ONCE_INIT;
 
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));//老是返回0

12。4 多線程程序中的共享變量

  • 一個變量是共享的,當且僅當多個線程引用這個變量的某個實例。
  • 全局變量和static 變量 是存儲在數據段,因此,多線程共享之。
  • 因爲線程的棧是獨立的,全部線程中的自動變量是獨立的。即便多個線程運行同一段代碼總的自動變量,那麼他們的值也是根據線程的不一樣而不一樣。
  • 好比C++中,類屬性不是在用戶棧中的。因此線程共享之。

12.4.1線程內存模型

  • 每一個線程和其餘線程一塊兒共享進程上下文的剩餘部分。包括整個用戶虛擬地址空間,是由只讀文本、讀/寫數據、堆以及全部的共享庫代碼和數據區域組成的。線程也共享一樣的打開文件的集合。
  • 任何線程均可以訪問共享虛擬內存的任意位置。寄存器從不共享,虛擬存儲器老是共享的。

12.4.2將變量映射到內存

  • 全局變量:全局變量是定義在函數以外的變量。運行時虛擬內存的讀/寫區域只包含每一個全局變量的一個實例,任何線程均可以引用。
  • 本地自動變量:本地自動變量是定義在函數內部但沒有static屬性的變量。運行時每一個線程的棧都包含它本身的全部本地自動變量的實例。
  • 本地靜態變量:本地靜態變量是定義在函數內部並有static屬性的變量。和全局變量同樣運行時虛擬內存的讀/寫區域只包含每一個本地靜態變量的一個實例。

12.4.3 共享變量

  • 變量v是共享的,當且僅當它的一個實例被一個以上的線程引用。
  • 咱們說一個變量V是共享的,當且僅當它的一個實例被一個以上的線程引用。例如,示例程序中的變量cnt就是共享的,由於它只有一個運行時實例,而且這個實例被兩個對等線程引用在另外一方面,myid不是共享的,由於它的兩個實例中每個都只被一個線程引用。然而,認識到像msgs這樣的本地自動變量也能被共享是很重要的。

12.5 用信號量同步線程

  • 信號量一般稱之爲PV操做,雖然它的思想是將臨界代碼保護起來,達到互斥效果。這裏面操做系統使用到了線程掛起。
  • 共享變量引入了同步錯誤的可能性。
  • 線程i的循環代碼分解爲五部分:

Hi:在循環頭部的指令塊

Li:加載共享變量cnt到寄存器%eax的指令,%eax表示線程i中的寄存器%eax的值

Ui:更新(增長)%eax的指令

Si:將%eaxi的更新值存回到共享變量cnt的指令

Ti:循環尾部的指令塊。

12.5.1進度圖

  • 進程圖將n個併發進程的執行模型化爲一條n維笛卡爾空間中的軌跡線。
  • 進度圖將指令執行模式化爲從一種狀態到另外一種狀態的轉換。轉換被表示爲一條從一點到相鄰點的有向邊。合法的轉換是向右或者向上。
  • 互斥的訪問:確保每一個線程在執行它的臨界區中的指令時,擁有對共享變量的互斥的訪問。
  • 安全軌跡線:繞開不安全區的軌跡線
  • 不安全軌跡線:接觸到任何不安全區的軌跡線就叫作不安全軌跡線
  • 任何安全軌跡線都能正確的更新共享計數器。

12.5.2信號量

  • 當有多個線程在等待同一個信號量時,你不能預測V操做要重啓哪個線程。
  • 信號量不變性:一個正在運行的程序毫不能進入這樣一種狀態,也就是一個正確初始化了的信號量有一個負值。

12.5.3 使用信號量來實現互斥

  • 二元信號量:將每一個共享變量與一個信號量s聯繫起來,而後用P(S)和V(s)操做將這種臨界區包圍起來,這種方式來保護共享變量的信號量。
  • 互斥鎖:以提供互斥爲目的的二元信號量
  • 加鎖:一個互斥鎖上執行P操做稱爲對互斥鎖加鎖,執行V操做稱爲對互斥鎖解鎖。對一個互斥鎖加了鎖但尚未解鎖的線程稱爲佔用了這個互斥鎖。
  • 計數信號量:一個唄用做一組可用資源的計數器的信號量

12.5.4 利用信號量來調度共享資源

  • 信號量的做用:

提供互斥

調度對共享資源的訪問

  • 生產者—消費者問題:生產者產生項目並把他們插入到一個有限的緩衝區中,消費者從緩衝區中取出這些項目,而後消費它們。
  • 讀者—寫者問題:

讀者優先,要求不讓讀者等待,除非已經把使用對象的權限賦予了一個寫者。

寫者優先,要求一旦一個寫者準備好能夠寫,它就會盡量地完成它的寫操做。

飢餓就是一個線程無限期地阻塞,沒法進展。

12.6 使用線程提升並行性

  • 寫順序程序只有一條邏輯流,寫併發程序有多條併發流,並行程序是一個運行在多個處理器上的併發程序。並行程序的集合是併發程序集合的真子集。

12.7.1 線程安全

  • 咱們編程過程當中,儘量編寫線程安全函數,即一個函數當且僅當被多個併發線程反覆調用時,它會一直產生正確的結果。若是作不到這個條件咱們稱之爲線程不安全函數。下面介紹四類線程不安全函數:

不保護共享變量的函數。解決辦法是PV操做。

保持跨越多個調用的狀態函數。好比使用靜態變量的函數。解決方法是不要使用靜態變量或者使用可讀靜態變量。

返回指向靜態變量的指針的函數。解決方法是lock-and-copy(枷鎖-拷貝)

調用線程不安全函數的函數
死鎖。

  • 因爲PV操做不當,可能形成死鎖現象。這在程序中也會出現。

12.7.4 競爭

  • 競爭:當一個程序的正確性依賴於一個線程要在另外一個線程到達y點以前到達它的控制流中的x點時,就會發生競爭。
  • 線程化的程序必須對任何可行的軌跡線都正確工做。

12.7.5 死鎖

  • 一組線程被阻塞了,等待一個永遠也不會爲真的條件。
  • 解決死鎖的方法

a.不讓死鎖發生:

靜態策略:設計合適的資源分配算法,不讓死鎖發生---死鎖預防;

動態策略:進程在申請資源時,系統審查是否會產生死鎖,若會產生死鎖則不分配---死鎖避免。

b.讓死鎖發生:

進程申請資源時不進行限制,系統按期或者不按期檢測是否有死鎖發生,當檢測到時解決死鎖----死鎖檢測與解除。

教材學習中的問題和解決過程

問題1:如何理解線程,主內存和工做內存三者之間的關係

解決:

結對及互評

本週結對學習狀況

20155314劉子健

結對學習內容

第十二章

課後習題

12.1

併發服務器第33行上,父進程關閉了已鏈接描述符後,子進程仍可以使用該描述符和客戶端通訊的緣由是?

答:當父進程派生子進程時,它獲得一個已鏈接描述符的副本,並將相關文件表中的引用計數從1增長到2.當父進程關閉它的描述符副本時,引用計數就從2減小到1.由於內核不會關閉一個文件,知道文件表中它的引用計數值變爲0,因此子進程這邊的鏈接端將保持打開。

12.2

若是咱們要刪除圖12-5中關閉已鏈接描述符的第30行,從沒有內存泄漏的角度來講,代碼將仍然是正確的,爲何

答:當一個進程由於某種緣由終止時,內核將關閉全部打開的描述符。所以,當子進程退出時,它的已鏈接文件描述符的副本也將被自動關閉。

12.3

在Linux系統裏,在標準輸入上鍵入Ctrl+D表示EOF。圖12-6中的程序阻塞在對select的調用上,若是你鍵入Ctrl+D會發生什麼

答:若是一個從描述符中讀一個字節的請求不會阻塞,那麼這個描述符就準備好能夠讀了。假如EOF在一個描述符上爲真,那麼描述符也準備好可讀了,由於讀操做將當即返回一個零返回碼,表示EOF。所以,鍵入Ctrl+D會致使select函數返回,準備好的集合中有描述符0。

12.4

圖12-8所示的服務器中,咱們在每次調用select以前都當即當心地從新初始化pool.ready_set變量,爲何?

答:由於變量pool.read_set既做爲輸入參數,也做爲輸出參數,因此咱們在每一次調用select以前都從新初始化它。在輸入時,它包含讀集合。在輸出時,它包含準備好的集合。

12.5

在下圖中基於進程的服務器中,咱們在兩個位置當心地關閉了已鏈接描述符:父進程和子進程。然而,在圖2中,基於線程的服務器中,咱們只在一個位置關閉了已鏈接描述符:對等線程,爲何?

答:由於線程運行在同一個進程中,它們都共享相同的描述符表。不管有多少線程使用這個已鏈接描述符,這個已鏈接描述符的文件表的引用計數都等於1.所以,當咱們用完它時,一個close操做就足以釋放於這個已鏈接描述符相關的內存資源了。

12.6

12.7

12.8

指出下列軌跡是否安全。

12.9

12.10

下圖所示的對第一類讀者-寫者問題的解答給予讀者較高的優先級,可是從某種意義上說,這種優先級是很弱的,由於一個離開臨界區的寫者可能重啓一個在等待的寫者,而不是一個在等待的讀者。描述一個場景,其中這種弱優先級會致使一羣寫者使得一個讀者飢餓。

答:假設一個特殊的信號量實現爲每個信號量使用了一個LIFO的線程棧。當一個線程在P操做中阻塞在一個信號量上,它的ID就被壓入棧中。相似地,V操做從棧中彈出棧頂的線程ID,並重啓這個線程。根據這個棧的實現,一個在它的臨界區中競爭的寫者會簡單的等待,直到在他釋放這個信號量以前另外一個寫者阻塞在這個信號量上。在這種場景中,當兩個寫者來回地傳遞控制權時,正在等待的讀者可能會永遠的等待下去。

12.11

12.12

圖中ctime_ts函數是線程安全的,但不是可重入的。請解釋說明

答:ctime_ts函數不是可重入函數,由於每次調用都共享相同的由ctime函數返回的static變量。然而,它是線程安全的,由於對共享變量的訪問是被P和V操做保護的,所以是互斥的。

12.13

在下圖所示代碼中,咱們可能想要在主線程中的第14行後當即釋放已經分配的內存塊,而不是在對等線程中釋放它,這是個壞主意,爲何?

答:若是在第4行剛調用完pthread_create後就釋放內存,這回引發一個競爭,這個競爭發生在主線程對free的調用和24的行的賦值語句之間。

12.14

A.在上圖中,咱們經過爲每一個整數ID分配一個獨立的塊來消除競爭。給出一個不調用malloc或者free函數的不一樣的方法。

B.這種方法的利弊是什麼?

答:

A:另外一種方法是直接傳遞整數i,而不是傳遞一個指向i的指針

for(i=0;i<N;i++)
Pthread_create(&tid[i],NULL,thread,(void*)i);

咱們將參數強制轉換成一個int型變量,並將它賦值給myid;

int myid=(int)vargp;

B:優勢是它經過消除對malloc和free的調用下降了開銷。一個明顯的缺點是,它假設指針至少和int同樣大。即使這種假設對於全部得現代系統來講都爲真,可是它對於那些過去遺留下來的或從此的系統來講可能就不爲真了。

相關文章
相關標籤/搜索