Linux IO模型

1、概念說明

用戶空間和內(nèi)核空間
進程切換
進程的阻塞
文件描述符
緩存 IO

1.1、用戶空間與內(nèi)核空間

現(xiàn)在操作系統(tǒng)都是采用虛擬存儲器,那么對32位操作系統(tǒng)而言,它的尋址空間(虛擬存儲空間)為4G(2的32次方)。操作系統(tǒng)的核心是內(nèi)核,獨立于普通的應用程序,可以訪問受保護的內(nèi)存空間,也有訪問底層硬件設(shè)備的所有權(quán)限。為了保證用戶進程不能直接操作內(nèi)核(kernel),保證內(nèi)核的安全,操作系統(tǒng)將虛擬空間劃分為兩部分,一部分為內(nèi)核空間,一部分為用戶空間。針對linux操作系統(tǒng)而言,將最高的1G字節(jié)(從虛擬地址0xC0000000到0xFFFFFFFF),供內(nèi)核使用,稱為內(nèi)核空間,而將較低的3G字節(jié)(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱為用戶空間。

1.2、進程切換

為了控制進程的執(zhí)行,內(nèi)核必須有能力掛起正在CPU上運行的進程,并恢復以前掛起的某個進程的執(zhí)行。這種行為被稱為進程切換。因此可以說,任何進程都是在操作系統(tǒng)內(nèi)核的支持下運行的,是與內(nèi)核緊密相關(guān)的。

從一個進程的運行轉(zhuǎn)到另一個進程上運行,這個過程中經(jīng)過下面這些變化:

1、保存處理機上下文,包括程序計數(shù)器和其他寄存器。
2、更新PCB信息。
3、把進程的PCB移入相應的隊列,如就緒、在某事件阻塞等隊列。
4、選擇另一個進程執(zhí)行,并更新其PCB。
5、更新內(nèi)存管理的數(shù)據(jù)結(jié)構(gòu)。
6、恢復處理機上下文。

1.3、進程的阻塞

正在執(zhí)行的進程,由于期待的某些事件未發(fā)生,如請求系統(tǒng)資源失敗、等待某種操作的完成、新數(shù)據(jù)尚未到達或無新工作做等,則由系統(tǒng)自動執(zhí)行阻塞原語(Block),使自己由運行狀態(tài)變?yōu)樽枞麪顟B(tài)。可見,進程的阻塞是進程自身的一種主動行為,也因此只有處于運行態(tài)的進程(獲得CPU),才可能將其轉(zhuǎn)為阻塞狀態(tài)。當進程進入阻塞狀態(tài),是不占用CPU資源的。

1.4、文件描述符fd

文件描述符(File descriptor)是計算機科學中的一個術(shù)語,是一個用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一個非負整數(shù)。實際上,它是一個索引值,指向內(nèi)核為每一個進程所維護的該進程打開文件的記錄表。當程序打開一個現(xiàn)有文件或者創(chuàng)建一個新文件時,內(nèi)核向進程返回一個文件描述符。在程序設(shè)計中,一些涉及底層的程序編寫往往會圍繞著文件描述符展開。但是文件描述符這一概念往往只適用于UNIX、Linux這樣的操作系統(tǒng)。

1.5、緩存 IO

緩存 IO 又被稱作標準 IO,大多數(shù)文件系統(tǒng)的默認 IO 操作都是緩存 IO。在 Linux 的緩存 IO 機制中,操作系統(tǒng)會將 IO 的數(shù)據(jù)緩存在文件系統(tǒng)的頁緩存( page cache )中,也就是說,數(shù)據(jù)會先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應用程序的地址空間。

緩存 IO 的缺點:
數(shù)據(jù)在傳輸過程中需要在應用程序地址空間和內(nèi)核進行多次數(shù)據(jù)拷貝操作,這些數(shù)據(jù)拷貝操作所帶來的 CPU 以及內(nèi)存開銷是非常大的。

2、Linux IO模型

網(wǎng)絡(luò)IO的本質(zhì)是socket的讀取,socket在linux系統(tǒng)被抽象為流,IO可以理解為對流的操作。對于一次IO訪問(以read舉例),數(shù)據(jù)會先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應用程序的地址空間。所以說,當一個read操作發(fā)生時,它會經(jīng)歷兩個階段:

  • 第一階段:等待數(shù)據(jù)準備 (Waiting for the data to be ready)。
  • 第二階段:將數(shù)據(jù)從內(nèi)核拷貝到進程中 (Copying the data from the kernel to the process)。

對于socket流而言:

  • 第一步:通常涉及等待網(wǎng)絡(luò)上的數(shù)據(jù)分組到達,然后被復制到內(nèi)核的某個緩沖區(qū)。
  • 第二步:把數(shù)據(jù)從內(nèi)核緩沖區(qū)復制到應用進程緩沖區(qū)。

