io模型及java中對應使用

一.linux基礎知識講解

1.32位操作系統爲什麼只能使用4G的內存

cpu的位是指一次性可處理的數據量是多少,1字節=8位,32位處理器可以一次性處理4個字節的數據量。如果用32位標識內存中的一個地址,那麼就只能有2的32次方=4G地址了,所以32位的cpu只能使用4G的內存了。

2.內核空間和用戶空間

爲了保證用戶進程不能直接操作內核,保證內核的安全,操心繫統將4G內存劃分爲兩部分,一部分爲內核空間,一部分爲用戶空間。針對linux操作系統而言,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱爲內核空間,而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱爲用戶空間。每個進程可以通過系統調用進入內核,因此,Linux內核由系統內的所有進程共享。於是,從具體進程的角度來看,每個進程可以擁有4G字節的虛擬空間。

有了用戶空間和內核空間,整個linux內部結構可以分爲三部分,從最底層到最上層依次是:硬件–>內核空間–>用戶空間。

3.數據操作流程

我們都知道,爲了OS的安全性等的考慮,進程是無法直接操作I/O設備的,其必須通過系統調用請求內核來協助完成I/O動作,而內核會爲每個I/O設備維護一個buffer。
整個請求過程爲: 用戶進程發起請求,內核接受到請求後,從I/O設備中獲取數據到buffer中,再將buffer中的數據copy到用戶進程的地址空間,該用戶進程獲取到數據後再響應客戶端。

二.I/O模型

數據流入的兩階段:

  1. 等待數據準備階段(數據從IO設備進入內核buffer)
  2. 數據從內核buffer被複制到用戶空間

根據這兩個階段的阻塞情況,吧I/O劃分爲五種模型
在這裏插入圖片描述

1.阻塞I/O

在這裏插入圖片描述
當用戶進程調用了recvfrom這個系統調用,內核就開始了IO的第一個階段:等待數據準備。對於network io來說,很多時候數據在一開始還沒有到達(比如,還沒有收到一個完整的TCP包),這個時候內核就要等待足夠的數據到來。而在用戶進程這邊,整個進程會被阻塞。當內核一直等到數據準備好了,它就會將數據從內核中拷貝到用戶內存,然後內核返回結果,用戶進程才解除block的狀態,重新運行起來。
所以,blocking IO的特點就是在IO執行的兩個階段都被block了。

對應java實現:
ServerSocket.accept(), socket.read()

2.非阻塞I/O

在這裏插入圖片描述
當用戶進程調用recvfrom時,系統不會阻塞用戶進程,而是立刻返回一個ewouldblock錯誤,從用戶進程角度講 ,並不需要等待,而是馬上就得到了一個結果。用戶進程判斷標誌是ewouldblock時,就知道數據還沒準備好,於是它就可以去做其他的事了,於是它可以再次發送recvfrom,一旦內核中的數據準備好了。並且又再次收到了用戶進程的system call,那麼它馬上就將數據拷貝到了用戶內存,然後返回。

只有第二階段阻塞;

當一個應用程序在一個循環裏對一個非阻塞調用recvfrom,我們稱爲輪詢。應用程序不斷輪詢內核,看看是否已經準備好了某些操作,這通常是浪費CPU時間。

對應java實現:
java nio中不使用selecter的情況
需要ServerSocketChannel.configureBlocking(false)配置爲非阻塞
serverSocketChannel.accept(),SocketChannel.read()

3.I/O多路複用

就是我們常說的select,poll,epoll
在這裏插入圖片描述
當用戶進程調用了select,那麼整個進程會被block,而同時,內核會“監視”所有select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操作,將數據從內核拷貝到用戶進程。

select

基本原理:select 函數監視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調用後select函數會阻塞,直到有描述符就緒(有數據 可讀、可寫、或者有except),或者超時(timeout指定等待時間,如果立即返回設爲null即可),函數返回。當select函數返回後,可以通過遍歷fdset,來找到就緒的描述符。

缺點:
1、select最大的缺陷就是單個進程所打開的FD是有一定限制的,它由FDSETSIZE設置,32位機默認是1024個,64位機默認是2048。
一般來說這個數目和系統內存關係很大,”具體數目可以cat /proc/sys/fs/file-max察看”。32位機默認是1024個。64位機默認是2048.
2、對socket進行掃描時是線性掃描,即採用輪詢的方法,效率較低。
當套接字比較多的時候,每次select()都要通過遍歷FDSETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。”如果能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢”,這正是epoll與kqueue做的。
3、需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時複製開銷大

poll

基本原理:poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然後查詢每個fd對應的設備狀態,如果設備就緒則在設備等待隊列中加入一項並繼續遍歷,如果遍歷完所有fd後沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒後它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷。

它沒有最大連接數的限制,原因是它是基於鏈表來存儲的,但是同樣有缺點

  1. 大量的fd的數組被整體複製於用戶態和內核地址空間之間,而不管這樣的複製是不是有意義。
  2. poll還有一個特點是“水平觸發”,如果報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。

注意:從上面看,select和poll都需要在返回後,通過遍歷文件描述符來獲取已經就緒的socket。事實上,同時連接的大量客戶端在一時刻可能只有很少的處於就緒狀態,因此隨着監視的描述符數量的增長,其效率也會線性下降。

epoll

基本原理:epoll支持水平觸發和邊緣觸發,最大的特點在於邊緣觸發,它只告訴進程哪些fd剛剛變爲就緒態,並且只會通知一次。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epollctl註冊fd,一旦該fd就緒,內核就會採用類似callback的回調機制來激活該fd,epollwait便可以收到通知。

epoll的優點:

  1. 沒有最大併發連接的限制,能打開的FD的上限遠大於1024(1G的內存上能監聽約10萬個端口)。
  2. 效率提升,不是輪詢的方式,不會隨着FD數目的增加效率下降。
    只有活躍可用的FD纔會調用callback函數;即Epoll最大的優點就在於它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高於select和poll。
  3. 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少複製開銷。

對應java實現:
java nio中使用selecter的情況

4.信號驅動I/O

在這裏插入圖片描述

5.異步IO

在這裏插入圖片描述
當用戶進程向內核發起某個操作後,會立刻得到返回,並把所有的任務都交給內核去完成(包括將數據從內核拷貝到用戶自己的緩衝區),內核完成之後,只需返回一個信號告訴用戶進程已經完成就可以了。

對應java實現:
java.nio.channels.AsynchronousChannel
標記一個channel支持異步IO操作。

java.nio.channels.AsynchronousServerSocketChannel
ServerSocket的aio版本,創建TCP服務端,綁定地址,監聽端口等。

java.nio.channels.AsynchronousSocketChannel
面向流的異步socket channel,表示一個連接。

三.I/O模型對比

在這裏插入圖片描述
結果表明:前四個模型之間的主要區別是第一階段,四個模型的第二階段是一樣的:過程受阻在調用recvfrom當數據從內核拷貝到用戶緩衝區。然而,異步I/O處理兩個階段,與前四個不同。

四.參考網站

https://mp.weixin.qq.com/s?__biz=MzU0MzQ5MDA0Mw==&mid=2247483907&idx=1&sn=3d5e1384a36bd59f5fd14135067af1c2&chksm=fb0be897cc7c61815a6a1c3181f3ba3507b199fd7a8c9025e9d8f67b5e9783bc0f0fe1c73903&scene=21#wechat_redirect

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