Linux 阻塞IO和非阻塞IO

 

1 什麼是I/O

程序是由數據+指令構成的,運行程序的過程可以分成下面這幾步:

1.將代碼加載到內存中,逐條運行內存中的代碼
2.在運行代碼的過程中,可能需要對文件的讀寫,即將文件輸入(Input)到內存和將代碼執行結果產生的文件輸出(Output)到外設(網絡、磁盤)的過程。那麼這個數據交換的過程就是I/O

IO中的阻塞、非阻塞、同步、異步概念分析
通過上面這篇文章你可以知道同步、異步、阻塞、非阻塞這些概念,並且可以瞭解到java中I/O編程的三種模型,阻塞IO(BIO)、非阻塞IO(NIO)和異步IO(AIO)。Java中NIO和AIO都是通過epoll來實現的。

我們常說的IO,指的是文件的輸入和輸出,但是在操作系統層面是如何定義IO的呢?到底什麼樣的過程可以叫做是一次IO呢?

拿一次磁盤文件讀取爲例,我們要讀取的文件是存儲在磁盤上的,我們的目的是把它讀取到內存中。可以把這個步驟簡化成把數據從硬件(硬盤)中讀取到用戶空間中。

其實真正的文件讀取還涉及到緩存等細節,這裏就不展開講述了。關於用戶空間、內核空間以及硬件等的關係如果讀者不理解的話,可以通過釣魚的例子理解。

釣魚的時候,剛開始魚是在魚塘裏面的,我們的釣魚動作的最終結束標誌是魚從魚塘中被我們釣上來,放入魚簍中。

這裏面的魚塘就可以映射成磁盤,中間過渡的魚鉤可以映射成內核空間,最終放魚的魚簍可以映射成用戶空間。一次完整的釣魚(IO)操作,是魚(文件)從魚塘(硬盤)中轉移(拷貝)到魚簍(用戶空間)的過程

在Linux(UNIX)操作系統中,共有五種IO模型,分別是:

阻塞IO模型、

非阻塞IO模型、

IO複用模型、

信號驅動IO模型

以及異步IO模型。

2 同步IO模型

2.1 阻塞IO模型

我們釣魚的時候,有一種方式比較愜意,比較輕鬆,那就是我們坐在魚竿面前,這個過程中我們什麼也不做,雙手一直把着魚竿,就靜靜的等着魚兒咬鉤。一旦手上感受到魚的力道,就把魚釣起來放入魚簍中。然後再釣下一條魚。

映射到Linux操作系統中,這就是一種最簡單的IO模型,即阻塞IO。 阻塞 I/O 是最簡單的 I/O 模型,一般表現爲進程或線程等待某個條件,如果條件不滿足,則一直等下去。條件滿足,則進行下一步操作。

(簡單來說:阻塞:當資源不夠用的時候,應用程序就會掛起,當程序可用的時候,喚醒應用程序,打開文件使用open,默認是阻塞式打開。阻塞訪問的最大好處就是文件不可用的時候程序進行了休眠,這樣就可以把CPU的資源是方釋放出來。但是當設備文件可以操作的時候就必須喚醒進程,一般在中斷函數中完成喚醒工作。

linux 中提供了wait queue(等待隊列)來實現阻塞進程的喚醒工作。如果要使用對列,就用在創建出初始化地時候創建等待隊列頭。

                     非阻塞:當資源不可用的時候,應用程序輪詢查看,或放棄,會有超時處理機制)

                             

 

       


等待隊列:1、wait_queue_head_t

                         2.  等待隊列項 :

                               wait_queue_t:表示隊列項

                         3.添加隊列項到等待隊列頭

                         4.移除等待隊列項

                            資源可用的時候使用 remove_wait_queue函數移除

                        5.喚醒:wakeup



這裏寫圖片描述

應用進程通過系統調用 recvfrom 接收數據,但由於內核還未準備好數據報,應用進程就會阻塞住,直到內核準備好數據報,recvfrom 完成數據報復制工作,應用進程才能結束阻塞狀態。

這種釣魚方式相對來說比較簡單,對於釣魚的人來說,不需要什麼特製的魚竿,拿一根夠長的木棍就可以悠閒的開始釣魚了(實現簡單)。缺點就是比較耗費時間,比較適合那種對魚的需求量小的情況(併發低,時效性要求低)。

2.2 非阻塞IO模型

我們釣魚的時候,在等待魚兒咬鉤的過程中,我們可以做點別的事情,比如玩一把王者榮耀、看一集《延禧攻略》等等。但是,我們要時不時的去看一下魚竿,一旦發現有魚兒上鉤了,就把魚釣上來。

