新聞中心
在網(wǎng)絡(luò)編程領(lǐng)域中,I/O(Input/Output)操作是常見的操作之一。由于網(wǎng)絡(luò)操作涉及到數(shù)據(jù)傳輸,必然存在傳輸過程中的阻塞問題。如果一個操作在傳輸數(shù)據(jù)時遇到阻塞,那么程序就會停下來等待傳輸完成,這樣就會導(dǎo)致程序在等待的過程中處于空閑狀態(tài),從而影響整個程序的運行效率,為了解決這個問題,Linux 采用了非阻塞 IO 模型。下面我們就簡要介紹一下這個模型。

1. 阻塞 IO 模型
在傳統(tǒng)的阻塞 IO 模型中,當(dāng)一個 I/O 操作被阻塞時,程序會停止執(zhí)行,直到相應(yīng)的操作完成后程序才會繼續(xù)執(zhí)行。以一個簡單的網(wǎng)絡(luò)編程流程為例,客戶端向服務(wù)器發(fā)送數(shù)據(jù),服務(wù)器將數(shù)據(jù)讀取出來,并將處理結(jié)果返回給客戶端,客戶端接收到處理結(jié)果后再做出后續(xù)操作,這樣的流程中,如果客戶端發(fā)送數(shù)據(jù)的過程中發(fā)生阻塞,那么整個程序就會處于等待狀態(tài),直到數(shù)據(jù)發(fā)送成功才會繼續(xù)執(zhí)行后續(xù)的操作。
2. 非阻塞 IO 模型
為了解決傳統(tǒng)阻塞 IO 模型中存在的問題,Linux 提出了一種新的模型——非阻塞 IO 模型。非阻塞 IO 模型的核心思想是將數(shù)據(jù)傳輸過程中的阻塞改為非阻塞,當(dāng)一個操作遇到阻塞時,不再將程序掛起等待,而是立即返回一個 EAGN 或 EWOULDBLOCK 錯誤,告訴用戶進(jìn)程現(xiàn)在沒有數(shù)據(jù)可讀或可寫,用戶進(jìn)程就可以繼續(xù)執(zhí)行其他操作而不必等待。
3. select/poll/epoll 三種 IO 復(fù)用技術(shù)
為了實現(xiàn)非阻塞 IO 模型,Linux 提供了三種 IO 復(fù)用技術(shù),分別是 select、poll 和 epoll。這三種技術(shù)的本質(zhì)都是在 I/O 操作上設(shè)置一個觀察者,從而實現(xiàn)對多個文件描述符(socket)的異步控制。這樣我們就可以同時監(jiān)聽多個文件描述符上的事件,只有當(dāng)有一個或多個文件描述符有事件發(fā)生時,才會觸發(fā)相應(yīng)的回調(diào)函數(shù)來處理這些事件,這就是所謂的“異步回調(diào)”,在這個回調(diào)函數(shù)中可以讀取或?qū)懭霐?shù)據(jù),實現(xiàn)相應(yīng)的數(shù)據(jù)傳輸。
三種技術(shù)的區(qū)別在于它們的實現(xiàn)方式不同。select 是最傳統(tǒng)的一種基于輪詢的 IO 復(fù)用技術(shù),它可以同時控制的文件描述符數(shù)量有限,而且每次調(diào)用 select 都會掃描整個文件描述符,導(dǎo)致效率較低。poll 和 select 類似,它也是基于輪詢的,但是它可以控制的文件描述符數(shù)量沒有限制,而且 poll 支持動態(tài)增加或刪除文件描述符。epoll 是最新的 IO 復(fù)用技術(shù),它采用回調(diào)機(jī)制,只在有 I/O 事件到來時出發(fā)處理函數(shù),因此效率比 select 和 poll 要高很多,它也支持動態(tài)添加或刪除文件描述符。
4.
本文簡要介紹了 Linux 中非阻塞 IO 模型的實現(xiàn)原理和相應(yīng)的技術(shù),其中 select、poll 和 epoll 三種 IO 復(fù)用技術(shù),是實現(xiàn)非阻塞 IO 模型的核心補充手段。對于網(wǎng)絡(luò)編程人員來說,理解非阻塞 IO 模型的原理、特點和應(yīng)用,能夠幫助他們更好地編寫高效的網(wǎng)絡(luò)編程代碼,提高程序的運行效率和穩(wěn)定性。
相關(guān)問題拓展閱讀:
- linux 調(diào)試串口 是阻塞還是非阻塞模式
- Linux 磁盤IO
linux 調(diào)試串口 是阻塞還是非阻塞模式
我感覺應(yīng)當(dāng)取決于你使用的控件吧
兩種方式都提供的,取決于你調(diào)用讀寫函數(shù)的參數(shù)timeout。
當(dāng)你有能力去優(yōu)化時,你已經(jīng)不用來問這個問題了。具體到某個例子,比如說開發(fā)慶游界面,在PC上我們用VC;
在嵌入式Linux里也許我此差枯們用QT也許用Android,這個時候你應(yīng)該去學(xué)學(xué)QT、Android的編程。但是基礎(chǔ)還是C或JAVA,在此基森洞礎(chǔ)上去熟悉它們的接口。
你學(xué)過VC的話,也是要花時間去了解那些類、控件的。
Linux 磁盤IO
磁盤結(jié)構(gòu)與數(shù)據(jù)存儲方式, 數(shù)據(jù)是如何存儲的,又通過怎樣的方式被訪問?
機(jī)械硬盤主要由磁盤盤片、磁頭、主軸與傳動軸等組成;數(shù)據(jù)就存放在磁盤盤片中
現(xiàn)代硬盤尋道都是采用CHS( Cylinder Head Sector )的方式,硬盤讀取數(shù)據(jù)時,讀寫磁頭沿徑向移動,移到要讀取的扇區(qū)所在磁道的上方,這段時間稱為
尋道時間(seek time)
。
因讀寫磁頭的起始位置與目標(biāo)位置之間的距離不同,尋道時間也不同
。磁頭到達(dá)指定磁道后,然后通過盤片的旋轉(zhuǎn),使得要讀取的扇區(qū)轉(zhuǎn)到讀寫磁頭的下方,這段時間稱為
旋轉(zhuǎn)延遲時間(rotational latencytime)
。然后再讀寫數(shù)據(jù),讀手租寫數(shù)據(jù)也需要時間,這段時間稱為
傳輸時間(transfer time)
。
固態(tài)硬盤主要由主控芯片、閃存顆粒與緩存組成;數(shù)據(jù)就存放在閃存芯片中
通過主控芯片進(jìn)行尋址, 因為畢好兆是電信號方式, 沒有任何物理結(jié)構(gòu), 所以尋址速度非常快且與數(shù)據(jù)存儲位置無關(guān)
如何查看系統(tǒng)IO狀態(tài)
查看磁盤空間
調(diào)用 open , fwrite 時到底發(fā)生了什么?
在一個IO過程中,以下5個API/系統(tǒng)調(diào)用是必不可少的
Create 函數(shù)用來打開一個文件,如果該文件不存在,那么需要在磁盤上創(chuàng)建該文件
Open 函數(shù)用于打開一個指定的文件。如果在 Open 函數(shù)中指定 O_CREATE 標(biāo)記,那么 Open 函數(shù)同樣可以實現(xiàn) Create 函數(shù)的功能
Clos e函數(shù)用于釋放文件句柄
Write 和 Read 函數(shù)用于實現(xiàn)文件的讀寫過程
O_SYNC (先寫緩存, 但是需要實際落襪粗盤之后才返回, 如果接下來有讀請求, 可以從內(nèi)存讀 ), write-through
O_DSYNC (D=data, 類似O_SYNC, 但是只同步數(shù)據(jù), 不同步元數(shù)據(jù))
O_DIRECT (直接寫盤, 不經(jīng)過緩存)
O_ASYNC (異步IO, 使用信號機(jī)制實現(xiàn), 不推薦, 直接用aio_xxx)
O_NOATIME (讀取的時候不更新文件 atime(access time))
sync() 全局緩存寫回磁盤
fsync() 特定fd的sync()
fdatasync() 只刷數(shù)據(jù), 不同步元數(shù)據(jù)
mount noatime(全局不記錄atime), re方式(只讀), sync(同步方式)
一個IO的傳奇一生 這里有一篇非常好的資料,講述了整個IO過程;
下面簡單記錄下自己的理解的一次常見的Linux IO過程, 想了解更詳細(xì)及相關(guān)源碼,非常推薦閱讀上面的原文
Linux IO體系結(jié)構(gòu)
Superblock
超級描述了整個文件系統(tǒng)的信息。為了保證可靠性,可以在每個塊組中對superblock進(jìn)行備份。為了避免superblock冗余過多,可以采用稀疏存儲的方式,即在若干個塊組中對superblock進(jìn)行保存,而不需要在所有的塊組中都進(jìn)行備份
GDT 組描述符表
組描述符表對整個組內(nèi)的數(shù)據(jù)布局進(jìn)行了描述。例如,數(shù)據(jù)塊位圖的起始地址是多少?inode位圖的起始地址是多少?inode表的起始地址是多少?塊組中還有多少空閑塊資源等。組描述符表在superblock的后面
數(shù)據(jù)塊位圖
數(shù)據(jù)塊位圖描述了塊組內(nèi)數(shù)據(jù)塊的使用情況。如果該數(shù)據(jù)塊已經(jīng)被某個文件使用,那么位圖中的對應(yīng)位會被置1,否則該位為0
Inode位圖
Inode位圖描述了塊組內(nèi)inode資源使用情況。如果一個inode資源已經(jīng)使用,那么對應(yīng)位會被置1
Inode表
(即inode資源)和數(shù)據(jù)塊。這兩塊占據(jù)了塊組內(nèi)的絕大部分空間,特別是數(shù)據(jù)塊資源
一個文件是由inode進(jìn)行描述的。一個文件占用的數(shù)據(jù)塊block是通過inode管理起來的
。在inode結(jié)構(gòu)中保存了直接塊指針、一級間接塊指針、二級間接塊指針和三級間接塊指針。對于一個小文件,直接可以采用直接塊指針實現(xiàn)對文件塊的訪問;對于一個大文件,需要采用間接塊指針實現(xiàn)對文件塊的訪問
最簡單的調(diào)度器。它本質(zhì)上就是一個鏈表實現(xiàn)的
fifo
隊列,并對請求進(jìn)行簡單的
合并
處理。
調(diào)度器本身并沒有提供任何可以配置的參數(shù)
讀寫請求被分成了兩個隊列, 一個用訪問地址作為索引,一個用進(jìn)入時間作為索引,并且采用兩種方式將這些request管理起來;
在請求處理的過程中,deadline算法會優(yōu)先處理那些訪問地址臨近的請求,這樣可以更大程度的減少磁盤抖動的可能性。
只有在有些request即將被餓死的時候,或者沒有辦法進(jìn)行磁盤順序化操作的時候,deadline才會放棄地址優(yōu)先策略,轉(zhuǎn)而處理那些即將被餓死的request
deadline算法可調(diào)整參數(shù)
read_expire
: 讀請求的超時時間設(shè)置(ms)。當(dāng)一個讀請求入隊deadline的時候,其過期時間將被設(shè)置為當(dāng)前時間+read_expire,并放倒fifo_list中進(jìn)行排序
write_expire
:寫請求的超時時間設(shè)置(ms)
fifo_batch
:在順序(sort_list)請求進(jìn)行處理的時候,deadline將以batch為單位進(jìn)行處理。每一個batch處理的請求個數(shù)為這個參數(shù)所限制的個數(shù)。在一個batch處理的過程中,不會產(chǎn)生是否超時的檢查,也就不會產(chǎn)生額外的磁盤尋道時間。這個參數(shù)可以用來平衡順序處理和饑餓時間的矛盾,當(dāng)饑餓時間需要盡可能的符合預(yù)期的時候,我們可以調(diào)小這個值,以便盡可能多的檢查是否有饑餓產(chǎn)生并及時處理。增大這個值當(dāng)然也會增大吞吐量,但是會導(dǎo)致處理饑餓請求的延時變長
writes_starved
:這個值是在上述deadline出隊處理之一步時做檢查用的。用來判斷當(dāng)讀隊列不為空時,寫隊列的饑餓程度是否足夠高,以時deadline放棄讀請求的處理而處理寫請求。當(dāng)檢查存在有寫請求的時候,deadline并不會立即對寫請求進(jìn)行處理,而是給相關(guān)數(shù)據(jù)結(jié)構(gòu)中的starved進(jìn)行累計,如果這是之一次檢查到有寫請求進(jìn)行處理,那么這個計數(shù)就為1。如果此時writes_starved值為2,則我們認(rèn)為此時饑餓程度還不足夠高,所以繼續(xù)處理讀請求。只有當(dāng)starved >= writes_starved的時候,deadline才回去處理寫請求??梢哉J(rèn)為這個值是用來平衡deadline對讀寫請求處理優(yōu)先級狀態(tài)的,這個值越大,則寫請求越被滯后處理,越小,寫請求就越可以獲得趨近于讀請求的優(yōu)先級
front_merges
:當(dāng)一個新請求進(jìn)入隊列的時候,如果其請求的扇區(qū)距離當(dāng)前扇區(qū)很近,那么它就是可以被合并處理的。而這個合并可能有兩種情況,一個是向當(dāng)前位置后合并,另一種是向前合并。在某些場景下,向前合并是不必要的,那么我們就可以通過這個參數(shù)關(guān)閉向前合并。默認(rèn)deadline支持向前合并,設(shè)置為0關(guān)閉
在調(diào)度一個request時,首先需要選擇一個一個合適的cfq_group。Cfq調(diào)度器會為每個cfq_group分配一個時間片,當(dāng)這個時間片耗盡之后,會選擇下一個cfq_group。每個cfq_group都會分配一個vdisktime,并且通過該值采用紅黑樹對cfq_group進(jìn)行排序。在調(diào)度的過程中,每次都會選擇一個vdisktime最小的cfq_group進(jìn)行處理。
一個cfq_group管理了7棵service tree,每棵service tree管理了需要調(diào)度處理的對象cfq_queue。因此,一旦cfq_group被選定之后,需要選擇一棵service tree進(jìn)行處理。這7棵service tree被分成了三大類,分別為RT、BE和IDLE。這三大類service tree的調(diào)度是按照優(yōu)先級展開的
通過優(yōu)先級可以很容易的選定一類Service tree。當(dāng)一類service tree被選定之后,采用service time的方式選定一個合適的cfq_queue。每個Service tree是一棵紅黑樹,這些紅黑樹是按照service time進(jìn)行檢索的,每個cfq_queue都會維護(hù)自己的service time。分析到這里,我們知道,cfq算法通過每個cfq_group的vdisktime值來選定一個cfq_group進(jìn)行服務(wù),在處理cfq_group的過程通過優(yōu)先級選擇一個最需要服務(wù)的service tree。通過該Service tree得到最需要服務(wù)的cfq_queue。該過程在 cfq_select_queue 函數(shù)中實現(xiàn)
一個cfq_queue被選定之后,后面的過程和deadline算法有點類似。在選擇request的時候需要考慮每個request的延遲等待時間,選擇那種等待時間最長的request進(jìn)行處理。但是,考慮到磁盤抖動的問題,cfq在處理的時候也會進(jìn)行順序批量處理,即將那些在磁盤上連續(xù)的request批量處理掉
cfq調(diào)度算法的參數(shù)
back_seek_max
:磁頭可以向后尋址的更大范圍,默認(rèn)值為16M
back_seek_penalty
:向后尋址的懲罰系數(shù)。這個值是跟向前尋址進(jìn)行比較的
fifo_expire_async
:設(shè)置異步請求的超時時間。同步請求和異步請求是區(qū)分不同隊列處理的,cfq在調(diào)度的時候一般情況都會優(yōu)先處理同步請求,之后再處理異步請求,除非異步請求符合上述合并處理的條件限制范圍內(nèi)。當(dāng)本進(jìn)程的隊列被調(diào)度時,cfq會優(yōu)先檢查是否有異步請求超時,就是超過fifo_expire_async參數(shù)的限制。如果有,則優(yōu)先發(fā)送一個超時的請求,其余請求仍然按照優(yōu)先級以及扇區(qū)編號大小來處理
fifo_expire_sync
:這個參數(shù)跟上面的類似,區(qū)別是用來設(shè)置同步請求的超時時間
slice_idle
:參數(shù)設(shè)置了一個等待時間。這讓cfq在切換cfq_queue或service tree的時候等待一段時間,目的是提高機(jī)械硬盤的吞吐量。一般情況下,來自同一個cfq_queue或者service tree的IO請求的尋址局部性更好,所以這樣可以減少磁盤的尋址次數(shù)。這個值在機(jī)械硬盤上默認(rèn)為非零。當(dāng)然在固態(tài)硬盤或者硬RAID設(shè)備上設(shè)置這個值為非零會降低存儲的效率,因為固態(tài)硬盤沒有磁頭尋址這個概念,所以在這樣的設(shè)備上應(yīng)該設(shè)置為0,關(guān)閉此功能
group_idle
:這個參數(shù)也跟上一個參數(shù)類似,區(qū)別是當(dāng)cfq要切換cfq_group的時候會等待一段時間。在cgroup的場景下,如果我們沿用slice_idle的方式,那么空轉(zhuǎn)等待可能會在cgroup組內(nèi)每個進(jìn)程的cfq_queue切換時發(fā)生。這樣會如果這個進(jìn)程一直有請求要處理的話,那么直到這個cgroup的配額被耗盡,同組中的其它進(jìn)程也可能無法被調(diào)度到。這樣會導(dǎo)致同組中的其它進(jìn)程餓死而產(chǎn)生IO性能瓶頸。在這種情況下,我們可以將slice_idle = 0而group_idle = 8。這樣空轉(zhuǎn)等待就是以cgroup為單位進(jìn)行的,而不是以cfq_queue的進(jìn)程為單位進(jìn)行,以防止上述問題產(chǎn)生
low_latency
:這個是用來開啟或關(guān)閉cfq的低延時(low latency)模式的開關(guān)。當(dāng)這個開關(guān)打開時,cfq將會根據(jù)target_latency的參數(shù)設(shè)置來對每一個進(jìn)程的分片時間(slice time)進(jìn)行重新計算。這將有利于對吞吐量的公平(默認(rèn)是對時間片分配的公平)。關(guān)閉這個參數(shù)(設(shè)置為0)將忽略target_latency的值。這將使系統(tǒng)中的進(jìn)程完全按照時間片方式進(jìn)行IO資源分配。這個開關(guān)默認(rèn)是打開的
target_latency
:當(dāng)low_latency的值為開啟狀態(tài)時,cfq將根據(jù)這個值重新計算每個進(jìn)程分配的IO時間片長度
quantum
:這個參數(shù)用來設(shè)置每次從cfq_queue中處理多少個IO請求。在一個隊列處理事件周期中,超過這個數(shù)字的IO請求將不會被處理。這個參數(shù)只對同步的請求有效
slice_sync
:當(dāng)一個cfq_queue隊列被調(diào)度處理時,它可以被分配的處理總時間是通過這個值來作為一個計算參數(shù)指定的。公式為: time_slice = slice_sync + (slice_sync/5 * (4 – prio)) 這個參數(shù)對同步請求有效
slice_async
:這個值跟上一個類似,區(qū)別是對異步請求有效
slice_async_rq
:這個參數(shù)用來限制在一個slice的時間范圍內(nèi),一個隊列最多可以處理的異步請求個數(shù)。請求被處理的更大個數(shù)還跟相關(guān)進(jìn)程被設(shè)置的io優(yōu)先級有關(guān)
通常在Linux上使用的IO接口是同步方式的,進(jìn)程調(diào)用 write / read 之后會阻塞陷入到內(nèi)核態(tài),直到本次IO過程完成之后,才能繼續(xù)執(zhí)行,下面介紹的異步IO則沒有這種限制,但是當(dāng)前Linux異步IO尚未成熟
目前Linux aio還處于較不成熟的階段,只能在 O_DIRECT 方式下才能使用(glibc_aio),也就是無法使用默認(rèn)的Page Cache機(jī)制
正常情況下,使用aio族接口的簡要方式如下:
io_uring 是 2023 年 5 月發(fā)布的 Linux 5.1 加入的一個重大特性 —— Linux 下的全新的異步 I/O 支持,希望能徹底解決長期以來 Linux AIO 的各種不足
io_uring 實現(xiàn)異步 I/O 的方式其實是一個生產(chǎn)者-消費者模型:
邏輯卷管理
RAID0
RAID1
RAID5(糾錯)
條帶化
Linux系統(tǒng)性能調(diào)整:IO過程
Linux的IO調(diào)度
一個IO的傳奇一生
理解inode
Linux 文件系統(tǒng)是怎么工作的?
Linux中Buffer cache性能問題一探究竟
Asynchronous I/O and event notification on linux
AIO 的新歸宿:io_uring
關(guān)于linux非阻塞i o模型的介紹到此就結(jié)束了,不知道你從中找到你需要的信息了嗎 ?如果你還想了解更多這方面的信息,記得收藏關(guān)注本站。
創(chuàng)新互聯(lián)服務(wù)器托管擁有成都T3+級標(biāo)準(zhǔn)機(jī)房資源,具備完善的安防設(shè)施、三線及BGP網(wǎng)絡(luò)接入帶寬達(dá)10T,機(jī)柜接入千兆交換機(jī),能夠有效保證服務(wù)器托管業(yè)務(wù)安全、可靠、穩(wěn)定、高效運行;創(chuàng)新互聯(lián)專注于成都服務(wù)器托管租用十余年,得到成都等地區(qū)行業(yè)客戶的一致認(rèn)可。
標(biāo)題名稱:Linux非阻塞IO模型簡介(linux非阻塞io模型)
網(wǎng)頁URL:http://m.fisionsoft.com.cn/article/cdjcpjd.html


咨詢
建站咨詢