網(wǎng)絡(luò)應用需要處理的無非就是兩大類問題,網(wǎng)絡(luò)IO,數(shù)據(jù)計算。相對于后者,網(wǎng)絡(luò)IO的延遲,給應用帶來的性能瓶頸大于后者。網(wǎng)絡(luò)IO的模型大致有如下幾種:

* 同步模型(synchronous IO)
  * 阻塞IO(bloking IO)
  * 非阻塞IO(non-blocking IO)
  * 多路復用IO(multiplexing IO)
  * 信號驅(qū)動式IO(signal-driven IO)
* 異步IO(asynchronous IO)

劃重點 :
同步IO和異步IO的區(qū)別就在于:數(shù)據(jù)拷貝的時候進程是否阻塞 阻塞IO和非阻塞IO的區(qū)別就在于:應用程序的調(diào)用是否立即返回!

2.1、同步阻塞 IO(blocking IO)

2.1.1、場景

因為最近我和女友都比較忙,所以晚飯就沒時間自己做,由我下班后買好,帶回家和女友一塊兒吃。女友喜歡吃炒菜,我喜歡吃面條,而且女友喜歡的餐館A只賣炒菜,我喜歡的餐館B只賣面條,經(jīng)過最近幾天在AB兩家餐廳的買飯經(jīng)歷,對于如何“高效率”買飯,我有一些心得。
第一天,我先去A餐館點了炒菜,然后就百無聊賴的在那兒等,十幾分鐘后,女友要的炒菜出鍋,然后我又去了B餐館點了自己喜歡吃的面,也是經(jīng)過漫長的百無聊賴的等待,終于我的面也出鍋了,多么漫長且百無聊賴的等待,這就是典型的阻塞。

2.1.2 網(wǎng)絡(luò)模型

當用戶進程調(diào)用了recv / recvfrom 這個系統(tǒng)調(diào)用,kernel就開始了IO的第一個階段:準備數(shù)據(jù)(對于網(wǎng)絡(luò)IO來說,很多時候數(shù)據(jù)在一開始還沒有到達。比如,還沒有收到一個完整的UDP包。這個時候kernel就要等待足夠的數(shù)據(jù)到來)。這個過程需要等待,也就是說數(shù)據(jù)被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中是需要一個過程的。而在用戶進程這邊,整個進程會被阻塞(當然,是進程自己選擇的阻塞)。第二個階段:當kernel一直等到數(shù)據(jù)準備好了,它就會將數(shù)據(jù)從kernel中拷貝到用戶內(nèi)存,然后kernel返回結(jié)果,用戶進程才解除block的狀態(tài),重新運行起來。
所以,blocking IO的特點就是在IO執(zhí)行的兩個階段都被block了。

優(yōu)點:能夠及時返回數(shù)據(jù),無等待
缺點:等待時間長

2.2、同步非阻塞 IO(nonblocking IO)

2.2.1、場景描述

第二天,我先去A餐館點了炒菜,但是我?guī)Я吮緯?,然后在那兒邊看書邊等,并且時不時的問服務(wù)員炒菜好了沒有,十幾分鐘后,女友要的炒菜出鍋,然后我又去了B餐館點了自己喜歡吃的面,也是邊看書邊等,并且時不時的問服務(wù)員我點的面有沒有做好,經(jīng)過很長時間,終于我的面也出鍋了,等待時間雖長,但是我還是從書本中獲益匪淺。

2.2.2 網(wǎng)絡(luò)模型

同步非阻塞就是 “每隔一會兒瞄一眼進度條” 的輪詢(polling)方式。在這種模型中,設(shè)備是以非阻塞的形式打開的。這意味著 IO 操作不會立即完成,read 操作可能會返回一個錯誤代碼,說明這個命令不能立即滿足(EAGAIN 或 EWOULDBLOCK)。

在網(wǎng)絡(luò)IO時候,非阻塞IO也會進行recvform系統(tǒng)調(diào)用,檢查數(shù)據(jù)是否準備好,與阻塞IO不一樣,"非阻塞將大的整片時間的阻塞分成N多的小的阻塞, 所以進程不斷地有機會 '被' CPU光顧"。

也就是說非阻塞的recvform系統(tǒng)調(diào)用調(diào)用之后,進程并沒有被阻塞,內(nèi)核馬上返回給進程,如果數(shù)據(jù)還沒準備好,此時會返回一個error。進程在返回之后,可以干點別的事情,然后再發(fā)起recvform系統(tǒng)調(diào)用。重復上面的過程,循環(huán)往復的進行recvform系統(tǒng)調(diào)用。這個過程通常被稱之為輪詢。輪詢檢查內(nèi)核數(shù)據(jù),直到數(shù)據(jù)準備好,再拷貝數(shù)據(jù)到進程,進行數(shù)據(jù)處理。需要注意,拷貝數(shù)據(jù)整個過程,進程仍然是屬于阻塞的狀態(tài)。

