備戰秋招——操做系統

 

進程與線程:

概念:node

線程:是操做系統可以進行運算調度的最小單位。是進程中的一個執行流程,一個進程中能夠運行多個線程。linux

進程:一個執行中的程序的實例程序員

 

進程 與 線程 的區別

進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存web

進程和線程的主要差異在於它們是不一樣的操做系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不一樣執行路徑。線程有本身的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,因此多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行而且又要共享某些變量的併發操做,只能用線程,不能用進程。算法

 

進程在執行過程當中擁有獨立的內存單元,而多個線程共享進程的內存。(資源分配給進程,同一進程的全部線程共享該進程的全部資源。同一進程中的多個線程共享代碼段(代碼和常量),數據段(全局變量和靜態變量),擴展段(堆存儲)。可是每一個線程擁有本身的棧段,棧段又叫運行時段,用來存放全部局部變量和臨時變量。數組

進程是資源分配的最小單位,線程是CPU調度的最小單位;緩存

系統開銷: 因爲在建立或撤消進程時,系統都要爲之分配或回收資源,如內存空間、I/o設備等。所以,操做系統所付出的開銷將顯著地大於在建立或撤消線程時的開銷。相似地,在進行進程切換時,涉及到整個當前進程CPU環境的保存以及新被調度運行的進程的CPU環境的設置。而線程切換隻須保存和設置少許寄存器的內容,並不涉及存儲器管理方面的操做。可見,進程切換的開銷也遠大於線程切換的開銷。安全

通訊:因爲同一進程中的多個線程具備相同的地址空間,導致它們之間的同步和通訊的實現,也變得比較容易。進程間通訊IPC,線程間能夠直接讀寫進程數據段(如全局變量)來進行通訊——須要進程同步和互斥手段的輔助,以保證數據的一致性。在有的系統中,線程的切換、同步和通訊都無須操做系統內核的干預網絡

進程間不會相互影響 ;線程一個線程掛掉將致使整個進程掛掉數據結構

 

進程間通訊

在 linux 下進程間通訊的幾種主要手段簡介:

  1. 管道(Pipe)及有名管道(named pipe):管道:傳輸資源。本質上是內核的一塊緩衝區。(特性:半雙工,單向通訊)。 Linux一切皆文件,操做系統爲管道提供操做的方法:文件操做。管道可用於具備親緣關係進程間的通訊,有名管道克服了管道沒有名字的限制,所以,除具備管道所具備的功能外,它還容許無親緣關係進程間的通訊;

  2. 信號(Signal):信號是比較複雜的通訊方式,用於通知接受進程有某種事件發生,除了用於進程間通訊外,進程還能夠發送信號給進程自己;linux除了支持Unix早期信號語義函數sigal外,還支持語義符合Posix.1標準的信號函數sigaction(實際上,該函數是基於BSD的,BSD爲了實現可靠信號機制,又可以統一對外接口,用sigaction函數從新實現了signal函數);
  3. 消息隊列(Message)

    消息隊列其實是操做系統在內核爲咱們建立的一個隊列,經過這個隊列的標識符key,每個進程均可以打開這個隊列,每一個進程均可以經過這個隊列向這個隊列中插入一個結點或者獲取一個結點來完成不一樣進程間的通訊。
    用戶組織一個帶有類型的數據塊,添加到隊列中,其餘的進程從隊列中獲取數據塊,即消息隊列發送的是一個帶有類型的數據塊;消息隊列是一個全雙工通訊,可讀可寫(能夠發送數據,也能夠接受數據)

    消息隊列是消息的連接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程能夠向隊列中添加消息,被賦予讀權限的進程則能夠讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩衝區大小受限等缺點。

  4. ** 共享內存**:使得多個進程能夠訪問同一塊內存空間,是最快的可用IPC形式。是針對其餘通訊機制運行效率較低而設計的。每每與其它通訊機制,如信號量結合使用,來達到進程間的同步及互斥。

  5. 信號量(semaphore):主要做爲進程間以及同一進程不一樣線程之間的同步手段。

  6. 套接口(Socket):更爲通常的進程間通訊機制,可用於不一樣機器之間的進程間通訊。起初是由Unix系統的BSD分支開發出來的,但如今通常能夠移植到其它類Unix系統上:Linux和System V的變種都支持套接字。

各類通訊方式的比較和優缺點:

  1. 管道:速度慢,容量有限,只有父子進程能通信

  2. 有名管道(named pipe):任何進程間都能通信,但速度慢

  3. 消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題

  4. 信號量:不能傳遞複雜消息,只能用來同步

  5. 共享內存:可以很容易控制容量,速度快,但要保持同步,好比一個進程在寫的時候,另外一個進程要注意讀寫的問題,至關於線程中的線程安全,固然,共享內存區一樣能夠用做線程間通信,不過沒這個必要,線程間原本就已經共享了同一進程內的一塊內存

進程間通訊主要包括管道、系統IPC(包括消息隊列、信號量、信號、共享內存等)、以及套接字socket。

1.管道:

管道主要包括無名管道和命名管道:管道可用於具備親緣關係的父子進程間的通訊,有名管道除了具備管道所具備的功能外,它還容許無親緣關係進程間的通訊

1.1 普通管道PIPE:

1)它是半雙工的(即數據只能在一個方向上流動),具備固定的讀端和寫端

