【5.linux操做系統】-併發/異步/同步/阻塞/非阻塞模型

網絡從網卡到線程過程

clipboard.pngclipboard.png

1.一個以太網接口接收發送到它的單播地址和以太網廣播地址的幀。當一個完整的幀可用時,接口就產生一個硬中斷,而且內核調用接口層函數leintr
2.leintr檢測硬件,而且若是有一個幀到達,就調用leread把這個幀從接口轉移到一個mbuf(各層之間傳輸數據都用這個)鏈中,構造單獨的地址信息etherheaher。
etherinput檢查結構etherheaher來判斷接收到的數據的類型,根據以太網類型字段來跳轉。對於一個IP分組,schednetisr調度一個IP軟件中斷,並選擇IP輸入隊列,ipintrq。對於一個ARP分組,調度ARP軟件中斷,並選擇arpintrq。並將接收到的分組加入到隊列中等待處理。
3.當收到的數據報的協議字段指明這是一個TCP報文段時,ipintrq(經過協議轉換表中的prinput函數)會調用tcpinp t進行處理.從mbuf中取ip,tcp首部,尋找pcb,發送給插口層
3.1個關於pcb
每一個線程的task_struct都有打開文件描述符,若是是sock類型還會關聯到全局inpcb中.
clipboard.png
listen某個fd時(new socket()=>bind(listen的端口)=>lisnten())在pcb中該fd是listen狀態
3.2in_pcblookup
搜索它的整個Internet PCB表,找到一個匹配。徹底匹配得到最高優先級,包含通配的最小通配數量的優先級高。因此當同一個端口已經創建鏈接後有了外部地址和端口,再有數據會選擇該插口。好比當140.252.1.11:1500來的數據直接就匹配到第三個的插口,其餘地址的發送到第一個插口。
4.插口層
4.1 listen 若是監聽插口收到了報文段
listen狀態下只接收SYN,即便攜帶了數據也等創建鏈接後才發送,建立新的so,在收到三次握手的最後一個報文段後,調用soisconnected喚醒插口層acceptreact

clipboard.png

4.2 插口層accept
while (so->so_qlen == 0 && so->so_error == 0) {tsleep((caddr_t)&so->so_timeo, PSOCK | PCATCH,netcon, 0))}
當so_qlen不是0,調用falloc(p, &fp, &tmpfd)建立新的插口fd,從插口隊列中複製,返回,此時該fd也在線程的打開文件中了。調用soqremque將插口從接收隊列中刪除。
4.3 插口層read,send 從緩衝區複製mbuf。略
5.線程調用內核的accept,從調用開始會sleep直到此時能夠返回fd。
【後文若用了epoll在lsfd有事件時通知線程再調用accept會節省調用到sleep時間。】linux

io傳輸

DMA

DMA:硬件到內存數據拷貝脫離cpu
CPU與外設之間的數據傳送方式有程序傳送方式(cpu輪詢檢查,執行輸入指令(IN)或輸出指令(OUT))、中斷傳送方式(外設主動,請求前外設與CPU能夠並行工做,須要進行斷點和現場的保護和恢復,浪費了不少CPU的時間,適合少許數據的傳送)。CPU是經過系統總線與其餘部件鏈接並進行數據傳輸。
一般系統總線是由CPU管理的,在DMA方式時,由DMA控制器發一個信號給CPU。DMA控制器得到總線控制權,控制傳送的字節數,判斷DMA是否結束,以及發出DMA結束信號nginx

clipboard.png

socket四次數據拷貝

須要複製硬件數據到socket發送時,原方案4次用戶空間與內核空間的上下文切換,以及4次數據拷貝(涉及到TCP時,tcp/ip維護send buffer和recv buffer緩衝區——內核空間,須要cpu從用戶態複製到內核態,而後DMA經過網卡發送。)
clipboard.png
如下叫領拷貝(不拷貝到用戶態)編程

sendfile

2次用戶空間與內核空間的上下文切換,以及3次數據的拷貝,但用戶不能直接操做寫安全

clipboard.png

mmap

user space不拷貝共享kernel space數據:4次用戶空間與內核空間的上下文切換,以及3次數據拷貝網絡

clipboard.png

mmap是os到用戶內存無拷貝,用指針。首先,應用程序調用mmap(圖中1),陷入到內核中後調用do_mmap_pgoff(圖中2)。該函數從應用程序的地址空間中分配一段區域做爲映射的內存地址,並使用一個VMA(vm_area_struct)結構表明該區域,以後就返回到應用程序(圖中3)。當應用程序訪問mmap所返回的地址指針時(圖中4),因爲虛實映射還沒有創建,會觸發缺頁中斷(圖中5)。以後系統會調用缺頁中斷處理函數(圖中6),在缺頁中斷處理函數中,內核經過相應區域的VMA結構判斷出該區域屬於文件映射,因而調用具體文件系統的接口讀入相應的Page Cache項(圖中七、八、9),並填寫相應的虛實映射表。session

clipboard.png

epoll

  • 原理
    poll/select/epoll的實現都是基於文件提供的poll方法(f_op->poll),該方法利用poll_table提供的_qproc方法向文件內部事件掩碼_key對應的的一個或多個等待隊列(wait_queue_head_t)上添加包含喚醒函數(wait_queue_t.func)的節點(wait_queue_t),並檢查文件當前就緒的狀態返回給poll的調用者(依賴於文件的實現)。當文件的狀態發生改變時(例如網絡數據包到達),文件就會遍歷事件對應的等待隊列並調用回調函數(wait_queue_t.func)喚醒等待線程。
  • 數據結構:
    clipboard.png
  • epoll_crete 建立event_poll,實際上建立了一個socketfd,稱epfd。
  • epoll_ctl 將回調爲ep_poll_callback的節點 加入到epitem對應fd的等待隊列中(即sk_sleep的wait_queue_head_t),關聯到event_poll的紅黑樹等結構體中
  • epoll_wait 將回調爲try_to_wake_up的節點 加入到epfd的等待隊列中你。
    當發生事件,socket調用ep_poll_callback 會調用try_to_wake_up 進而喚醒wait的線程,向用戶賦值rdlist數據,用戶線程繼續執行
    (以scoket爲例,當socket數據ready,終端會調用相應的接口函數好比rawv6_rcv_skb,此函數會調用sock_def_readable而後,經過sk_has_sleeper判斷sk_sleep上是否有等待的進程,若是有那麼經過wake_up_interruptible_sync_poll函數調用ep_poll_callback。從wait隊列中調出epitem,檢查狀態epitem的event.events,如果感興趣的事情發生,加入到rdllist或者ovflist中,調用try_to_wake_up。)
  • 數據拷貝:
    1.拷貝新添加的events
    YSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,struct epoll_event __user *, event)
    copy_from_user(&epds, event, sizeof(struct epoll_event)))
    2.傳給用戶的就緒的event
    在ep_send_events_proc時
    __put_user(revents, &uevent->events。__put_user(epi->event.data, &uevent->data)
  • 與select區別
    重複讀入參數,全量的掃描文件描述符;調用開始,將進程加入到每一個文件描述符的等待隊列,在調用結束後又把進程從等待隊列中刪除;(每次發生事件fd位圖結構改變,從新清除再select)select最多支持1024個文件描述符。從內核實現上,循環全部fd,調用poll->poll_wait加入到fd等待隊列。有時間設置mask掩碼,循環fd檢測有mask返回。(nfds,readxx,writexx)
    poll結構上的差別,(fds裏直接有事件),不須要遍歷全部nfds。無1024限制
    epoll 註冊事件只須要一次拷貝(增量拷貝,依靠回調),另外返回就緒fd,不須要遍歷全部的。把過程拆除帶ctl加入poll_wait有事件直接通知,加入rdllist返回

運行模型

是否當即返回。數據結構

阻塞:空cpu,IO阻塞線程
非阻塞

是否由本線程執行多線程

同步IO
異步

1.全部請求單進程/線程

2.一個請求一個線程/進程

accept後一個鏈接所有過程在一個進程/線程 ,結束後結束線程/進程,每次有新鏈接建立一個新的進程/線程去處理請求併發

  • 一個鏈接一個進程:父進程fork子進程 =》fork代價大 百級別
  • prefork 多個進程會accept同一個lsfd,linux2.6已經支持就覺多進程同時accept一個時的驚羣現象。
  • 一個鏈接一個線程 萬級別限制,線程相互影響不穩定
  • prethread 多線程共享數據,能夠直接accept後分配給線程,也能夠多個線程共同accept(accept實現了線程安全?).

3.一個進程/線程處理多個請求

線程池/進程池+非阻塞+IO多路複用 (非阻塞+IO多路複用 少了哪一個這種模型都沒有意義)
reactor 監聽全部類型事件,區分accept和業務處理

  • 單reactor單線程。reactor是負責IO事件監聽
  • 單reactor多線程 接收後read後給子線程處理,處理後返回發送;主線程負責全部IO事件處理

    clipboard.png

    clipboard.png

  • 多reactor多進程 nginx沒有accept後分配,而是子進程本身listen,accept。
  • 多reactor多線程 主accept接收後把fd給子線程,子線程讀-處理-寫(更簡單,無需傳遞讀寫數據)memchache、netty

    這個網上的圖是錯的。 accept後全部的讀寫處理都在一個線程中,無共享數據須要傳遞

    clipboard.png

總結下:

基本上是accept確定要在一個線程中,由於只有一個fd。
1)單reactor單線程 accept+read/process/send
2)單reactor多線程 accept+read/send =》多process
3)多reactor多線程 accepct=>多read/process/send
4)另外一種 accepct[0號]=>子多read/send =》多process 當只有一個時退化爲單reactor多線程。線上就這個。

適用範圍:

假設4個請求併發鏈接,2個線程

  • 若p是瓶頸,好比p1佔用4個格子
    1)單reactor多線程

    r1->r2->r3->r4->s1->s2->s3->s4
      p1w|w|w|w|->p3w|w|w|w|
          p2w|w|w|w|->p4w|w|w|w|

    2)多reactor多線程

    r1->p1w|w|w|w|->r3->p3w|w|w|w|->s1->s3
    r2->p2w|w|w|w|->r4->p4w|w|w|w|->s2->s4

    此時單reactor多線程更快。多reactor多線程編寫簡單

  • 若r是瓶頸(好比佔3個,4個換行了=。=)
    1)單reactor多線程

    r1w|w|w|->r2w|w|w|->r3w|w|w|->r4w|w|w|->s1w|w|w|->s2w|w|w|->s3w|w|w|->s4w|w|w|
            p1->                p3
                      p2->                p4

    2)多reactor多線程

    r1w|w|w|->p1->r3w|w|w|->p3->s1w|w|w|->s3w|w|w|
    r2w|w|w|->p2->r4w|w|w|->p4->s2w|w|w|->s4w|w|w|

    此時多reactor多線程快

  • 最後一種模式分別

    r1->r3      ->s1        ->s3
      p1w|w|w|w|->p3w|w|w|w|
    r2->r4      ->s2        ->s4
      p2w|w|w|w|->p4w|w|w|w|
    
    r1w|w|w|->r3w|w|w|->s1w|w|w|->s3w|w|w|
            p1->      p3 
    r2w|w|w|->r4w|w|w|->s2w|w|w|->s4w|w|w|
            p2->      p4
  • 結論:
    若讀寫爲瓶頸建議多reactor多線程。
    若處理爲瓶頸建議單reactor多線程,
    用最後一種混合須要評估下,若是處理爲瓶頸還能夠考慮下,io爲瓶頸就不用了,由於並無快多少,反而編程麻煩。

4.proactor

與reactor區別是reactor是同步讀寫,preactor是異步讀寫

  • 在Reactor中實現讀
    註冊讀就緒事件和相應的事件處理器。
    事件分發器等待事件。
    事件到來,激活分發器,分發器調用事件對應的處理器。
    事件處理器完成實際的讀操做,處理讀到的數據,註冊新的事件,而後返還控制權。
  • 在Proactor中實現讀:
    處理器發起異步讀操做(注意:操做系統必須支持異步IO)。在這種狀況下,處理器無視IO就緒事件,它關注的是完成事件。
    事件分發器等待操做完成事件。
    在分發器等待過程當中,操做系統利用並行的內核線程執行實際的讀操做,並將結果數據存入用戶自定義緩衝區,最後通知事件分發器讀操做完成。
    事件分發器呼喚處理器。
    事件處理器處理用戶自定義緩衝區中的數據,而後啓動一個新的異步操做,並將控制權返回事件分發器。

關於數據共享

早在1973年,actor模式被提出(跟圖靈機一個級別的模式,只是個模式),主要是對立於數據共享加鎖的。一個是分佈式計算中沒辦法很好的共享數據,另外一個是共享數據加鎖會阻塞線程(單機的話)/請求超時(分佈式),還有個不重要的有時候鎖不住(好比cpu的多級cache,沒個線程裏的cache不一樣步),所以採用一種非阻塞和通訊的方式,數據發送到actor排隊後即返回不加鎖,經過通訊傳遞改變,actor是整個數據+行爲(對象)的組合,不只僅負責數據的鎖,actor之間要作到無共享,發給actor的本身複製一份新的,actor能夠繼續調actor,因此要都無共享。(這麼說非阻塞IO,select等都是借鑑的這個。)
actor行爲
1.Actor將消息加入到消息隊列的尾部。
2.假如一個Actor並未被調度執行,則將其標記爲可執行。
3.一個(對外部不可見)調度器對Actor的執行進行調度。
4.Actor從消息隊列頭部選擇一個消息進行處理。
5.Actor在處理過程當中修改自身的狀態,併發送消息給其餘的Actor。
爲了實現這些行爲,Actor必須有如下特性:
● 郵箱(做爲一個消息隊列)
● 行爲(做爲Actor的內部狀態,處理消息邏輯)
● 消息(請求Actor的數據,可當作方法調用時的參數數據)
● 執行環境(好比線程池,調度器,消息分發機制等)
● 位置信息(用於後續可能會發生的行爲)
另一種:CSP,不要經過共享內存來通訊,而應該經過通訊來共享內存的思想,Actor 和 CSP 就是兩種基於這種思想的併發編程模型.Actor 模型的重點在於參與交流的實體,而 CSP 模型的重點在於用於交流的通道.channel。channel共享帶鎖,再也不擴展了。actor的設計也要抽象好,好比好比左右兩個叉子,若是加鎖。鎖(左右)/ 爲了併發,單獨左右。actor要左右組合在一塊兒,每次判斷左叉子和右叉子都有返回成功。zmq 每一個線程綁定一個cpu,線程之間不會共享session,不須要加鎖。每一個鏈接的操做都在一個worker中.通訊傳遞數據。

相關文章
相關標籤/搜索