相比同步阻塞方式:

優(yōu)點:能夠在等待任務(wù)完成的時間里干其他活了(包括提交其他任務(wù),也就>是 “后臺” 可以有多個任務(wù)在同時執(zhí)行)。

缺點:任務(wù)完成的響應延遲增大了,因為每過一段時間才去輪詢一次read操>作,而任務(wù)可能在兩次輪詢之間的任意時間完成。這會導致整體數(shù)據(jù)吞吐量>的降低。

2.3、IO 多路復用( IO multiplexing)

2.3.1、場景描述

第三天,我先去A餐館點了炒菜,然后離開,又去了B餐館點了面條,然后我又去了位于兩家餐館中間的書店看書,在看書過程中,我時不時的打電話到兩家餐館確認我的餐做好沒有,很幸運,沒打幾次電話,在A餐館點的炒菜出鍋了,我隨即去取了炒菜,再次回到書店看了會兒書,打電話確認B餐館點的面條也出鍋了,我又去了B餐館取了面條。這次等待的時間不長,中間看書的環(huán)境也好,但是老是要打電話給餐廳確認還是挺不舒服的。

2.3.2 網(wǎng)絡(luò)模型

由于同步非阻塞方式需要不斷主動輪詢,輪詢占據(jù)了很大一部分過程,輪詢會消耗大量的CPU時間,而 “后臺” 可能有多個任務(wù)在同時進行,人們就想到了循環(huán)查詢多個任務(wù)的完成狀態(tài),只要有任何一個任務(wù)完成,就去處理它。如果輪詢不是進程的用戶態(tài),而是有人幫忙就好了。那么這就是所謂的 “IO 多路復用”。UNIX/Linux 下的 select、poll、epoll 就是干這個的(epoll 比 poll、select 效率高,做的事情是一樣的)。

IO多路復用有兩個特別的系統(tǒng)調(diào)用select、poll、epoll函數(shù)。select調(diào)用是內(nèi)核級別的,select輪詢相對非阻塞的輪詢的區(qū)別在于---前者可以等待多個socket,能實現(xiàn)同時對多個IO端口進行監(jiān)聽,當其中任何一個socket的數(shù)據(jù)準好了,就能返回進行可讀,然后進程再進行recvform系統(tǒng)調(diào)用,將數(shù)據(jù)由內(nèi)核拷貝到用戶進程,當然這個過程是阻塞的。select或poll調(diào)用之后,會阻塞進程,與blocking IO阻塞不同在于,此時的select不是等到socket數(shù)據(jù)全部到達再處理, 而是有了一部分數(shù)據(jù)就會調(diào)用用戶進程來處理。如何知道有一部分數(shù)據(jù)到達了呢?監(jiān)視的事情交給了內(nèi)核,內(nèi)核負責數(shù)據(jù)到達的處理。也可以理解為"非阻塞"吧。

I/O復用模型會用到select、poll、epoll函數(shù),這幾個函數(shù)也會使進程阻塞,但是和阻塞I/O所不同的的,這兩個函數(shù)可以同時阻塞多個I/O操作。而且可以同時對多個讀操作,多個寫操作的I/O函數(shù)進行檢測,直到有數(shù)據(jù)可讀或可寫時(注意不是全部數(shù)據(jù)可讀或可寫),才真正調(diào)用I/O操作函數(shù)。

對于多路復用,也就是輪詢多個socket。多路復用既然可以處理多個IO,也就帶來了新的問題,多個IO之間的順序變得不確定了,當然也可以針對不同的編號。

I/O多路復用的主要應用場景如下:

  • 服務(wù)器需要同時處理多個處于監(jiān)聽狀態(tài)或者多個連接狀態(tài)的套接字。
  • 服務(wù)器需要同時處理多種網(wǎng)絡(luò)協(xié)議的套接字。

2.4、異步非阻塞 IO(asynchronous IO)

2.4.1 場景描述

我在公司分別向AB兩家餐廳訂了餐,并且讓他們做好以后給我送到公司,然后我再將飯拎回家,在等待的過程中,我順手修補了我們系統(tǒng)中一個已知的bug,完美。

2.4.2 網(wǎng)絡(luò)模型

相對于同步IO,異步IO不是順序執(zhí)行。用戶進程進行aio_read系統(tǒng)調(diào)用之后,無論內(nèi)核數(shù)據(jù)是否準備好,都會直接返回給用戶進程,然后用戶態(tài)進程可以去做別的事情。等到socket數(shù)據(jù)準備好了,內(nèi)核直接復制數(shù)據(jù)給進程,然后從內(nèi)核向進程發(fā)送通知。IO兩個階段,進程都是非阻塞的。

