Linux IO 概念(2)

 

        在上一篇IO底層的概念中雜合了不少模糊的概念,受知識水平的限制,只是從網上抄了不少過來.從linux一切皆文件的設計哲學,介紹了文件描述符,從進程的運行內存分配,進程的切換,介紹了進程的阻塞,以及引出了阻塞IO.linux

        在講到阻塞IO的時,候受限於知識水平,也沒有實際操做過,仍是沒有理解進程和IO函數的調用關係,IO又是如何操做磁盤,文件描述符又是怎樣工做,進程怎麼去拷貝字節流,數組

        瞭解linuxIO的最終目的我是想知道JavaIO和JavaNIO在虛擬機中是如何調用的,虛擬機做爲一個linux進程又是如何跟底層IO進行交互的.這些問題最終仍是要去圖書館查閱書籍才能理解的更清楚,緩存

        下面繼續在網絡上搬遷別人家的博客網絡

        注:如下下文章整理自網絡多線程

        

    阻塞IO併發

 

 

 

非阻塞IO異步

 

 

        

 

        多路複用IO,socket

        多路複用IO是爲了處理多個IO問價句柄的數據操做,一個典型場景是當有不少socket服務監聽不一樣端口以接收數據時,若是採用阻塞IO則須要多線程,每一個線程和進程負責一個端口socket.可是,大量的線程和進程每每形成CPU的浪費函數

        linuxIO多路複用技術提供一個單進程,單線程內監聽多個IO讀寫時間的機制,其基本原理是各個IO將句柄設置爲非阻塞IO,而後將各個IO句柄註冊到linux提供的IO複用函數上(select,poll或者epoll),若是某個句柄的IO數據就緒,則函數返回,因爲開發者進行該IO數據處理.多路複用函數幫咱們進行了多個非阻塞IO數據是否就緒的輪詢操做,只不過IO多路複用函數的輪詢更有效率,由於函數一次性傳遞文件描述符到內核態,在內核態中進行輪詢(epoll則是進行等待邊緣事件的觸發),沒必要反覆進行用戶態和內核態的切換性能

 

 

        

 

linuxIO的多路複用技術主要的實現方式,select,poll,和epoll,過根據觸發方式不一樣,與是否須要輪詢的的不一樣

    

SELECT

        select是Linux最先支持的多路IO複用函數,其函數原型爲:

int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);

        參數nfds是全部文件描述符的數量+1,而readfds、writefds和errorfds分別爲等待讀、寫和錯誤IO操做的文件描述符的集合,而timeout是超時時間,超過timeout時間select將返回(0表示不阻塞,NULL則是沒有超時時間)。

        select的返回值是有可用的IO操做的文件描述符數量,若是超時返回0,若是發生錯誤返回-1。

        select函數須要和四個宏配合使用:FD_SET()、FD_CLR()、FD_ZERO()和FD_ISSET()。具體使用再也不介紹,能夠參考資料[7,8]的相關內容,下面介紹select函數的內部實現原理和主要流程:

一、使用copy_from_user從用戶空間拷貝fd_set到內核空間;

二、遍歷全部fd,調用其對應的poll函數,再由poll函數調用__pollwait函數;

三、poll函數會判斷當前文件描述符上的IO操做是否就緒,並利用__pollwait的主要工做就是把當前進程掛到設備的等待隊列中,但這並不表明進程會睡眠;

四、poll方法返回時會返回一個描述讀寫操做是否就緒的mask掩碼,根據這個mask掩碼給fd_set賦值;

五、若是遍歷完全部的fd,尚未返回一個可讀寫的mask掩碼,則會調用schedule_timeout使進程進入睡眠。當設備驅動發生自身資源可讀寫後,會喚醒其等待隊列上睡眠的進程,更新fd_set後select返回;

六、若是超過超時時間schedule_timeout,仍是沒人喚醒,則調用select的進程會從新被喚醒得到CPU,進而從新遍歷fd,判斷有沒有就緒的fd,流程如上;