映射到Linux操作系統中,這就是非阻塞的IO模型。應用進程與內核交互,目的未達到之前,不再一味的等着,而是直接返回。然後通過輪詢的方式,不停的去問內核數據準備有沒有準備好。如果某一次輪詢發現數據已經準備好了,那就把數據拷貝到用戶空間中。
這裏寫圖片描述
應用進程通過 recvfrom 調用不停的去和內核交互,直到內核準備好數據。如果沒有準備好,內核會返回error,應用進程在得到error後,過一段時間再發送recvfrom請求。在兩次發送請求的時間段,進程可以先做別的事情。

這種方式釣魚,和阻塞IO比,所使用的工具沒有什麼變化,但是釣魚的時候可以做些其他事情,增加時間的利用率。

2.3 信號驅動IO模型

我們釣魚的時候,爲了避免自己一遍一遍的去查看魚竿,我們可以給魚竿安裝一個報警器。當有魚兒咬鉤的時候立刻報警。然後我們再收到報警後,去把魚釣起來。

映射到Linux操作系統中,這就是信號驅動IO。應用進程在讀取文件時通知內核,如果某個 socket 的某個事件發生時,請向我發一個信號。在收到信號後,信號對應的處理函數會進行後續處理。
這裏寫圖片描述
應用進程預先向內核註冊一個信號處理函數,然後用戶進程返回,並且不阻塞,當內核數據準備就緒時會發送一個信號給進程,用戶進程便在信號處理函數中開始把數據拷貝的用戶空間中。

這種方式釣魚,和前幾種相比,所使用的工具有了一些變化,需要有一些定製(實現複雜)。但是釣魚的人就可以在魚兒咬鉤之前徹底做別的事兒去了。等着報警器響就行了。

2.4 IO複用模型

生產中經常會用到的一種模型
我們釣魚的時候,爲了保證可以最短的時間釣到最多的魚,我們同一時間擺放多個魚竿,同時釣魚。然後哪個魚竿有魚兒咬鉤了,我們就把哪個魚竿上面的魚釣起來。

映射到Linux操作系統中,這就是IO複用模型。多個進程的IO可以註冊到同一個管道上,這個管道會統一和內核進行交互。當管道中的某一個請求需要的數據準備好之後,進程再把對應的數據拷貝到用戶空間中。

這裏寫圖片描述

IO多路轉接是多了一個select函數,多個進程的IO可以註冊到同一個select上,當用戶進程調用該select,select會監聽所有註冊好的IO,如果所有被監聽的IO需要的數據都沒有準備好時,select調用進程會阻塞。當任意一個IO所需的數據準備好之後,select調用就會返回,然後進程在通過recvfrom來進行數據拷貝。

這裏的IO複用模型,並沒有向內核註冊信號處理函數,所以,他並不是非阻塞的。進程在發出select後,要等到select監聽的所有IO操作中至少有一個需要的數據準備好,纔會有返回,並且也需要再次發送請求去進行文件的拷貝。

這種方式的釣魚,通過增加魚竿的方式,可以有效的提升效率。

2.5 小結

我們說阻塞IO模型、非阻塞IO模型、IO複用模型和信號驅動IO模型都是同步的IO模型。原因是因爲,無論以上那種模型,真正的數據拷貝過程,都是同步進行的。

信號驅動難道不是異步的麼? 信號驅動,內核是在數據準備好之後通知進程,然後進程再通過recvfrom操作進行數據拷貝。我們可以認爲數據準備階段是異步的,但是,數據拷貝操作是同步的。所以,整個IO過程也不能認爲是異步的。

我們把釣魚過程,可以拆分爲兩個步驟:1、魚咬鉤(數據準備)。2、把魚釣起來放進魚簍裏(數據拷貝)。無論以上提到的哪種釣魚方式,在第二步,都是需要人主動去做的,並不是魚竿自己完成的。所以,這個釣魚過程其實還是同步進行的。

3 異步IO模型

我們釣魚的時候,採用一種高科技釣魚竿,即全自動釣魚竿。可以自動感應魚上鉤,自動收竿,更厲害的可以自動把魚放進魚簍裏。然後,通知我們魚已經釣到了,他就繼續去釣下一條魚去了。

映射到Linux操作系統中,這就是異步IO模型。應用進程把IO請求傳給內核後,完全由內核去操作文件拷貝。內核完成相關操作後,會發信號告訴應用進程本次IO已經完成。
這裏寫圖片描述

用戶進程發起aio_read操作之後,給內核傳遞描述符、緩衝區指針、緩衝區大小等,告訴內核當整個操作完成時,如何通知進程,然後就立刻去做其他事情了。當內核收到aio_read後,會立刻返回,然後內核開始等待數據準備,數據準備好以後,直接把數據拷貝到用戶控件,然後再通知進程本次IO已經完成。

這種方式的釣魚,無疑是最省事兒的。啥都不需要管,只需要交給魚竿就可以了。

4 五種IO模型對比

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章