在 Linux 中,通知的方式是 “信號”:

如果這個進程正在用戶態(tài)忙著做別的事(例如在計算兩個矩陣的乘積),那就強行打斷之,調(diào)用事先注冊的信號處理函數(shù),這個函數(shù)可以決定何時以及如何處理這個異步任務(wù)。由于信號處理函數(shù)是突然闖進來的,因此跟中斷處理程序一樣,有很多事情是不能做的,因此保險起見,一般是把事件 “登記” 一下放進隊列,然后返回該進程原來在做的事。

如果這個進程正在內(nèi)核態(tài)忙著做別的事,例如以同步阻塞方式讀寫磁盤,那就只好把這個通知掛起來了,等到內(nèi)核態(tài)的事情忙完了,快要回到用戶態(tài)的時候,再觸發(fā)信號通知。

如果這個進程現(xiàn)在被掛起了,例如無事可做 sleep 了,那就把這個進程喚醒,下次有 CPU 空閑的時候,就會調(diào)度到這個進程,觸發(fā)信號通知。

image.png

select、poll、epoll簡介

epoll跟select都能提供多路I/O復用的解決方案。在現(xiàn)在的Linux內(nèi)核里有都能夠支持,其中epoll是Linux所特有,而select則應該是POSIX所規(guī)定,一般操作系統(tǒng)均有實現(xiàn)

select:

select本質(zhì)上是通過設(shè)置或者檢查存放fd標志位的數(shù)據(jù)結(jié)構(gòu)來進行下一步處理。這樣所帶來的缺點是:

1、 單個進程可監(jiān)視的fd數(shù)量被限制,即能監(jiān)聽端口的大小有限。一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大,具體數(shù)目可以cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.

2、 對socket進行掃描時是線性掃描,即采用輪詢的方法,效率較低:當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調(diào)度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字注冊某個回調(diào)函數(shù),當他們活躍時,自動完成相關(guān)操作,那就避免了輪詢,這正是epoll與kqueue做的。

3、需要維護一個用來存放大量fd的數(shù)據(jù)結(jié)構(gòu),這樣會使得用戶空間和內(nèi)核空間在傳遞該結(jié)構(gòu)時復制開銷大

poll:

poll本質(zhì)上和select沒有區(qū)別,它將用戶傳入的數(shù)組拷貝到內(nèi)核空間,然后查詢每個fd對應的設(shè)備狀態(tài),如果設(shè)備就緒則在設(shè)備等待隊列中加入一項并繼續(xù)遍歷,如果遍歷完所有fd后沒有發(fā)現(xiàn)就緒設(shè)備,則掛起當前進程,直到設(shè)備就緒或者主動超時,被喚醒后它又要再次遍歷fd。這個過程經(jīng)歷了多次無謂的遍歷。

它沒有最大連接數(shù)的限制,原因是它是基于鏈表來存儲的,但是同樣有一個缺點:

1、大量的fd的數(shù)組被整體復制于用戶態(tài)和內(nèi)核地址空間之間,而不管這樣的復制是不是有意義。 2、poll還有一個特點是“水平觸發(fā)”,如果報告了fd后,沒有被處理,那么下次poll時會再次報告該fd。

epoll:

epoll支持水平觸發(fā)和邊緣觸發(fā),最大的特點在于邊緣觸發(fā),它只告訴進程哪些fd剛剛變?yōu)榫托钁B(tài),并且只會通知一次。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內(nèi)核就會采用類似callback的回調(diào)機制來激活該fd,epoll_wait便可以收到通知

epoll的優(yōu)點:
1、沒有最大并發(fā)連接的限制,能打開的FD的上限遠大于1024(1G的內(nèi)存上能監(jiān)聽約10萬個端口);
2、效率提升,不是輪詢的方式,不會隨著FD數(shù)目的增加效率下降。只有活躍可用的FD才會調(diào)用callback函數(shù);
即Epoll最大的優(yōu)點就在于它只管你“活躍”的連接,而跟連接總數(shù)無關(guān),因此在實際的網(wǎng)絡(luò)環(huán)境中,Epoll的效率就會遠遠高于select和poll。
3、 內(nèi)存拷貝,利用mmap()文件映射內(nèi)存加速與內(nèi)核空間的消息傳遞;即epoll使用mmap減少復制開銷。

select、poll、epoll 區(qū)別總結(jié):

區(qū)別

總結(jié):

綜上,在選擇select,poll,epoll時要根據(jù)具體的使用場合以及這三種方式的自身特點。

1、表面上看epoll的性能最好,但是在連接數(shù)少并且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數(shù)回調(diào)。

2、select低效是因為每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設(shè)計改善

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容