七、把fd_set從內核空間拷貝到用戶空間,select返回。

       

         從上面的select內部流程中能夠看出,select操做既有阻塞等待,也有主動輪詢,相比於純粹的輪詢操做,效率應該稍微高一些。可是其缺點仍然十分明顯:

一、每次調用select,都須要把fd集合從用戶態拷貝到內核態返回時還要從內核態拷貝到用戶態,這個開銷在fd不少時會很大;

二、每次調用select都須要在內核遍歷傳遞進來的全部fd,這個開銷在fd不少時也很大;

三、select返回後,用戶不得不本身再遍歷一遍fd集合,以找到哪些fd的IO操做可用;

四、再次調用select時,fd數組須要從新被初始化;

五、select支持的文件描述符數量過小了,默認是1024。

 

 

POLL

 

 

        poll的函數原型爲int poll(struct pollfd *fds, nfds_t nfds, int timeout)。其實現和select很是類似,只是描述fd集合的方式不一樣,poll經過一個pollfd數組向內核傳遞須要關注的事件,故沒有描述符個數的限制。

        pollfd中的events字段和revents分別用於標示關注的事件和發生的事件,故pollfd數組只須要被初始化一次。

        poll的實現機制與select相似,其對應內核中的sys_poll,只不過poll向內核傳遞pollfd數組,而後對pollfd中的每一個描述符進行poll。

        poll返回後,一樣須要對pollfd中的每一個元素檢查其revents值,來得指事件是否發生。

        因而可知,poll除了沒有文件描述個數限制和文件描述符數組只需初始化一次之外,select的其餘缺點扔存在,而存在的缺點是select和poll性能低的主要緣由。

 

 

EPOLL(等下一個IO可讀取才返回)

 

        Epoll是Linux 2.6版本以後才引入的一種新的多路IO複用技術,epoll解決了select技術的全部主要缺點,能夠取代select方式成爲推薦的多路IO複用技術。

        epoll經過epoll_create建立一個用於epoll輪詢的描述符,經過epoll_ctl添加/修改/刪除事件,經過epoll_wait等待IO就緒或者IO狀態變化的事件發生,epoll_wait的第二個參數用於存放結果。

        epoll與select、poll不一樣,首先,其不用每次調用都向內核拷貝事件描述信息,在第一次調用後,事件信息就會與對應的epoll描述符關聯起來。另外epoll不是經過輪詢,而是經過在等待的描述符上註冊回調函數,當事件發生時,回調函數負責把發生的事件存儲在就緒事件鏈表中,最後寫到用戶空間。

        epoll返回後,該參數指向的緩衝區中即爲發生的事件,對緩衝區中每一個元素進行處理便可,而不須要像poll、select那樣進行輪詢檢查。

        之因此epoll可以避免效率低下的主動輪詢,而徹底採用效率更高的被動等待IO事件通知,是由於epoll在返回時機上支持被成爲「邊沿觸發」(edge=triggered)的新思想,與此相對,select的觸發時機被成爲「水平觸發」(level-triggered)。epoll同時支持這兩種觸發方式。

        邊沿觸發是指當有新的IO事件發生時,epoll才喚醒進程以後返回;而水平觸發是指只要當前IO知足就緒態的要求,epoll或select就會檢查到而後返回,即便在調用以後沒有任何新的IO事件發生。

        舉例來講,一個管道內收到了數據,註冊該管道描述符的epoll返回,可是用戶只讀取了一部分數據,而後再次調用了epoll。這時,若是是水平觸發方式,epoll將馬上返回,由於當前有數據可讀,知足IO就緒的要求;可是若是是邊沿觸發方式,epoll不會返回,由於調用以後尚未新的IO事件發生,直到有新的數據到來,epoll纔會返回,用戶能夠一併讀到老的數據和新的數據。

        經過邊沿觸發方式,epoll能夠註冊回調函數,等待指望的IO事件發生,系統內核會在事件發生時通知,而沒必要像水平觸發那樣去主動輪詢檢查狀態。邊沿觸發和水平觸發方式相似於電子信號中的電位高低變化,由此得名。

 