2)它只能用於具備親緣關係的進程之間的通訊(也是父子進程或者兄弟進程之間)

3)它能夠當作是一種特殊的文件,對於它的讀寫也可使用普通的read、write等函數。可是它不是普通的文件,並不屬於其餘任何文件系統,而且只存在於內存中。

1.2 命名管道FIFO:

1)FIFO能夠在無關的進程之間交換數據

2)FIFO有路徑名與之相關聯,它以一種特殊設備文件形式存在於文件系統中。

 

2. 系統IPC:

2.1 消息隊列

消息隊列,是消息的連接表,存放在內核中。一個消息隊列由一個標識符(即隊列ID)來標記。 (消息隊列克服了信號傳遞信息少,管道只能承載無格式字節流以及緩衝區大小受限等特色)具備寫權限得進程能夠按照必定得規則向消息隊列中添加新信息;對消息隊列有讀權限得進程則能夠從消息隊列中讀取信息;

特色:

1)消息隊列是面向記錄的,其中的消息具備特定的格式以及特定的優先級。

2)消息隊列獨立於發送與接收進程。進程終止時,消息隊列及其內容並不會被刪除。

3)消息隊列能夠實現消息的隨機查詢,消息不必定要以先進先出的次序讀取,也能夠按消息的類型讀取。

2.2 信號量semaphore

信號量(semaphore)與已經介紹過的 IPC 結構不一樣,它是一個計數器,能夠用來控制多個進程對共享資源的訪問。信號量用於實現進程間的互斥與同步,而不是用於存儲進程間通訊數據。

特色:

1)信號量用於進程間同步,若要在進程間傳遞數據須要結合共享內存。

2)信號量基於操做系統的 PV 操做,程序對信號量的操做都是原子操做。

3)每次對信號量的 PV 操做不只限於對信號量值加 1 或減 1,並且能夠加減任意正整數。

4)支持信號量組。

2.3 信號signal

信號是一種比較複雜的通訊方式,用於通知接收進程某個事件已經發生。

2.4 共享內存(Shared Memory)

它使得多個進程能夠訪問同一塊內存空間,不一樣進程能夠及時看到對方進程中對共享內存中數據得更新。這種方式須要依靠某種同步操做,如互斥鎖和信號量等

特色:

1)共享內存是最快的一種IPC,由於進程是直接對內存進行存取

2)由於多個進程能夠同時操做,因此須要進行同步

3)信號量+共享內存一般結合在一塊兒使用,信號量用來同步對共享內存的訪問

 

3. 套接字SOCKET:

socket也是一種進程間通訊機制,與其餘通訊機制不一樣的是,它可用於不一樣主機之間的進程通訊。

 

 

線程間通訊的方式:

一、臨界區:

經過多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問;

二、互斥量 Synchronized/Lock:

採用互斥對象機制,只有擁有互斥對象的線程纔有訪問公共資源的權限。由於互斥對象只有一個,因此能夠保證公共資源不會被多個線程同時訪問

三、信號量 Semphare:

爲控制具備有限數量的用戶資源而設計的,它容許多個線程在同一時刻去訪問同一個資源,但通常須要限制同一時刻訪問此資源的最大線程數目。

四、事件(信號),Wait/Notify:

經過通知操做的方式來保持多線程同步,還能夠方便的實現多線程優先級的比較操做

 

 

併發(concurrency)和並行(parallelism)

併發(concurrency):指宏觀上看起來兩個程序在同時運行,好比說在單核cpu上的多任務。可是從微觀上看兩個程序的指令是交織着運行的,你的指令之間穿插着個人指令,個人指令之間穿插着你的,在單個週期內只運行了一個指令。這種併發並不能提升計算機的性能,只能提升效率。

並行(parallelism):指嚴格物理意義上的同時運行,好比多核cpu,兩個程序分別運行在兩個核上,二者之間互不影響,單個週期內每一個程序都運行了本身的指令,也就是運行了兩條指令。這樣說來並行的確提升了計算機的效率。因此如今的cpu都是往多核方面發展。

 

 

有了進程,爲何還要有線程?

線程產生的緣由:

進程可使多個程序能併發執行,以提升資源的利用率和系統的吞吐量;可是其具備一些缺點:

進程在同一時間只能幹一件事

進程在執行的過程當中若是阻塞,整個進程就會掛起,即便進程中有些工做不依賴於等待的資源,仍然不會執行。

 

所以,操做系統引入了比進程粒度更小的線程,做爲併發執行的基本單位,從而減小程序在併發執行時所付出的時空開銷,提升併發性。和進程相比,線程的優點以下:

從資源上來說,線程是一種很是"節儉"的多任務操做方式。在linux系統下,啓動一個新的進程必須分配給它獨立的地址空間,創建衆多的數據表來維護它的代碼段、堆棧段和數據段,這是一種"昂貴"的多任務工做方式。

