Unix網路編程之IO模型

  • 2020 年 3 月 10 日
  • 筆記

點擊上方疾風先生可以訂閱哦

首先,我們要了解IO模型先要知道在底層作業系統是通過哪些設備來實現數據的傳輸,其次要了解IO模型中哪些是發生阻塞調用操作,然後有了上述的基本認知之後,開始來了解IO模型是如何演進,最後通過IO模型的演進我們要辨別IO模型中的關鍵術語聯繫與區分,上述的思維導圖囊括以下要分享的知識點!

數據傳輸交換媒介

Unix作業系統結構圖

概要說明

  • 用戶空間: 將上述用戶級別或者是unix編程的應用程式的部分稱為用戶空間,我們可以通過啟動進程來調用內核命令完成從硬體設備讀取或寫入等操作
  • 系統內核: 是直接與電腦硬體打交道的應用程式級別,在電腦相關的書籍中也稱為作業系統,可以通過作業系統級別提供的一些組件來幫助用戶進程與電腦硬體完成通訊交互的操作,可以稱為一個中轉站
  • 硬體: 對於網路編程而言,與關聯的硬體設備就是網路介面控制器等設備,通過網路介面控制器將位元組流數據傳輸到互聯網再根據IP地址等資訊傳輸到其他電腦系統應用程式,實現多台電腦系統之間的通訊
  • 文件描述符(File descriptor):在linux/unix系統中,文件進程存儲著一份文件描述表(數組存儲),數組存儲file結構指針類型,文件進程通過數組索引來完成文件的操作,這裡的數組索引也就是文件描述符,簡稱為fd

至此,通過上述的圖解以及簡要說明可知,用戶進程無法直接操作硬體設備,必須通過向內核發起請求命令,然後由內核向硬體設備解析並執行相應的命令操作,於是跨電腦的進程網路數據傳輸需要將數據發送到系統內核再到網路介面控制器,通過網路介面控制器實現電腦之間的通訊,如下圖所示:

網路IO阻塞操作

IO讀取操作核心步驟

  • 用戶進程向系統內核發起數據讀取操作請求,必須等待內核從硬體設備中獲取數據直到數據報準備完成
  • 當內核從設備中準備好數據的時候,需要將數據報從內核複製到用戶空間中

IO阻塞操作

  • recvfrom函數:屬於IO操作的系統調用,這裡區分系統內核和用戶進程,調用函數的時候需要從用戶進程的上下文空間切換到內核空間中運行,一段時間之後才能切換回來
  • select/poll函數: 允許進程指示內核等待多個事件中的任何一個發生,並只在有一個或者多個事件發生抑或是超過指定時間段便喚醒用戶進程,告知當前事件是否就緒狀態

IO操作的對比

  • recvfrom函數: 調用當前函數的時候,此時用戶進程受阻於系統內核,這個時候其他進程發起請求調用的時候須等待系統內核完成工作之外才能處理其他的進程請求,也就是1:1模型
  • select/poll函數:調用當前函數的時候,此時用戶進程受阻於當前select函數,不斷輪詢向內核select就緒狀態的描述符或者是超過指定時間段被喚醒,由於select系統函數調用是攜帶描述符集合到內核,因此可以對N個描述符進行監聽(姑且稱描述符的就緒狀態為事件可讀狀態,後面論述均以事件論述),即N:1模型,如下圖:

IO模型演進

基於上述的數據傳輸以及IO阻塞操作可知,網路編程需要讀取網路傳輸過來的數據需要先經過系統內核再到用戶空間,期間需要系統內核等待數據準備完成以及數據複製到用戶空間兩個步驟,同時為了簡化概念,將TCP的低套接字等資訊隱藏不做說明,用簡單且易於理解的UDP傳輸方式來演示Unix下的5種IO模型

阻塞式IO模型

  • 自應用進程發起recvfrom系統調用,在此期間一直處於被阻塞,因為這個時候需要等待內核獲取數據報資訊複製到用戶空間中,除非被中斷異常返回,否則將一直處於阻塞狀態
  • 以下是時序圖展示阻塞式IO

非阻塞式IO模型

  • 非阻塞式主要體現在用戶進程發起recvfrom系統調用的時候,這個時候系統內核還沒有接收到數據報,直接返回錯誤給用戶進程,告訴用戶進程「當前還沒有數據報可達,晚點再來」
  • 用戶進程接收到資訊,但是用戶進程不知道什麼時候數據報可達,於是就開始不斷輪詢(polling)向系統內核發起recvfrom的系統調用「詢問數據來了沒」,如果沒有則繼續返回錯誤
  • 用戶進程輪詢發起recvfrom系統調用直至數據報可達,這個時候需要等待系統內核複製數據報到用戶進程的緩衝區,複製完成之後將返回成功提示
  • 以下時序圖展示NIO的方式

IO復用模型

  • IO復用模型是使用select或者poll函數向系統內核發起調用,阻塞於這兩個之一的系統函數調用,而不是真正阻塞於實際的IO操作(recvfrom調用才是實際阻塞IO操作的系統調用)
  • 阻塞於select函數的調用,等待系統內核準備數據並通知當前事件為可讀狀態
  • 當select接收到系統內核通知事件為可讀狀態時,就可以向系統內核發起recvfrom調用將數據複製到用戶空間的緩衝區
  • IO復用模式時序圖如下