信號驅動IO

 

 

        信號驅動的IO是一種半異步的IO模型。使用信號驅動I/O時,當網絡套接字可讀後,內核經過發送SIGIO信號通知應用進程,因而應用能夠開始讀取數據。

        具體的說,程序首先容許套接字使用信號驅動I/O模式,而且經過sigaction系統調用註冊一個SIGIO信號處理程序。當有數據到達後,系統嚮應用進程交付一個SIGIO信號,而後應用程序調用read函數從內核中讀取數據到用戶態的數據緩存中。這樣應用進程都不會由於尚無數據達到而被阻塞,應用主循環邏輯能夠繼續執行其餘功能,直到收到通知後去讀取數據或者處理已經在信號處理程序中讀取完畢的數據。

        設置套接字容許信號驅動IO的步驟以下:

1.註冊SIGIO信號處理程序。(安裝信號處理器)

2.使用fcntl的F_SETOWN命令,設置套接字全部者。(設置套接字的全部者)

3.使用fcntl的F_SETFL命令,置O_ASYNC標誌,容許套接字信號驅動I/O。(容許這個套接字進行信號輸入輸出)

        信號驅動的IO內部時序流程以下所示:



 

 

 

之因此說信號驅動的IO是半異步的,是由於實際讀取數據到應用進程緩存的工做仍然是由應用本身負責的,而這部分工做執行期間進程依然是阻塞的,如上圖中的後半部分。而在下面介紹的異步IO則是徹底的異步

 

異步IO

 

        異步I/O模型是一種處理與I/O重疊進行的模型。讀請求會當即返回,說明read 請求已經成功發起了。在後臺完成讀操做時,應用程序而後會執行其餘處理操做。當read 的響應到達時,就會產生一個信號或執行一個基於線程的回調函數來完成此次I/O 處理過程。

        在一個進程中爲了執行多個I/O請求而對計算操做和I/O 處理進行重疊處理的能力利用了處理速度與I/O速度之間的差別。當一個或多個I/O 請求掛起時,CPU能夠執行其餘任務;或者更爲常見的是,在發起其餘I/O的同時對已經完成的I/O 進行操做。

        在傳統的I/O模型中,有一個使用唯一句柄標識的I/O 通道。在 UNIX® 中,這些句柄是文件描述符(這等同於文件、管道、套接字等等)。在阻塞I/O中,咱們發起了一次傳輸操做,當傳輸操做完成或發生錯誤時,系統調用就會返回。

        在異步非阻塞I/O中,咱們能夠同時發起多個傳輸操做。這須要每一個傳輸操做都有唯一的上下文,這樣咱們才能在它們完成時區分究竟是哪一個傳輸操做完成了。在AIO中,這是一個aiocb(AIO I/O Control Block)結構。這個結構包含了有關傳輸的全部信息,包括爲數據準備的用戶緩衝區。在產生I/O(稱爲完成)通知時,aiocb結構就被用來唯一標識所完成的I/O操做。

        以read操做爲例,一個異步IO操做的時序流程以下圖所示:

 

 

        從上圖中能夠看出,比起信號驅動的IO那種半異步模式,異步IO中從內核拷貝數據到用戶緩存空間的工做也是有系統完成的異步過程,用戶程序只須要在指定的數組中引用數據便可。

        數據接收後的處理程序是一個回調函數,Linux提供了兩種機制實現異步IO的回調函數:

        一種是信號回調函數機制,這種機制跟信號驅動的IO相似,利用信號觸發回調函數的執行以處理接收的數據,這回中斷正在執行的代碼,而不會產生新的進程和線程;

        另外一種是線程回調函數機制,在這種機制下也須要編寫相同的回調函數,可是這個函數將註冊到異步IO的事件回調結構體對象中,當數據接收完成後將建立新的線程,在新的線程中調用回調函數進行數據處理。

 

各個IO模型的比較和應用場景

 

        爲了比較各個IO模型的性能,這裏設計了三種最主要的應用場景,分別是單個用戶鏈接的頻繁IO操做、少許用戶鏈接的併發頻繁IO操做、大量用戶鏈接的併發頻繁IO操做。在進行性能比較時,主要考慮的是總的IO等待、系統調用狀況和CPU調度切換,IO等待越少、系統調用越少、CPU調度切換越少意味着IO操做的高效率。

 

 

        在單個用戶鏈接頻繁的IO操做中,能夠採用單線程單進程的方式,這樣能夠不用考慮進程內部的CPU調度,只需關注IO等待和系統調用的頻率。從上面各個IO模型的流程時序圖來看,AIO的用戶程序在執行Io操做時沒有任何Io等待,並且只須要調用IO操做時一次系統調用,因爲是異步操做,信號操做的回傳不須要進行系統調用,連由內核返回用戶態的系統調用都省了,所以效率最高。

        在信號驅動的IO模型中,IO等待時間要比基本的阻塞式IO和多路複用IO要少,只須要等待數據從內核到用戶緩存的操做。可是信號驅動的IO模型和多路複用IO的系統調用次數同樣,須要兩次系統調用,共四次上下文切換,而基本的阻塞模式只須要一次系統調用。在IO頻繁的場景下,仍是基本阻塞IO效率最高,其次爲信號驅動IO,而後是多路複用IO。

 

        基本非阻塞IO的性能最差,由於在IO等待期間不只不交出CPU控制權,還一遍又一遍進行昂貴的系統調用操做進行主動輪詢,而主動輪詢對於IO操做和業務操做都沒有實際的意義,所以CPU計算資源浪費最嚴重。

        

        在單個用戶鏈接的頻繁IO操做中,性能排名有好到差爲:AIO>基本阻塞IO>信號IO>epoll>poll>select>基本非阻塞IO。

 

        在少許用戶下的頻繁IO操做中,基本阻塞IO通常要使用多線程操做,所以要產生額外的線程調度工做。雖然因爲線程較少,遠少於系統的總進程數,可是因爲IO操做頻繁,CPU切換仍是會集中在IO操做的各個線程內。

        對於基本阻塞IO和多路複用IO來說,雖然多路IO複用一次系統調用能夠完成更多的IO操做,可是在IO操做完成後對於每一個IO操做仍是要系統調用將內核中的數據取回到用戶緩存中,所以系統調用次數仍然比阻塞IO略多,但線程切換的開銷更大。特別對於select來講,因爲select內部採用半輪詢方式,效率不如阻塞方式,所以在這種少許用戶鏈接的IO場景下,還不能只經過理論判斷基本阻塞IO和select方式孰優孰劣。

        其餘的IO模型相似於單用戶下,再也不分析,由此得出在少許用戶鏈接IO操做下的IO模型性能,由好到壞依次爲AIO>信號IO>epoll>基本阻塞IO?poll>select>基本非阻塞IO。

 

        在大量,甚至海量用戶的併發頻繁IO操做下,多路IO複用技術的性能會全面超越簡單的多線程阻塞IO,由於這時大量的CPU切換操做將顯著減小CPU效率,而多路複用一次完成大量的IO操做的優點更加明顯。對於AIO和信號IO,在這種場景下依然有着更少的IO等待和更少的系統調用操做,性能依然最好。

        因而可知,在大量用戶的併發頻繁IO操做下,IO性能由好到差依次爲AIO>信號IO>epoll>poll>select>基本阻塞IO>基本非阻塞IO。

    

https://mp.weixin.qq.com/s?__biz=MzI4NTEzMjc5Mw==&mid=2650554708&idx=1&sn=4fa4e599c5028825fda5ead907ec86a6&chksm=f3f833c2c48fbad49fda347833f14f553f764fc0e46ae71073d0b31028f7ec4f85b60d448e9a#rd

相關文章
相關標籤/搜索