從切換效率上來說,運行於一個進程中的多個線程,它們之間使用相同的地址空間,並且線程間彼此切換所需時間也遠遠小於進程間切換所須要的時間。據統計,一個進程的開銷大約是一個線程開銷的30倍左右。(

從通訊機制上來說,線程間方便的通訊機制。對不一樣進程來講,它們具備獨立的數據空間,要進行數據的傳遞只能經過進程間通訊的方式進行,這種方式不只費時,並且很不方便。線程則否則,因爲同一進程下的線程之間共享數據空間,因此一個線程的數據能夠直接爲其餘線程所用,這不只快捷,並且方便。

除以上優勢外,多線程程序做爲一種多任務、併發的工做方式,還有以下優勢:

一、使多CPU系統更加有效。操做系統會保證當線程數不大於CPU數目時,不一樣的線程運行於不一樣的CPU上。

二、改善程序結構。一個既長又複雜的進程能夠考慮分爲多個線程,成爲幾個獨立或半獨立的運行部分,這樣的程序纔會利於理解和修改。

 

 

多線程和多進程的不一樣

進程是資源分配的最小單位,而線程時CPU調度的最小單位。多線程之間共享同一個進程的地址空間,線程間通訊簡單,同步複雜,線程建立、銷燬和切換簡單,速度快,佔用內存少,適用於多核分佈式系統,可是線程間會相互影響,一個線程意外終止會致使同一個進程的其餘線程也終止,程序可靠性弱。而多進程間擁有各自獨立的運行地址空間,進程間不會相互影響,程序可靠性強,可是進程建立、銷燬和切換複雜,速度慢,佔用內存多,進程間通訊複雜,可是同步簡單,適用於多核、多機分佈。
 
 

多進程和多線程的使用場景 

多進程模型的優點是CPU

多線程模型主要優點爲線程間切換代價較小,所以適用於I/O密集型的工做場景,所以I/O密集型的工做場景常常會因爲I/O阻塞致使頻繁的切換線程。同時,多線程模型也適用於單機多核分佈式場景。

 

多進程模型,適用於CPU密集型。同時,多進程模型也適用於多機分佈式場景中,易於多機擴展。

 

 

死鎖發生的條件以及如何解決死鎖

死鎖是指兩個或兩個以上進程在執行過程當中,因爭奪資源而形成的相互等待的現象。死鎖發生的四個必要條件以下:

互斥條件:進程對所分配到的資源不容許其餘進程訪問,若其餘進程訪問該資源,只能等待,直至佔有該資源的進程使用完成後釋放該資源;

請求和保持條件:進程得到必定的資源後,又對其餘資源發出請求,可是該資源可能被其餘進程佔有,此時請求阻塞,但該進程不會釋放本身已經佔有的資源

不可剝奪條件:進程已得到的資源,在未完成使用以前,不可被剝奪,只能在使用後本身釋放

環路等待條件:進程發生死鎖後,必然存在一個進程-資源之間的環形鏈

解決死鎖的方法即破壞上述四個條件之一,主要方法以下:

資源一次性分配,從而剝奪請求和保持條件

可剝奪資源:即當進程新的資源未獲得知足時,釋放已佔有的資源,從而破壞不可剝奪的條件

資源有序分配法:系統給每類資源賦予一個序號,每一個進程按編號遞增的請求資源,釋放則相反,從而破壞環路等待的條件

 

 

互斥鎖(mutex)機制,以及互斥鎖和讀寫鎖的區別

一、互斥鎖和讀寫鎖區別:

互斥鎖:mutex,用於保證在任什麼時候刻,都只能有一個線程訪問該對象。當獲取鎖操做失敗時,線程會進入睡眠,等待鎖釋放時被喚醒。

讀寫鎖:rwlock,分爲讀鎖和寫鎖。處於讀操做時,能夠容許多個線程同時得到讀操做。可是同一時刻只能有一個線程能夠得到寫鎖。其它獲取寫鎖失敗的線程都會進入睡眠狀態,直到寫鎖釋放時被喚醒。 注意:寫鎖會阻塞其它讀寫鎖。當有一個線程得到寫鎖在寫時,讀鎖也不能被其它線程獲取;寫者優先於讀者(一旦有寫者,則後續讀者必須等待,喚醒時優先考慮寫者)。適用於讀取數據的頻率遠遠大於寫數據的頻率的場合。

互斥鎖和讀寫鎖的區別:

1)讀寫鎖區分讀者和寫者,而互斥鎖不區分

2)互斥鎖同一時間只容許一個線程訪問該對象,不管讀寫;讀寫鎖同一時間內只容許一個寫者,可是容許多個讀者同時讀對象。

二、Linux的4種鎖機制:

互斥鎖:mutex,用於保證在任什麼時候刻,都只能有一個線程訪問該對象。當獲取鎖操做失敗時,線程會進入睡眠,等待鎖釋放時被喚醒

讀寫鎖:rwlock,分爲讀鎖和寫鎖。處於讀操做時,能夠容許多個線程同時得到讀操做。可是同一時刻只能有一個線程能夠得到寫鎖。其它獲取寫鎖失敗的線程都會進入睡眠狀態,直到寫鎖釋放時被喚醒。 注意:寫鎖會阻塞其它讀寫鎖。當有一個線程得到寫鎖在寫時,讀鎖也不能被其它線程獲取;寫者優先於讀者(一旦有寫者,則後續讀者必須等待,喚醒時優先考慮寫者)。適用於讀取數據的頻率遠遠大於寫數據的頻率的場合。

自旋鎖:spinlock,在任什麼時候刻一樣只能有一個線程訪問對象。可是當獲取鎖操做失敗時,不會進入睡眠,而是會在原地自旋,直到鎖被釋放。這樣節省了線程從睡眠狀態到被喚醒期間的消耗,在加鎖時間短暫的環境下會極大的提升效率。但若是加鎖時間過長,則會很是浪費CPU資源。

RCU:即read-copy-update,在修改數據時,首先須要讀取數據,而後生成一個副本,對副本進行修改。修改完成後,再將老數據update成新的數據。使用RCU時,讀者幾乎不須要同步開銷,既不須要得到鎖,也不使用原子指令,不會致使鎖競爭,所以就不用考慮死鎖問題了。而對於寫者的同步開銷較大,它須要複製被修改的數據,還必須使用鎖機制同步並行其它寫者的修改操做。在有大量讀操做,少許寫操做的狀況下效率很是高。

 

 

A* a = new A; a->i = 10;在內核中的內存分配上發生了什麼?

一、程序內存管理:

一個程序本質上都是由BSS段、data段、text段三個組成的。能夠看到一個可執行程序在存儲(沒有調入內存)時分爲代碼段、數據區和未初始化數據區三部分。

BSS段(未初始化數據區):一般用來存放程序中未初始化的全局變量和靜態變量的一塊內存區域。BSS段屬於靜態分配,程序結束後靜態變量資源由系統自動釋放。

數據段:存放程序中已初始化的全局變量的一塊內存區域。數據段也屬於靜態內存分配

 

代碼段:存放程序執行代碼的一塊內存區域。這部分區域的大小在程序運行前就已經肯定,而且內存區域屬於只讀。在代碼段中,也有可能包含一些只讀的常數變量

 

text段和data段在編譯時已經分配了空間,而BSS段並不佔用可執行文件的大小,它是由連接器來獲取內存的。

 

bss段(未進行初始化的數據)的內容並不存放在磁盤上的程序文件中。其緣由是內核在程序開始運行前將它們設置爲0。須要存放在程序文件中的只有正文段和初始化數據段。

 

data段(已經初始化的數據)則爲數據分配空間,數據保存到目標文件中。

 

數據段包含通過初始化的全局變量以及它們的值。BSS段的大小從可執行文件中獲得,而後連接器獲得這個大小的內存塊,緊跟在數據段的後面。當這個內存進入程序的地址空間後所有清零。包含數據段和BSS段的整個區段此時一般稱爲數據區。

 

可執行程序在運行時又多出兩個區域:棧區和堆區。

 

棧區:由編譯器自動釋放,存放函數的參數值、局部變量等。每當一個函數被調用時,該函數的返回類型和一些調用的信息被存放到棧中。而後這個被調用的函數再爲他的自動變量和臨時變量在棧上分配空間。每調用一個函數一個新的棧就會被使用。棧區是從高地址位向低地址位增加的,是一塊連續的內存區域,最大容量是由系統預先定義好的,申請的棧空間超過這個界限時會提示溢出,用戶能從棧中獲取的空間較小。

 

堆區:用於動態分配內存,位於BSS和棧中間的地址區域。由程序員申請分配和釋放。堆是從低地址位向高地址位增加,採用鏈式存儲結構。頻繁的 malloc/free形成內存空間的不連續,產生碎片。當申請堆空間時庫函數是按照必定的算法搜索可用的足夠大的空間。所以堆的效率比棧要低的多。

二、A* a = new A; a->i = 10:

1)A *a:a是一個局部變量,類型爲指針,故而操做系統在程序棧區開闢4/8字節的空間(0x000m),分配給指針a。

2)new A:經過new動態的在堆區申請類A大小的空間(0x000n)。

3)a = new A:將指針a的內存區域填入棧中類A申請到的地址的地址。即*(0x000m)=0x000n。

4)a->i:先找到指針a的地址0x000m,經過a的值0x000n和i在類a中偏移offset,獲得a->i的地址0x000n + offset,進行*(0x000n + offset) = 10的賦值操做,即內存0x000n + offset的值是10。

 

 