訊號驅動式IO模型

  • 先開啟套接字的訊號IO驅動功能,並通過一個內置安裝訊號處理函數的signaction系統調用,當發起調用之後將會直接返回
  • 其次,等待內核從網路中接收數據報之後,向用戶進程發送當前數據可達的訊號給到訊號處理函數
  • 訊號處理函數接收到資訊就發起recvfrom系統調用等待內核複製數據報到用戶空間的緩衝區
  • 接收到複製完成的返回成功提示之後,應用進程就可以開始從網路中讀取數據
  • 上述是基於訊號驅動式IO模型,當系統內核描述符就緒時將會發送SIGNO給到用戶進程,這個時候再發起recvfrom的系統調用等待返回成功提示,流程如下:

非同步IO模型

  • 由POSIX規範定義,告知系統內核啟動某個操作,並讓內核在整個操作包括數據等待以及數據複製過程的完成之後通知用戶進程數據已經準備完成,可以進行讀取數據
  • 與上述的訊號IO模型區分在於通過非同步方式直接通知我們何時IO操作完成,而訊號IO是通知我們何時可以啟動一個IO操作
  • 時序圖如下

5種IO模型分析

  • 對於阻塞式IO模型,屬於1:1模型,也就是系統內核只能處理一個請求,其他請求過來的時候必須等待當前請求處理完成才能進行,性能相當差,可以通過多執行緒變更為N:1模型,與非阻塞式IO類似,區分在於前者多執行緒,後者單執行緒
  • 對於驅動式訊號IO模型,雖然是非阻塞式IO模型,但是基於內核通知回調的實現機制比較複雜(在訊號函數非同步處理IO與讀取數據操作要保持先後順序,個人認為訊號函數正確設計是不處理業務的,即後續的數據讀取等操作)
  • 而對於非阻塞式IO模型,相比阻塞式IO而言,而是通過不斷輪詢的方式發起recvfrom系統調用,由於存在內核與用戶空間的切換,會損耗性能
  • 對於IO復用模型而言,阻塞於select函數的調用(本質上是基於文件描述符集合的遍歷),向內核註冊對應的事件並等待事件可讀或者超時通知到select函數
  • 而對於AIO模型而言,是一種實現真正的非阻塞非同步IO方式,但是在linux/unix系統支援此IO模型設計並不確定
  • 目前大多數Unix/Linux伺服器都是基於IO復用模型進行優化改進,即對select/poll方法進行增強優化

IO關鍵術語

同步與非同步的定義

  • 同步:發起一個fn的調用,需要等待調用結果返回,該調用結果要麼是期望的結果要麼是異常拋出的結果,可以說是原子性操作(要麼成功要麼失敗返回)
  • 非同步: 發起一個fn調用,無需等待結果就直接返回,只有當被調用者執行處理程式之後通過「喚醒」手段通知調用方獲取結果(喚醒的方式有回調,事件通知等)
  • 小結: 同步和非同步關注的是程式之間的通訊

阻塞與非阻塞的定義

  • 阻塞: 類比執行緒阻塞來說明,在並發多執行緒爭搶資源的競態條件下,如果有一個執行緒已持有鎖,那麼當前執行緒將無法獲取鎖而被掛起,處於等待狀態
  • 非阻塞: 一旦執行緒釋放鎖,其他執行緒將會進入就緒狀態,具備爭搶鎖的資格
  • 小結: 阻塞與非阻塞更關注是程式等待結果的狀態
  • 由此可知,同步非同步與阻塞非阻塞之間不存在關聯,關注的目標是不一樣的

同步IO與非同步IO(基於POSIX規範)

  • 同步IO: 表示應用進程發起真實的IO操作請求(recvfrom)導致進程一直處於等待狀態,這時候進程被阻塞,直到IO操作完成返回成功提示
  • 非同步IO: 表示應用進程發起真實的IO操作請求(recvfrom),這個時候系統內核向用戶進程將直接返回一個錯誤資訊,「相當於告訴進程還沒有處理好,好了會通知你」
  • 阻塞IO: 主要是體現發起IO操作請求通知內核並且內核接收到到請求之後如果讓進程等待,那麼就是阻塞
  • 非阻塞IO: 發起IO操作請求的時候不論結果如何直接告訴進程「不用等待,晚點再來」,那就是非阻塞

IO模型對比

根據上述的同步與非同步IO定義並結合上述的IO模型可知,只有非同步IO模型符合POSIX規範的非同步IO,其他IO模型都存在recvfrom系統調用被內核阻塞,屬於同步IO操作,由此可總結如下:

  • 也就是說,要麼稱為同步IO和非同步IO,要麼稱為上述的IO模型名稱
  • 大部分作業系統都是基於同步IO的方式實現,對於支援非同步IO模型的作業系統還不確定,在實際工作中接觸到的IO模型,從嚴格意義上來說應該稱為Blocking-IO(阻塞IO)和Non-Blocking-IO(非阻塞IO),而不是同步IO和非同步IO
  • 小結: 同步與非同步針對通訊機制,阻塞與非阻塞針對程式調用等待結果的狀態

你好,我是疾風先生,先後從事外企和互聯網大廠的java和python工作, 記錄並分享個人技術棧,歡迎關注我的公眾號,致力於做一個有深度,有廣度,有故事的工程師,歡迎成長的路上有你陪伴,關注後回復greek可添加私人微信,歡迎技術互動和交流,謝謝!

Tags: