什么是 IO(應(yīng)用程序不能直接操作內(nèi)核空間需要將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間才能使用無論是read操作還是write操作都只能在內(nèi)核空間里執(zhí)行) 在計算機操作系統(tǒng)中,,所謂的I/O就是 輸入(Input)和輸出(Output),,也可以理解為讀(Read)和寫(Write),針對不同的對象,,I/O模式可以劃分為磁盤IO模型和網(wǎng)絡(luò)IO模型,。 IO操作會涉及到用戶空間和內(nèi)核空間的轉(zhuǎn)換,先來理解以下規(guī)則:
再來看看所謂的讀(Read)和寫(Write)操作:
用戶空間&內(nèi)核空間野生程序員對于這個概念可能比較陌生,這其實是 Linux 操作系統(tǒng)中的概念,。虛擬內(nèi)存(操作系統(tǒng)中的概念,,和物理內(nèi)存是對應(yīng)的)被操作系統(tǒng)劃分成兩塊:User Space(用戶空間 和 Kernel Space(內(nèi)核空間),本質(zhì)上電腦的物理內(nèi)存是不劃分這些的,,只是操作系統(tǒng)開機啟動后在邏輯上虛擬劃分了地址和空間范圍,。 操作系統(tǒng)會給每個進(jìn)程分配一個獨立的、連續(xù)的虛擬內(nèi)存地址空間(物理上可能不連續(xù)),,以32位操作系統(tǒng)為例,,該大小一般是4G,即232 ,。其中將高地址值的內(nèi)存空間分配給系統(tǒng)內(nèi)核占用(網(wǎng)上查資料得知:Linux下占1G,,Windows下占2G),其余的內(nèi)存地址空間分配給用戶進(jìn)程使用,。 因為我們不是要深入學(xué)習(xí)操作系統(tǒng),,所以這里以32位系統(tǒng)舉例旨在幫助你理解原理。32 位的 LInux 操作系統(tǒng)下,,0~3G為用戶空間,,3~4G為內(nèi)核空間: 那為什么要這樣劃分出空間范圍呢? 也很好理解,,畢竟操作系統(tǒng)身份高貴,,太重要了,不能和用戶應(yīng)用程序在一起玩耍,,各自的數(shù)據(jù)都要分開存儲并且嚴(yán)格控制權(quán)限不能越界,。這樣才能保證操作系統(tǒng)的穩(wěn)定運行,用戶應(yīng)用程序太不可控了,,不同公司或者個人都可以開發(fā),,碰到坑爹的誤操作或者惡意破壞系統(tǒng)數(shù)據(jù)直接宕機玩完了。隔離后應(yīng)用程序要掛你就掛,,操作系統(tǒng)可以正常運行,。 簡單說,內(nèi)核空間 是操作系統(tǒng) 內(nèi)核代碼運行的地方,,用戶空間 是 用戶程序代碼運行的地方,。當(dāng)應(yīng)用進(jìn)程執(zhí)行系統(tǒng)調(diào)用陷入內(nèi)核代碼中執(zhí)行時就處于內(nèi)核態(tài),當(dāng)應(yīng)用進(jìn)程在運行用戶代碼時就處于用戶態(tài),。 同時內(nèi)核空間可以執(zhí)行任意的命令,,而用戶空間只能執(zhí)行簡單的運算,不能直接調(diào)用系統(tǒng)資源和數(shù)據(jù),。必須通過操作系統(tǒng)提供接口,,向系統(tǒng)內(nèi)核發(fā)送指令。 一旦調(diào)用系統(tǒng)接口,,應(yīng)用進(jìn)程就從用戶空間切換到內(nèi)核空間了,,因為開始運行內(nèi)核代碼了。 簡單看幾行代碼,,分析下是應(yīng)用程序在用戶空間和內(nèi)核空間之間的切換過程: str = "i am qige" // 用戶空間 x = x + 2 file.write(str) // 切換到內(nèi)核空間 y = x + 4 // 切換回用戶空間 上面代碼中,,第一行和第二行都是簡單的賦值運算,在用戶空間執(zhí)行,。第三行需要寫入文件,,就要切換到內(nèi)核空間,因為用戶不能直接寫文件,,必須通過內(nèi)核安排,。第四行又是賦值運算,就切換回用戶空間,。 用戶態(tài)切換到內(nèi)核態(tài)的3種方式:
以上3種方式,除了系統(tǒng)調(diào)用是進(jìn)程主動發(fā)起切換,,異常和外圍設(shè)備中斷是被動切換的,。 查看 CPU 時間在 User Space 與 Kernel Space 之間的分配情況,可以使用top命令,。它的第三行輸出就是 CPU 時間分配統(tǒng)計,。 我們來看看圖中圈出來的 CPU 使用率的三個指標(biāo): 其中,第一項 7.57% user 就是 CPU 消耗在 User Space 的時間百分比,,第二項 7.0% sys是消耗在 Kernel Space 的時間百分比,。第三項 85.4% idle 是 CPU 消耗在閑置進(jìn)程的時間百分比,這個值越低,,表示 CPU 越忙,。 PIO&DMA大家都知道一般我們的數(shù)據(jù)是存儲在磁盤上的,應(yīng)用程序想要讀寫這些數(shù)據(jù)肯定就需要加載到內(nèi)存中,。接下來給大家介紹下 PIO 和 DMA 這兩種 IO 設(shè)備和內(nèi)存之間的數(shù)據(jù)傳輸方式,。 PIO 工作原理
PIO缺點:每次IO請求都需要CPU多次參與,,效率很低,。 DMA 工作原理DMA(直接內(nèi)存訪問,Direct Memory Access),。
跟PIO模式相比,,DMA就是CPU的一個代理,,它負(fù)責(zé)了一部分的拷貝工作,從而減輕了CPU的負(fù)擔(dān),。 需要注意的是,,DMA承擔(dān)的工作是從磁盤的緩沖區(qū)到內(nèi)核緩沖區(qū)或網(wǎng)卡設(shè)備到內(nèi)核的 soket buffer的拷貝工作,以及內(nèi)核緩沖區(qū)到磁盤緩沖區(qū)或內(nèi)核的 soket buffer 到網(wǎng)卡設(shè)備的拷貝工作,,而內(nèi)核緩沖區(qū)到用戶緩沖區(qū)之間的拷貝工作仍然由CPU負(fù)責(zé),。 可以肯定的是,PIO模式的計算機我們現(xiàn)在已經(jīng)很少見到了,。 緩沖IO和直接IO學(xué)習(xí)用戶空間和內(nèi)核空間的時候我們也說了,,用戶空間是不能直接訪問內(nèi)核空間的數(shù)據(jù)的,如果需要訪問怎么辦,?很簡單,,就需要將數(shù)據(jù)從內(nèi)核空間拷貝的用戶空間。
緩沖 IO緩沖 IO 也被成為標(biāo)準(zhǔn) IO,,大多數(shù)的文件系統(tǒng)系統(tǒng)默認(rèn)都是以緩沖 IO 的方式來工作的,。在Linux的緩沖I/O機制中,數(shù)據(jù)先從磁盤復(fù)制到內(nèi)核空間的緩沖區(qū),,然后從內(nèi)核空間緩沖區(qū)復(fù)制到應(yīng)用程序的地址空間,。 接下來我們看看緩沖 IO 下讀寫操作是如何進(jìn)行?
操作系統(tǒng)檢查內(nèi)核的緩沖區(qū)有沒有需要的數(shù)據(jù),,如果已經(jīng)緩沖了,,那么就直接從緩沖中返回;否則從磁盤中讀取到內(nèi)核緩沖中,,然后再復(fù)制到用戶空間緩沖中,。
將數(shù)據(jù)從用戶空間復(fù)制到內(nèi)核空間的緩沖中,。這時對用戶程序來說寫操作就已經(jīng)完成,至于什么時候再寫到磁盤中由操作系統(tǒng)決定,,除非顯示地調(diào)用了sync同步命令,。 緩沖I/O的優(yōu)點:
緩沖I/O的缺點: 在緩沖 I/O 機制中,,DMA 方式可以將數(shù)據(jù)直接從磁盤讀到內(nèi)核空間頁緩沖中,或者將數(shù)據(jù)從內(nèi)核空間頁緩沖直接寫回到磁盤上,,而不能直接在用戶地址空間和磁盤之間進(jìn)行數(shù)據(jù)傳輸,,這樣數(shù)據(jù)在傳輸過程中需要在應(yīng)用程序地址空間(用戶空間)和內(nèi)核緩沖(內(nèi)核空間)之間進(jìn)行多次數(shù)據(jù)拷貝操作,這些數(shù)據(jù)拷貝操作所帶來的CPU以及內(nèi)存開銷是非常大的,。 直接IO顧名思義,,直接IO就是應(yīng)用程序直接訪問磁盤數(shù)據(jù),,而不經(jīng)過內(nèi)核緩沖區(qū),,也就是繞過內(nèi)核緩沖區(qū),自己管理I/O緩沖區(qū),這樣做的目的是減少一次從內(nèi)核緩沖區(qū)到用戶程序緩沖的數(shù)據(jù)復(fù)制,。 引入內(nèi)核緩沖區(qū)這個主要是為了提升從磁盤讀寫數(shù)據(jù)文件的性能,,這也是很多系統(tǒng)優(yōu)化中常見的手段,多一層緩存可以有效減少很多磁盤 IO 操作,;而當(dāng)用戶程序需要向磁盤文件中寫入數(shù)據(jù)時,,實際上只需要寫入到內(nèi)核緩沖區(qū)便可以返回了,而真正的落盤是有一定的延遲策略的,,但這無疑提升了應(yīng)用程序?qū)懭胛募捻憫?yīng)速度,。 在數(shù)據(jù)庫管理系統(tǒng)這類應(yīng)用中,它們更傾向于選擇自己實現(xiàn)的緩存機制,,因為數(shù)據(jù)庫管理系統(tǒng)往往比操作系統(tǒng)更了解數(shù)據(jù)庫中存放的數(shù)據(jù),,數(shù)據(jù)庫管理系統(tǒng)可以提供一種更加有效的緩存機制來提高數(shù)據(jù)庫中數(shù)據(jù)的存取性能。 直接I/O的優(yōu)點: 應(yīng)用程序直接訪問磁盤數(shù)據(jù),,不經(jīng)過操作系統(tǒng)內(nèi)核數(shù)據(jù)緩沖區(qū),,這樣做的最直觀目的是減少一次從內(nèi)核緩沖區(qū)到用戶程序緩沖的數(shù)據(jù)復(fù)制。這種方式通常用在數(shù)據(jù)庫,、消息中間件中,,由應(yīng)用程序來實現(xiàn)數(shù)據(jù)的緩存管理。 直接I/O的缺點: 如果訪問的數(shù)據(jù)不在應(yīng)用程序緩沖中,,那么每次數(shù)據(jù)都會直接從磁盤進(jìn)行加載,,這種直接加載會非常緩慢。通常 直接I/O 跟 異步I/O 結(jié)合使用會得到較好的性能。(異步IO:當(dāng)訪問數(shù)據(jù)的線程發(fā)出請求之后,,線程會接著去處理其他事,,而不是阻塞等待) IO 訪問方式我們常說的 IO 操作,不僅僅是磁盤 IO,,還有常見的網(wǎng)絡(luò)數(shù)據(jù)傳輸即網(wǎng)絡(luò) IO,。 磁盤 IO讀操作: 當(dāng)應(yīng)用程序調(diào)用read()方法時,操作系統(tǒng)檢查內(nèi)核高速緩沖區(qū)中是否存在需要的數(shù)據(jù),,如果存在,,那么就直接把內(nèi)核空間的數(shù)據(jù)copy到用戶空間,供用戶的應(yīng)用程序使用,。如果內(nèi)核緩沖區(qū)沒有需要的數(shù)據(jù),,通過通過DMA方式從磁盤中讀取數(shù)據(jù)到內(nèi)核緩沖區(qū),然后由CPU控制,,把內(nèi)核空間的數(shù)據(jù)copy到用戶空間,。 這個過程會涉及到兩次緩沖區(qū)copy,第一次是從磁盤到內(nèi)核緩沖區(qū),,第二次是從內(nèi)核緩沖區(qū)到用戶緩沖區(qū),,第一次是DMA的copy,第二次是CPU的copy,。 寫操作: 當(dāng)應(yīng)用程序調(diào)用write()方法時,,應(yīng)用程序?qū)?shù)據(jù)從用戶空間copy到內(nèi)核空間的緩沖區(qū)中(如果用戶空間沒有相應(yīng)的數(shù)據(jù),則需要從磁盤—>內(nèi)核緩沖區(qū)—>用戶緩沖區(qū)),,這時對用戶程序來說寫操作就已經(jīng)完成,,至于什么時候把數(shù)據(jù)再寫到磁盤(從內(nèi)核緩沖區(qū)到磁盤的寫操作也由DMA控制,不需要cpu參與),,由操作系統(tǒng)決定,。除非應(yīng)用程序顯示地調(diào)用了sync命令,立即把數(shù)據(jù)寫入磁盤,。 如果應(yīng)用程序沒準(zhǔn)備好寫的數(shù)據(jù),,則必須先從磁盤讀取數(shù)據(jù)才能執(zhí)行寫操作,這時會涉及到四次緩沖區(qū)的copy,,第一次是從磁盤的緩沖區(qū)到內(nèi)核緩沖區(qū),,第二次是從內(nèi)核緩沖區(qū)到用戶緩沖區(qū),第三次是從用戶緩沖區(qū)到內(nèi)核緩沖區(qū),,第四次是從內(nèi)核緩沖區(qū)寫回到磁盤,。前兩次是為了讀,后兩次是為了寫,。這其中有兩次 CPU 拷貝,,兩次DMA拷貝。 磁盤IO的延時: 為了讀或?qū)?,磁頭必須能移動到所指定的磁道上,,并等待所指定的扇區(qū)的開始位置旋轉(zhuǎn)到磁頭下,,然后再開始讀或?qū)憯?shù)據(jù)。磁盤IO的延時分成以下三部分:
網(wǎng)絡(luò) IO讀操作: 網(wǎng)絡(luò) IO 既可以從物理磁盤中讀數(shù)據(jù),也可以從Socket中讀數(shù)據(jù)(從網(wǎng)卡中獲?。?。當(dāng)從物理磁盤中讀數(shù)據(jù)的時候,其流程和磁盤IO的讀操作一樣,。當(dāng)從Socket中讀數(shù)據(jù),,應(yīng)用程序需要等待客戶端發(fā)送數(shù)據(jù),如果客戶端還沒有發(fā)送數(shù)據(jù),,對應(yīng)的應(yīng)用程序?qū)蛔枞?,直到客戶端發(fā)送了數(shù)據(jù),該應(yīng)用程序才會被喚醒,,從Socket協(xié)議棧(網(wǎng)卡)中讀取客戶端發(fā)送的數(shù)據(jù)到內(nèi)核空間(這個過程也由DMA控制),,然后把內(nèi)核空間的數(shù)據(jù) copy 到用戶空間,供應(yīng)用程序使用,。 寫操作: 為了簡化描述,,我們假設(shè)網(wǎng)絡(luò)IO的數(shù)據(jù)從磁盤中獲取,,讀寫操作的流程如下:
網(wǎng)絡(luò)IO 的寫操作也有四次緩沖區(qū)的copy,,第一次是從磁盤緩沖區(qū)到內(nèi)核緩沖區(qū)(由DMA控制),,第二次是內(nèi)核緩沖區(qū)到用戶緩沖區(qū)(CPU控制),第三次是用戶緩沖區(qū)到內(nèi)核緩沖區(qū)的 Socket Buffer(由CPU控制),,第四次是從內(nèi)核緩沖區(qū)的 Socket Buffer 到網(wǎng)卡設(shè)備(由DMA控制),。四次緩沖區(qū)的copy工作兩次由CPU控制,兩次由DMA控制,。 網(wǎng)絡(luò)IO的延時: 網(wǎng)絡(luò)IO主要延時是由:服務(wù)器響應(yīng)延時+帶寬限制+網(wǎng)絡(luò)延時+跳轉(zhuǎn)路由延時+本地接收延時 決定,。一般為幾十到幾千毫秒,,受環(huán)境影響較大。所以,,一般來說,,網(wǎng)絡(luò)IO延時要大于磁盤IO延時(不過同數(shù)據(jù)中心的交互除外,會比磁盤 IO 更快),。 零拷貝 IO在上述IO中,,一次讀寫操作要經(jīng)過四次緩沖區(qū)的拷貝,并經(jīng)歷了四次內(nèi)核態(tài)和用戶態(tài)的切換,。 零拷貝(zero copy)IO 技術(shù)減少不必要的內(nèi)核緩沖區(qū)跟用戶緩沖區(qū)之間的拷貝,,從而減少CPU的開銷和狀態(tài)切換帶來的開銷,達(dá)到性能的提升,。 我們還是對比上面不使用零拷貝時的網(wǎng)絡(luò) IO 傳輸過程來對比分析下: 和上圖普通的網(wǎng)絡(luò) IO 傳輸過程對比,,零拷貝的傳輸過程:硬盤 -> kernel buffer (快速拷貝到kernel socket buffer) -> Socket協(xié)議棧(網(wǎng)卡設(shè)備中)。
這里,,只經(jīng)歷了三次緩沖區(qū)的拷貝,,第一次是從磁盤緩沖區(qū)到內(nèi)核緩沖區(qū),第二次是從內(nèi)核緩沖區(qū)到 kernel socket buffer,,第三次是從 kernel socket buffer 到Socket 協(xié)議棧(網(wǎng)卡設(shè)備中),。只發(fā)生兩次內(nèi)核態(tài)和用戶態(tài)的切換,第一次是當(dāng)應(yīng)用程序調(diào)用read方法時,,用戶態(tài)切換到內(nèi)核態(tài)執(zhí)行read系統(tǒng)調(diào)用,,第二次是將數(shù)據(jù)從網(wǎng)絡(luò)中發(fā)送出去后系統(tǒng)調(diào)用返回,從內(nèi)核態(tài)切換到用戶態(tài),。 零拷貝(zero copy)的應(yīng)用:
注意:零拷貝要求輸入的fd必須是文件句柄,,不能是socket,,輸出的fd必須是socket,,也就是說,數(shù)據(jù)的來源必須是從本地的磁盤,,而不能是從網(wǎng)絡(luò)中,,如果數(shù)據(jù)來源于socket,就不能使用零拷貝功能了,。我們看一下sendfile接口就知道了: #include <sys/sendfile.h> ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
in_fd 必須指向真實的文件,不能是socket和管道,;而out_fd則必須是一個socket,。由此可見,sendfile 幾乎是專門為在網(wǎng)絡(luò)上傳輸文件而設(shè)計的,。 在Linxu系統(tǒng)中,,一切皆文件,因此socket也是一個文件,,也有文件句柄(或文件描述符),。 同步&異步、阻塞&非阻塞這兩組概念,,我接觸編程以來,,經(jīng)過聽到別人說服務(wù)端是 同步非阻塞模型 或者 異步阻塞的 IO 模型,也前后了解過幾次,,但是理解都不夠透徹,,特別是這個非阻塞和異步、同步和阻塞的概念很容易懵逼,,每個人的說法都不一樣,最近我耐心看了幾篇文章,,這次我感覺我是頓悟了,,這里分享下我的理解: 同步和異步是針對應(yīng)用程序向內(nèi)核發(fā)起任務(wù)后的狀態(tài)而言的:如果發(fā)起調(diào)用后,在沒有得到結(jié)果之前,,當(dāng)前調(diào)用就不返回,,不能接著做后面的事情,一直等待就是同步,。異步就是發(fā)出調(diào)用后,,雖然不能立即得到結(jié)果,,但是可以繼續(xù)執(zhí)行后面的事情,等調(diào)用結(jié)果出來時,,會通過狀態(tài),、通知和回調(diào)來通知調(diào)用者。 舉個例子加深下理解:
阻塞blocking,、非阻塞non-blocking,則聚焦的是CPU在等待結(jié)果的過程中的狀態(tài),。 阻塞調(diào)用是指調(diào)用結(jié)果返回之前,,當(dāng)前線程會被掛起,只有在得到結(jié)果之后才會返回,。你可能會把阻塞調(diào)用和同步調(diào)用等同起來,,實際上它們是不同的,同步只是說必須等到出結(jié)果才可以返回,,但是等的過程中線程可以是激活的,,阻塞是說線程被掛起了。 非阻塞和阻塞的概念相對應(yīng),,指在不能立刻得到結(jié)果之前,,該函數(shù)不會阻塞當(dāng)前線程,而會立刻返回,。 比如前面的例子,,排隊的過程中什么也不能做就是阻塞,CPU 執(zhí)行權(quán)是交出去的,;一邊排隊,,一邊看手機就是非阻塞,CPU 執(zhí)行權(quán)還在自己手里,,但是沒看完病之前依舊是在排隊死等,,所以還是同步的,。 總結(jié)通過今天的學(xué)習(xí),我們掌握了什么是 IO,、常見的 IO 操作類型以及對應(yīng)操作的原理,,還有非常重要但是卻很容易搞混的同步&異步、阻塞&非阻塞之間的區(qū)別,,講解的應(yīng)該還是比較清楚的,。 本文內(nèi)容還是比較簡單的,是一些基礎(chǔ)知識,,但是如果想深入學(xué)習(xí)網(wǎng)絡(luò)編程這些基礎(chǔ)是繞不開的,,了解了操作系統(tǒng)對于 IO 操作的優(yōu)化,才能搞明白各種高性能網(wǎng)絡(luò)服務(wù)器的原理,。 原文鏈接: 作者:七哥聊編程 |
|