一個類,裏面有static,virtual,之類的,來講一說這個類的內存分佈 

一、static修飾符

1)static修飾成員變量

對於非靜態數據成員,每一個類對象都有本身的拷貝。而靜態數據成員被當作是類的成員,不管這個類被定義了多少個,靜態數據成員都只有一份拷貝,爲該類型的全部對象所共享(包括其派生類)。因此,靜態數據成員的值對每一個對象都是同樣的,它的值能夠更新。

由於靜態數據成員在全局數據區分配內存,屬於本類的全部對象共享,因此它不屬於特定的類對象,在沒有產生類對象前就可使用。

2)static修飾成員函數

與普通的成員函數相比,靜態成員函數因爲不是與任何的對象相聯繫,所以它不具備this指針。從這個意義上來講,它沒法訪問屬於類對象的非靜態數據成員,也沒法訪問非靜態成員函數,只能調用其餘的靜態成員函數。

Static修飾的成員函數,在代碼區分配內存。

二、C++繼承和虛函數

C++多態分爲靜態多態和動態多態。靜態多態是經過重載和模板技術實現,在編譯的時候肯定。動態多態經過虛函數和繼承關係來實現,執行動態綁定,在運行的時候肯定。

動態多態實現有幾個條件:

(1) 虛函數;

(2) 一個基類的指針或引用指向派生類的對象;

基類指針在調用成員函數(虛函數)時,就會去查找該對象的虛函數表。虛函數表的地址在每一個對象的首地址。查找該虛函數表中該函數的指針進行調用。

每一個對象中保存的只是一個虛函數表的指針,C++內部爲每個類維持一個虛函數表,該類的對象的都指向這同一個虛函數表。

虛函數表中爲何就能準確查找相應的函數指針呢?由於在類設計的時候,虛函數表直接從基類也繼承過來,若是覆蓋了其中的某個虛函數,那麼虛函數表的指針就會被替換,所以能夠根據指針準確找到該調用哪一個函數。

三、virtual修飾符

若是一個類是局部變量則該類數據存儲在棧區,若是一個類是經過new/malloc動態申請的,則該類數據存儲在堆區。

若是該類是virutal繼承而來的子類,則該類的虛函數表指針和該類其餘成員一塊兒存儲。虛函數表指針指向只讀數據段中的類虛函數表,虛函數表中存放着一個個函數指針,函數指針指向代碼段中的具體函數。

若是類中成員是virtual屬性,會隱藏父類對應的屬性。

 

 

軟連接和硬連接區別

爲了解決文件共享問題,Linux引入了軟連接和硬連接。除了爲Linux解決文件共享使用,還帶來了隱藏文件路徑、增長權限安全及節省存儲等好處。若1個inode號對應多個文件名,則爲硬連接,即硬連接就是同一個文件使用了不一樣的別名,使用ln建立。若文件用戶數據塊中存放的內容是另外一個文件的路徑名指向,則該文件是軟鏈接。軟鏈接是一個普通文件,有本身獨立的inode,可是其數據塊內容比較特殊。

 

 

用戶態和內核態區別

用戶態和內核態是操做系統的兩種運行級別,二者最大的區別就是特權級不一樣。 用戶態擁有最低的特權級,內核態擁有較高的特權級。運行在用戶態的程序不能直接訪問操做系統內核數據結構和程序。內核態和用戶態之間的轉換方式主要包括:系統調用,異常和中斷。
 
 

協程

協程是一種用戶態的輕量級線程,協程的調度徹底由用戶控制。從技術的角度來講,「協程就是你能夠暫停執行的函數」。協程擁有本身的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操做棧則基本沒有內核切換的開銷,能夠不加鎖的訪問全局變量,因此上下文的切換很是快。

 

協程和進程線程的區別

1) 一個線程能夠多個協程,一個進程也能夠單獨擁有多個協程。

2) 線程進程都是同步機制,而協程則是異步

3) 協程能保留上一次調用時的狀態,每次過程重入時,就至關於進入上一次調用的狀態。

4)線程是搶佔式,而協程是非搶佔式的,因此須要用戶本身釋放使用權來切換到其餘協程,所以同一時間其實只有一個協程擁有運行權,至關於單線程的能力。

5)協程並非取代線程, 並且抽象於線程之上, 線程是被分割的CPU資源, 協程是組織好的代碼流程, 協程須要線程來承載運行, 線程是協程的資源, 但協程不會直接使用線程, 協程直接利用的是執行器(Interceptor), 執行器能夠關聯任意線程或線程池, 可使當前線程, UI線程, 或新建新程.。

6)線程是協程的資源。協程經過Interceptor來間接使用線程這個資源。

 
 

 

 

IO多路複用(IO multiplexing)

 IO multiplexing這個詞可能有點陌生,可是若是我說select/epoll,大概就都能明白了。有些地方也稱這種IO方式爲 事件驅動IO(event driven IO)。咱們都知道, select/epoll的好處就在於單個process就能夠同時處理多個網絡鏈接的IO。它的基本原理就是 select/epoll這個function會不斷的輪詢所負責的全部socket,當某個socket有數據到達了,就通知用戶進程。它的流程如圖:

 

當用戶進程調用了select,那麼整個進程會被block,而同時,kernel會「監視」全部select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操做,將數據從kernel拷貝到用戶進程。
    這個圖和blocking IO的圖其實並無太大的不一樣,事實上還更差一些。由於這裏須要使用兩個系統調用(select和recvfrom),而blocking IO只調用了一個系統調用(recvfrom)。可是,用select的優點在於它能夠同時處理多個connection。

    強調:

    1. 若是處理的鏈接數不是很高的話,使用select/epoll的web server不必定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。

    2. 在多路複用模型中,對於每個socket,通常都設置成爲non-blocking,可是,如上圖所示,整個用戶的process實際上是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。

    結論: select的優點在於能夠處理多個鏈接,不適用於單個鏈接 

 

 

 

(1)select==>時間複雜度O(n)

它僅僅知道了,有I/O事件發生了,卻並不知道是哪那幾個流(可能有一個,多個,甚至所有),咱們只能無差異輪詢全部流,找出能讀出數據,或者寫入數據的流,對他們進行操做。因此select具備O(n)的無差異輪詢複雜度,同時處理的流越多,無差異輪詢時間就越長。

(2)poll==>時間複雜度O(n)

poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,而後查詢每一個fd對應的設備狀態, 可是它沒有最大鏈接數的限制,緣由是它是基於鏈表來存儲的.

(3)epoll==>時間複雜度O(1)

epoll能夠理解爲event poll,不一樣於忙輪詢和無差異輪詢,epoll會把哪一個流發生了怎樣的I/O事件通知咱們。因此咱們說epoll其實是事件驅動(每一個事件關聯上fd)的,此時咱們對這些流的操做都是有意義的。(複雜度下降到了O(1))

 

 int epoll_create(int size); 
 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 
 int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); 

 

epoll的高效就在於,當咱們調用epoll_ctl往裏塞入百萬個句柄時,epoll_wait仍然能夠飛快的返回,並有效的將發生事件的句柄給咱們用戶。這是因爲咱們在調用epoll_create時,內核除了幫咱們在epoll文件系統裏建了個file結點,在內核cache裏建了個紅黑樹用於存儲之後epoll_ctl傳來的socket外,還會再創建一個list鏈表,用於存儲準備就緒的事件,當epoll_wait調用時,僅僅觀察這個list鏈表裏有沒有數據便可。有數據就返回,沒有數據就sleep,等到timeout時間到後即便鏈表沒數據也返回。因此,epoll_wait很是高效。 那麼,這個準備就緒list鏈表是怎麼維護的呢?當咱們執行epoll_ctl時,除了把socket放到epoll文件系統裏file對象對應的紅黑樹上以外,還會給內核中斷處理程序註冊一個回調函數,告訴內核,若是這個句柄的中斷到了,就把它放到準備就緒list鏈表裏。因此,當一個socket上有數據到了,內核在把網卡上的數據copy到內核中後就來把socket插入到準備就緒鏈表裏了。 如此,一顆紅黑樹,一張準備就緒句柄鏈表,少許的內核cache,就幫咱們解決了大併發下的socket處理問題。執行epoll_create時,建立了紅黑樹和就緒鏈表,執行epoll_ctl時,若是增長socket句柄,則檢查在紅黑樹中是否存在,存在當即返回,不存在則添加到樹幹上,而後向內核註冊回調函數,用於當中斷事件來臨時向準備就緒鏈表中插入數據。執行epoll_wait時馬上返回準備就緒鏈表裏的數據便可。

 

 

 

 

mmap

mmap是一種內存映射文件的方法,即將一個文件或者其它對象映射到進程的地址空間,實現文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關係。實現這樣的映射關係後,進程就能夠採用指針的方式讀寫操做這一段內存,而系統會自動回寫髒頁面到對應的文件磁盤上,即完成了對文件的操做而沒必要再調用read,write等系統調用函數。相反,內核空間對這段區域的修改也直接反映用戶空間,從而能夠實現不一樣進程間的文件共享。以下圖所示:

 

 

總而言之,常規文件操做須要從磁盤到頁緩存再到用戶主存的兩次數據拷貝。而mmap操控文件,只須要從磁盤到用戶主存的一次數據拷貝過程。說白了,mmap的關鍵點是實現了用戶空間和內核空間的數據直接交互而省去了空間不一樣數據不通的繁瑣過程。所以mmap效率更高。

由上文討論可知,mmap優勢共有一下幾點:

一、對文件的讀取操做跨過了頁緩存,減小了數據的拷貝次數,用內存讀寫取代I/O讀寫,提升了文件讀取效率。

二、實現了用戶空間和內核空間的高效交互方式。兩空間的各自修改操做能夠直接反映在映射的區域內,從而被對方空間及時捕捉。

三、提供進程間共享內存及相互通訊的方式。無論是父子進程仍是無親緣關係的進程,均可以將自身用戶空間映射到同一個文件或匿名映射到同一片區域。從而經過各自對映射區域的改動,達到進程間通訊和進程間共享的目的。

     同時,若是進程A和進程B都映射了區域C,當A第一次讀取C時經過缺頁從磁盤複製文件頁到內存中;但當B再讀C的相同頁面時,雖然也會產生缺頁異常,可是再也不須要從磁盤中複製文件過來,而可直接使用已經保存在內存中的文件數據。

四、可用於實現高效的大規模數據傳輸。內存空間不足,是制約大數據操做的一個方面,解決方案每每是藉助硬盤空間協助操做,補充內存的不足。可是進一步會形成大量的文件I/O操做,極大影響效率。這個問題能夠經過mmap映射很好的解決。換句話說,但凡是須要用磁盤空間代替內存的時候,mmap均可以發揮其功效。

 

 

MD5應用

MD5的典型應用是對一段信息(Message)產生信息摘要(Message-Digest),以防止被篡改。

MD5將整個文件看成一個大文本信息,經過其不可逆的字符串變換算法,產生了這個惟一的MD5信息摘要。

MD5就能夠爲任何文件(無論其大小、格式、數量)產生一個一樣獨一無二的「數字指紋」,若是任何人對文件作了任何改動,其MD5值也就是對應的「數字指紋」都會發生變化。

MD5其實是一種有損壓縮技術,壓縮前文件同樣MD5值必定同樣,反之MD5值同樣並不能保證壓縮前的數據是同樣的。在密碼學上發生這樣的機率是很小的,因此MD5在密碼加密領域有一席之地。可是專業的黑客甚至普通黑客也能夠利用MD5值,實際是有損壓縮技術這一原理,將MD5的逆運算的值做爲一張表俗稱彩虹表的散列表來破解密碼。

相關文章
相關標籤/搜索