當前位置:首頁 » 操作系統 » linux文件io

linux文件io

發布時間: 2023-02-17 23:43:21

linux系統如何查看網路IO

首先 、用top命令查看

top - 16:15:05 up 6 days, 6:25, 2 users, load average: 1.45, 1.77, 2.14

Tasks: 147 total, 1 running, 146 sleeping, 0 stopped, 0 zombie

Cpu(s): 0.2% us, 0.2% sy, 0.0% ni, 86.9% id, 12.6% wa, 0.0% hi, 0.0% si

Mem: 4037872k total, 4003648k used, 34224k free, 5512k buffers

Swap: 7164948k total, 629192k used, 6535756k free, 3511184k cached

查看12.6% wa

IO等待所佔用的CPU時間的百分比,高過30%時IO壓力高

其次、 用iostat -x 1 10

avg-cpu: %user %nice %sys %iowait %idle

0.00 0.00 0.25 33.46 66.29

Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util

sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

sdb 0.00 1122 17.00 9.00 192.00 9216.00 96.00 4608.00 123.79 137.23 1033.43 13.17 100.10

sdc 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

查看%util 100.10 %idle 66.29

如果 %util 接近 100%,說明產生的I/O請求太多,I/O系統已經滿負荷,該磁碟可能存在瓶頸。

idle小於70% IO壓力就較大了,一般讀取速度有較多的wait.

同時可以結合vmstat 查看查看b參數(等待資源的進程數)

vmstat -1

如果你想對硬碟做一個IO負荷的壓力測試可以用如下命令

time dd if=/dev/zero bs=1M count=2048 of=direct_2G

此命令為在當前目錄下新建一個2G的文件

我們在新建文件夾的同時來測試IO的負荷情況。

② linux 文件IO操作中,用read函數讀取文件,有沒有辦法每次只讀取一行,而不是讀取指定的位元組數

sorry,開始沒注意空格
char buf[256]; //一行超過256個位元組??
char temp;
int i = 0;
do
{
if(read(fd, &temp, 1) == 0)break;
if(temp == '\n') break;
buf[i++] = temp;
}while(1);
buf[i] =='\0';

③ 如何提高Linux伺服器磁碟io性能

您好,很高興為您解答。

在現有文件系統下進行優化:
linux內核和各個文件系統採用了幾個優化方案來提升磁碟訪問速度。但這些優化方案需要在我們的伺服器設計中進行配合才能得到充分發揮。
文件系統緩存
linux內核會將大部分空閑內存交給虛擬文件系統,來作為文件緩存,叫做page cache。在內存不足時,這部分內存會採用lru演算法進行淘汰。通過free命令查看內存,顯示為cached的部分就是文件緩存了。

如何針對性優化:
lru並不是一個優秀淘汰演算法,lru最大的優勢是普適性好,在各種使用場景下都能起到一定的效果。如果能找到當前使用場景下,文件被訪問的統計特徵,針 對性的寫一個淘汰演算法,可以大幅提升文件緩存的命中率。對於http正向代理來說,一個好的淘汰演算法可以用1GB內存達到lru演算法100GB內存的緩存 效果。如果不打算寫一個新的淘汰演算法,一般不需要在應用層再搭一個文件cache程序來做緩存。

最小分配:
當文件擴大,需要分配磁碟空間時,大部分文件系統不會僅僅只分配當前需要的磁碟空間,而是會多分配一些磁碟空間。這樣下次文件擴大時就可以使用已經分配好的空間,而不會頻繁的去分配新空間。
例如ext3下,每次分配磁碟空間時,最小是分配8KB。
最小分配的副作用是會浪費一些磁碟空間(分配了但是又沒有使用)

如何針對性優化:
我們在reiserfs下將最小分配空間從8KB改大到128K後提升了30%的磁碟io性能。如果當前使用場景下小文件很多,把預分配改大就會浪費很多 磁碟空間,所以這個數值要根據當前使用場景來設定。似乎要直接改源代碼才能生效,不太記得了,09年的時候改的,有興趣的同學自己google吧。

io訪問調度:
在同時有多個io訪問時,linux內核可以對這些io訪問按LBA進行合並和排序,這樣磁頭在移動時,可以「順便」讀出移動過程中的數據。
SATA等磁碟甚至在磁碟中內置了io排序來進一步提升性能,一般需要在主板中進行配置才能啟動磁碟內置io排序。linux的io排序是根據LBA進行的,但LBA是一個一維線性地址,無法完全反應出二維的圓形磁碟,所以磁碟的內置io排序能達到更好的效果。

如何針對性優化:
io訪問調度能大幅提升io性能,前提是應用層同時發起了足夠的io訪問供linux去調度。
怎樣才能從應用層同時向內核發起多個io訪問呢?
方案一是用aio_read非同步發起多個文件讀寫請求。
方案二是使用磁碟線程池同時發起多個文件讀寫請求。
對我們的http正向代理來說,採用16個線程讀寫磁碟可以將性能提升到2.5倍左右。具體開多少個線程/進程,可以根據具體使用場景來決定。

小提示:
將文件句柄設置為非阻塞時,進程還是會睡眠等待磁碟io,非阻塞對於文件讀寫是不生效的。在正常情況下,讀文件只會引入十幾毫秒睡眠,所以不太明顯;而在磁碟io極大時,讀文件會引起十秒以上的進程睡眠。

預讀取:
linux內核可以預測我們「將來的讀請求」並提前將數據讀取出來。通過預讀取可以減少讀io的次數,並且減小讀請求的延時。

如何針對性優化:
預讀取的預測准確率是有限的,與其依賴預讀取,不如我們直接開一個較大的緩沖區,一次性將文件讀出來再慢慢處理;盡量不要開一個較小的緩沖區,循環讀文件/處理文件。
雖然說「預讀取」和「延遲分配」能起到類似的作用,但是我們自己擴大讀寫緩沖區效果要更好。

延遲分配:
當文件擴大,需要分配磁碟空間時,可以不立即進行分配,而是暫存在內存中,將多次分配磁碟空間的請求聚合在一起後,再進行一次性分配。
延遲分配的目的也是減少分配次數,從而減少文件不連續。

延遲分配的副作用有幾個:
1、如果應用程序每次寫數據後都通過fsync等介面進行強制刷新,延遲分配將不起作用
2、延遲分配有可能間歇性引入一個較大的磁碟IO延時(因為要一次性向磁碟寫入較多數據)
只有少數新文件系統支持這個特性

如何針對性優化:
如果不是對安全性(是否允許丟失)要求極高的數據,可以直接在應用程序里緩存起來,積累到一定大小再寫入,效果比文件系統的延遲分配更好。如果對安全性要求極高,建議經常用fsync強制刷新。

在線磁碟碎片整理:
Ext4提供了一款碎片整理工具,叫e4defrag,主要包含三個功能:
1、讓每個文件連續存儲
2、盡量讓每個目錄下的文件連續存儲
3、通過整理空閑磁碟空間,讓接下來的分配更不容易產生碎片

如何針對性優化:
「讓每個目錄下的文件連續存儲」是一個極有價值的功能。
傳統的做法是通過拼接圖片來將這10張圖片合並到一張大圖中,再由前端將大圖切成10張小圖。
有了e4defrag後,可以將需連續訪問的文件放在同一個文件夾下,再定期使用e4defrag進行磁碟整理。

實現自己的文件系統:
在大部分伺服器上,不需要支持「修改文件」這個功能。一旦文件創建好,就不能再做修改操作,只支持讀取和刪除。在這個前提下,我們可以消滅所有文件碎片,把磁碟io效率提升到理論極限。

有一個公式可以衡量磁碟io的效率:
磁碟利用率 = 傳輸時間/(平均尋道時間+傳輸時間)

如若滿意,請點擊回答右側【採納答案】,如若還有問題,請點擊【追問】

~ O(∩_∩)O~

④ Linux 磁碟IO

磁碟結構與數據存儲方式, 數據是如何存儲的,又通過怎樣的方式被訪問?

機械硬碟主要由磁碟碟片、磁頭、主軸與傳動軸等組成;數據就存放在磁碟碟片中

現代硬碟尋道都是採用CHS( Cylinder Head Sector )的方式,硬碟讀取數據時,讀寫磁頭沿徑向移動,移到要讀取的扇區所在磁軌的上方,這段時間稱為 尋道時間(seek time) 因讀寫磁頭的起始位置與目標位置之間的距離不同,尋道時間也不同 。磁頭到達指定磁軌後,然後通過碟片的旋轉,使得要讀取的扇區轉到讀寫磁頭的下方,這段時間稱為 旋轉延遲時間(rotational latencytime) 。然後再讀寫數據,讀寫數據也需要時間,這段時間稱為 傳輸時間(transfer time)

固態硬碟主要由主控晶元、快閃記憶體顆粒與緩存組成;數據就存放在快閃記憶體晶元中
通過主控晶元進行定址, 因為是電信號方式, 沒有任何物理結構, 所以定址速度非常快且與數據存儲位置無關

如何查看系統IO狀態

查看磁碟空間

調用 open , fwrite 時到底發生了什麼?

在一個IO過程中,以下5個API/系統調用是必不可少的
Create 函數用來打開一個文件,如果該文件不存在,那麼需要在磁碟上創建該文件
Open 函數用於打開一個指定的文件。如果在 Open 函數中指定 O_CREATE 標記,那麼 Open 函數同樣可以實現 Create 函數的功能
Clos e函數用於釋放文件句柄
Write 和 Read 函數用於實現文件的讀寫過程

O_SYNC (先寫緩存, 但是需要實際落盤之後才返回, 如果接下來有讀請求, 可以從內存讀 ), write-through
O_DSYNC (D=data, 類似O_SYNC, 但是只同步數據, 不同步元數據)
O_DIRECT (直接寫盤, 不經過緩存)
O_ASYNC (非同步IO, 使用信號機制實現, 不推薦, 直接用aio_xxx)
O_NOATIME (讀取的時候不更新文件 atime(access time))

sync() 全局緩存寫回磁碟
fsync() 特定fd的sync()
fdatasync() 只刷數據, 不同步元數據

mount noatime(全局不記錄atime), re方式(只讀), sync(同步方式)

一個IO的傳奇一生 這里有一篇非常好的資料,講述了整個IO過程;
下面簡單記錄下自己的理解的一次常見的Linux IO過程, 想了解更詳細及相關源碼,非常推薦閱讀上面的原文

Linux IO體系結構

[站外圖片上傳中...(image-38a7b-1644137945193)]

Superblock 超級描述了整個文件系統的信息。為了保證可靠性,可以在每個塊組中對superblock進行備份。為了避免superblock冗餘過多,可以採用稀疏存儲的方式,即在若干個塊組中對superblock進行保存,而不需要在所有的塊組中都進行備份
GDT 組描述符表 組描述符表對整個組內的數據布局進行了描述。例如,數據塊點陣圖的起始地址是多少?inode點陣圖的起始地址是多少?inode表的起始地址是多少?塊組中還有多少空閑塊資源等。組描述符表在superblock的後面
數據塊點陣圖 數據塊點陣圖描述了塊組內數據塊的使用情況。如果該數據塊已經被某個文件使用,那麼點陣圖中的對應位會被置1,否則該位為0
Inode點陣圖 Inode點陣圖描述了塊組內inode資源使用情況。如果一個inode資源已經使用,那麼對應位會被置1
Inode表 (即inode資源)和數據塊。這兩塊占據了塊組內的絕大部分空間,特別是數據塊資源

一個文件是由inode進行描述的。一個文件佔用的數據塊block是通過inode管理起來的 。在inode結構中保存了直接塊指針、一級間接塊指針、二級間接塊指針和三級間接塊指針。對於一個小文件,直接可以採用直接塊指針實現對文件塊的訪問;對於一個大文件,需要採用間接塊指針實現對文件塊的訪問

最簡單的調度器。它本質上就是一個鏈表實現的 fifo 隊列,並對請求進行簡單的 合並 處理。
調度器本身並沒有提供任何可以配置的參數

讀寫請求被分成了兩個隊列, 一個用訪問地址作為索引,一個用進入時間作為索引,並且採用兩種方式將這些request管理起來;
在請求處理的過程中,deadline演算法會優先處理那些訪問地址臨近的請求,這樣可以最大程度的減少磁碟抖動的可能性。
只有在有些request即將被餓死的時候,或者沒有辦法進行磁碟順序化操作的時候,deadline才會放棄地址優先策略,轉而處理那些即將被餓死的request

deadline演算法可調整參數
read_expire : 讀請求的超時時間設置(ms)。當一個讀請求入隊deadline的時候,其過期時間將被設置為當前時間+read_expire,並放倒fifo_list中進行排序
write_expire :寫請求的超時時間設置(ms)
fifo_batch :在順序(sort_list)請求進行處理的時候,deadline將以batch為單位進行處理。每一個batch處理的請求個數為這個參數所限制的個數。在一個batch處理的過程中,不會產生是否超時的檢查,也就不會產生額外的磁碟尋道時間。這個參數可以用來平衡順序處理和飢餓時間的矛盾,當飢餓時間需要盡可能的符合預期的時候,我們可以調小這個值,以便盡可能多的檢查是否有飢餓產生並及時處理。增大這個值當然也會增大吞吐量,但是會導致處理飢餓請求的延時變長
writes_starved :這個值是在上述deadline出隊處理第一步時做檢查用的。用來判斷當讀隊列不為空時,寫隊列的飢餓程度是否足夠高,以時deadline放棄讀請求的處理而處理寫請求。當檢查存在有寫請求的時候,deadline並不會立即對寫請求進行處理,而是給相關數據結構中的starved進行累計,如果這是第一次檢查到有寫請求進行處理,那麼這個計數就為1。如果此時writes_starved值為2,則我們認為此時飢餓程度還不足夠高,所以繼續處理讀請求。只有當starved >= writes_starved的時候,deadline才回去處理寫請求。可以認為這個值是用來平衡deadline對讀寫請求處理優先順序狀態的,這個值越大,則寫請求越被滯後處理,越小,寫請求就越可以獲得趨近於讀請求的優先順序
front_merges :當一個新請求進入隊列的時候,如果其請求的扇區距離當前扇區很近,那麼它就是可以被合並處理的。而這個合並可能有兩種情況,一個是向當前位置後合並,另一種是向前合並。在某些場景下,向前合並是不必要的,那麼我們就可以通過這個參數關閉向前合並。默認deadline支持向前合並,設置為0關閉

在調度一個request時,首先需要選擇一個一個合適的cfq_group。Cfq調度器會為每個cfq_group分配一個時間片,當這個時間片耗盡之後,會選擇下一個cfq_group。每個cfq_group都會分配一個vdisktime,並且通過該值採用紅黑樹對cfq_group進行排序。在調度的過程中,每次都會選擇一個vdisktime最小的cfq_group進行處理。
一個cfq_group管理了7棵service tree,每棵service tree管理了需要調度處理的對象cfq_queue。因此,一旦cfq_group被選定之後,需要選擇一棵service tree進行處理。這7棵service tree被分成了三大類,分別為RT、BE和IDLE。這三大類service tree的調度是按照優先順序展開的

通過優先順序可以很容易的選定一類Service tree。當一類service tree被選定之後,採用service time的方式選定一個合適的cfq_queue。每個Service tree是一棵紅黑樹,這些紅黑樹是按照service time進行檢索的,每個cfq_queue都會維護自己的service time。分析到這里,我們知道,cfq演算法通過每個cfq_group的vdisktime值來選定一個cfq_group進行服務,在處理cfq_group的過程通過優先順序選擇一個最需要服務的service tree。通過該Service tree得到最需要服務的cfq_queue。該過程在 cfq_select_queue 函數中實現

一個cfq_queue被選定之後,後面的過程和deadline演算法有點類似。在選擇request的時候需要考慮每個request的延遲等待時間,選擇那種等待時間最長的request進行處理。但是,考慮到磁碟抖動的問題,cfq在處理的時候也會進行順序批量處理,即將那些在磁碟上連續的request批量處理掉

cfq調度演算法的參數
back_seek_max :磁頭可以向後定址的最大范圍,默認值為16M
back_seek_penalty :向後定址的懲罰系數。這個值是跟向前定址進行比較的

fifo_expire_async :設置非同步請求的超時時間。同步請求和非同步請求是區分不同隊列處理的,cfq在調度的時候一般情況都會優先處理同步請求,之後再處理非同步請求,除非非同步請求符合上述合並處理的條件限制范圍內。當本進程的隊列被調度時,cfq會優先檢查是否有非同步請求超時,就是超過fifo_expire_async參數的限制。如果有,則優先發送一個超時的請求,其餘請求仍然按照優先順序以及扇區編號大小來處理
fifo_expire_sync :這個參數跟上面的類似,區別是用來設置同步請求的超時時間
slice_idle :參數設置了一個等待時間。這讓cfq在切換cfq_queue或service tree的時候等待一段時間,目的是提高機械硬碟的吞吐量。一般情況下,來自同一個cfq_queue或者service tree的IO請求的定址局部性更好,所以這樣可以減少磁碟的定址次數。這個值在機械硬碟上默認為非零。當然在固態硬碟或者硬RAID設備上設置這個值為非零會降低存儲的效率,因為固態硬碟沒有磁頭定址這個概念,所以在這樣的設備上應該設置為0,關閉此功能
group_idle :這個參數也跟上一個參數類似,區別是當cfq要切換cfq_group的時候會等待一段時間。在cgroup的場景下,如果我們沿用slice_idle的方式,那麼空轉等待可能會在cgroup組內每個進程的cfq_queue切換時發生。這樣會如果這個進程一直有請求要處理的話,那麼直到這個cgroup的配額被耗盡,同組中的其它進程也可能無法被調度到。這樣會導致同組中的其它進程餓死而產生IO性能瓶頸。在這種情況下,我們可以將slice_idle = 0而group_idle = 8。這樣空轉等待就是以cgroup為單位進行的,而不是以cfq_queue的進程為單位進行,以防止上述問題產生
low_latency :這個是用來開啟或關閉cfq的低延時(low latency)模式的開關。當這個開關打開時,cfq將會根據target_latency的參數設置來對每一個進程的分片時間(slice time)進行重新計算。這將有利於對吞吐量的公平(默認是對時間片分配的公平)。關閉這個參數(設置為0)將忽略target_latency的值。這將使系統中的進程完全按照時間片方式進行IO資源分配。這個開關默認是打開的

target_latency :當low_latency的值為開啟狀態時,cfq將根據這個值重新計算每個進程分配的IO時間片長度
quantum :這個參數用來設置每次從cfq_queue中處理多少個IO請求。在一個隊列處理事件周期中,超過這個數字的IO請求將不會被處理。這個參數只對同步的請求有效
slice_sync :當一個cfq_queue隊列被調度處理時,它可以被分配的處理總時間是通過這個值來作為一個計算參數指定的。公式為: time_slice = slice_sync + (slice_sync/5 * (4 - prio)) 這個參數對同步請求有效
slice_async :這個值跟上一個類似,區別是對非同步請求有效
slice_async_rq :這個參數用來限制在一個slice的時間范圍內,一個隊列最多可以處理的非同步請求個數。請求被處理的最大個數還跟相關進程被設置的io優先順序有關

通常在Linux上使用的IO介面是同步方式的,進程調用 write / read 之後會阻塞陷入到內核態,直到本次IO過程完成之後,才能繼續執行,下面介紹的非同步IO則沒有這種限制,但是當前Linux非同步IO尚未成熟

目前Linux aio還處於較不成熟的階段,只能在 O_DIRECT 方式下才能使用(glibc_aio),也就是無法使用默認的Page Cache機制

正常情況下,使用aio族介面的簡要方式如下:

io_uring 是 2019 年 5 月發布的 Linux 5.1 加入的一個重大特性 —— Linux 下的全新的非同步 I/O 支持,希望能徹底解決長期以來 Linux AIO 的各種不足
io_uring 實現非同步 I/O 的方式其實是一個生產者-消費者模型:

邏輯卷管理
RAID0
RAID1
RAID5(糾錯)
條帶化

Linux系統性能調整:IO過程
Linux的IO調度
一個IO的傳奇一生
理解inode
Linux 文件系統是怎麼工作的?
Linux中Buffer cache性能問題一探究竟
Asynchronous I/O and event notification on linux
AIO 的新歸宿:io_uring
Linux 文件 I/O 進化史(四):io_uring —— 全新的非同步 I/O

⑤ Linux非同步IO

Linux中最常用的IO模型是同步IO,在這個模型中,當請求發出之後,應用程序就會阻塞,直到請求滿足條件為止。這是一種很好的解決方案,調用應用程序在等待IO完成的時候不需要佔用CPU,但是在很多場景中,IO請求可能需要和CPU消耗交疊,以充分利用CPU和IO提高吞吐率。

下圖描繪了非同步IO的時序,應用程序發起IO操作後,直接開始執行,並不等待IO結束,它要麼過一段時間來查詢之前的IO請求完成情況,要麼IO請求完成了會自動被調用與IO完成綁定的回調函數。

Linux的AIO有多種實現,其中一種實現是在用戶空間的glibc庫中實現的,本質上是借用了多線程模型,用開啟的新的線程以同步的方式做IO,新的AIO輔助線程與發起AIO的線程以pthread_cond_signal()的形式進行線程間的同步,glibc的AIO主要包含以下函數:

1、aio_read()
aio_read()函數請求對一個有效的文件描述符進行非同步讀操作。這個文件描述符可以代表一個文件、套接字,甚至管道,aio_read()函數原型如下:

aio_read()函數在請求進行排隊之後就會立即返回(盡管讀操作並未完成),如果執行成功就返回0,如果出現錯誤就返回-1。參數aiocb(AIO I/O Control Block)結構體包含了傳輸的所有信息,以及為AIO操作準備的用戶空間緩沖區。在產生IO完成通知時,aiocb結構就被用來唯一標識所完成的IO操作。

2.aio_write()
aio_write()函數用來請求一個非同步寫操作。函數原型如下:

aio_write()函數會立即返回,並且它的請求以及被排隊(成功時返回值為0,失敗時返回值為-1)

3.aio_error()

aio_error()函數被用來確定請求的狀態,其原型如下:

該函數的返回:

4.aio_return()
非同步IO和同步阻塞IO方式之間有一個區別就是不能立即訪問函數的返回狀態,因為非同步IO沒有阻塞在read()調用上。在標準的同步阻塞read()調用中,返回狀態是在該函數返回時提供的。
但是在非同步IO中,我們要用aio_return()函數,原型如下:

只有在aio_error()調用確定請求已經完成(可能成功、也可能發生了錯誤)之後,才會調用這個函數,aio_return()的返回值就等價於同步情況中read()或者write系統調用的返回值。

5.aio_suspend()
用戶可以用該函數阻塞調用進程,直到非同步請求完成為止,調用者提供了一個aiocb引用列表,其中任何一個完成都會導致aio_suspend()返回。函數原型如下:

6.aio_cancel()
該函數允許用戶取消對某個文件描述符執行的一個或所以IO請求。

要取消一個請求,用戶需要提供文件描述符和aiocb指針,如果這個請求被成功取消了,那麼這個函數就會返回AIO_CANCELED。如果請求完成了,就會返回AIO_NOTCANCELED。

7.lio_listio()
lio_listio()函數可用於同時發起多個傳輸。這個函數非常重要,它使得用戶可以在一個系統調用中啟動大量的IO操作,原型如下:

mode參數可以是LIO_WAIT或者是LIO_NOWAIT。LIO_WAIT會阻塞這個調用,直到所有的IO都返回為止,若是LIO_NOWAIT模型,在IO操作完成排隊之後,該函數就會返回。list是一個aiocb的列表,最大元素的個數是由nent定義的。如果list的元素為null,lio_listio()會將其忽略。

⑥ Linux系統I/O模型及select、poll、epoll原理和應用

理解Linux的IO模型之前,首先要了解一些基本概念,才能理解這些IO模型設計的依據

操作系統使用虛擬內存來映射物理內存,對於32位的操作系統來說,虛擬地址空間為4G(2^32)。操作系統的核心是內核,為了保護用戶進程不能直接操作內核,保證內核安全,操作系統將虛擬地址空間劃分為內核空間和用戶空間。內核可以訪問全部的地址空間,擁有訪問底層硬體設備的許可權,普通的應用程序需要訪問硬體設備必須通過 系統調用 來實現。

對於Linux系統來說,將虛擬內存的最高1G位元組的空間作為內核空間僅供內核使用,低3G位元組的空間供用戶進程使用,稱為用戶空間。

又被稱為標准I/O,大多數文件系統的默認I/O都是緩存I/O。在Linux系統的緩存I/O機制中,操作系統會將I/O的數據緩存在頁緩存(內存)中,也就是數據先被拷貝到內核的緩沖區(內核地址空間),然後才會從內核緩沖區拷貝到應用程序的緩沖區(用戶地址空間)。

這種方式很明顯的缺點就是數據傳輸過程中需要再應用程序地址空間和內核空間進行多次數據拷貝操作,這些操作帶來的CPU以及內存的開銷是非常大的。

由於Linux系統採用的緩存I/O模式,對於一次I/O訪問,以讀操作舉例,數據先會被拷貝到內核緩沖區,然後才會從內核緩沖區拷貝到應用程序的緩存區,當一個read系統調用發生的時候,會經歷兩個階段:

正是因為這兩個狀態,Linux系統才產生了多種不同的網路I/O模式的方案

Linux系統默認情況下所有socke都是blocking的,一個讀操作流程如下:

以UDP socket為例,當用戶進程調用了recvfrom系統調用,如果數據還沒准備好,應用進程被阻塞,內核直到數據到來且將數據從內核緩沖區拷貝到了應用進程緩沖區,然後向用戶進程返回結果,用戶進程才解除block狀態,重新運行起來。

阻塞模行下只是阻塞了當前的應用進程,其他進程還可以執行,不消耗CPU時間,CPU的利用率較高。

Linux可以設置socket為非阻塞的,非阻塞模式下執行一個讀操作流程如下:

當用戶進程發出recvfrom系統調用時,如果kernel中的數據還沒准備好,recvfrom會立即返回一個error結果,不會阻塞用戶進程,用戶進程收到error時知道數據還沒准備好,過一會再調用recvfrom,直到kernel中的數據准備好了,內核就立即將數據拷貝到用戶內存然後返回ok,這個過程需要用戶進程去輪詢內核數據是否准備好。

非阻塞模型下由於要處理更多的系統調用,因此CPU利用率比較低。

應用進程使用sigaction系統調用,內核立即返回,等到kernel數據准備好時會給用戶進程發送一個信號,告訴用戶進程可以進行IO操作了,然後用戶進程再調用IO系統調用如recvfrom,將數據從內核緩沖區拷貝到應用進程。流程如下:

相比於輪詢的方式,不需要多次系統調用輪詢,信號驅動IO的CPU利用率更高。

非同步IO模型與其他模型最大的區別是,非同步IO在系統調用返回的時候所有操作都已經完成,應用進程既不需要等待數據准備,也不需要在數據到來後等待數據從內核緩沖區拷貝到用戶緩沖區,流程如下:

在數據拷貝完成後,kernel會給用戶進程發送一個信號告訴其read操作完成了。

是用select、poll等待數據,可以等待多個socket中的任一個變為可讀,這一過程會被阻塞,當某個套接字數據到來時返回,之後再用recvfrom系統調用把數據從內核緩存區復制到用戶進程,流程如下:

流程類似阻塞IO,甚至比阻塞IO更差,多使用了一個系統調用,但是IO多路復用最大的特點是讓單個進程能同時處理多個IO事件的能力,又被稱為事件驅動IO,相比於多線程模型,IO復用模型不需要線程的創建、切換、銷毀,系統開銷更小,適合高並發的場景。

select是IO多路復用模型的一種實現,當select函數返回後可以通過輪詢fdset來找到就緒的socket。

優點是幾乎所有平台都支持,缺點在於能夠監聽的fd數量有限,Linux系統上一般為1024,是寫死在宏定義中的,要修改需要重新編譯內核。而且每次都要把所有的fd在用戶空間和內核空間拷貝,這個操作是比較耗時的。

poll和select基本相同,不同的是poll沒有最大fd數量限制(實際也會受到物理資源的限制,因為系統的fd數量是有限的),而且提供了更多的時間類型。

總結:select和poll都需要在返回後通過輪詢的方式檢查就緒的socket,事實上同時連的大量socket在一個時刻只有很少的處於就緒狀態,因此隨著監視的描述符數量的變多,其性能也會逐漸下降。

epoll是select和poll的改進版本,更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的只需一次。

epoll_create()用來創建一個epoll句柄。
epoll_ctl() 用於向內核注冊新的描述符或者是改變某個文件描述符的狀態。已注冊的描述符在內核中會被維護在一棵紅黑樹上,通過回調函數內核會將 I/O 准備好的描述符加入到一個就緒鏈表中管理。
epoll_wait() 可以從就緒鏈表中得到事件完成的描述符,因此進程不需要通過輪詢來獲得事件完成的描述符。

當epoll_wait檢測到描述符IO事件發生並且通知給應用程序時,應用程序可以不立即處理該事件,下次調用epoll_wait還會再次通知該事件,支持block和nonblocking socket。

當epoll_wait檢測到描述符IO事件發生並且通知給應用程序時,應用程序需要立即處理該事件,如果不立即處理,下次調用epoll_wait不會再次通知該事件。

ET模式在很大程度上減少了epoll事件被重復觸發的次數,因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用nonblocking socket,以避免由於一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。

【segmentfault】 Linux IO模式及 select、poll、epoll詳解
【GitHub】 CyC2018/CS-Notes

⑦ 在linux系統中如何查看cpu和io

在 Linux 系統中,可以使用以下命令查看 CPU 信息:

  • top: 顯示系統進程的實時狀態

  • htop: 與 top 類似,但提供了更多的信息和更好的可視化

  • mpstat: 顯示多核 CPU 的狀態

  • lscpu: 顯示系統 CPU 的配置信息

  • 查看 IO 信息,可以使用以下命令:

  • iostat : 用於檢測磁碟I/O的使用狀況

  • vmstat : 用於檢測虛擬內存的使用狀況

  • mpstat : 用於檢測 CPU 和磁碟I/O的使用狀況

  • dstat : 用於檢測磁碟I/O,網路,CPU等系統資源的使用狀況

  • 需要注意的是這些命令需要安裝對應的工具包

⑧ linux中block IO,no-block IO,非同步IO,IO多路復用筆記

        現在操作系統都是採用虛擬存儲器,那麼對32位操作系統而言,它的定址空間(虛擬存儲空間)為4G(2的32次方)。操作系統的核心是內核,獨立於普通的應用程序,可以訪問受保護的內存空間,也有訪問底層硬體設備的所有許可權。 為了保證用戶進程不能直接操作內核(kernel),保證內核的安全,操心系統將虛擬空間劃分為兩部分,一部分為內核空間,一部分為用戶空間 。針對linux操作系統而言, 將最高的1G位元組(從虛擬地址0xC0000000到0xFFFFFFFF) ,供內核使用,稱為內核空間, 而將較低的3G位元組(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱為用戶空間。

        文件描述符(File descriptor)是計算機科學中的一個術語,是一個用於表述 指向文件的引用的抽象化概念 。文件描述符在形式上是一個非負整數。 實際上,它是一個索引值,指向內核為每一個進程所維護的該進程打開文件的記錄表 。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫往往會圍繞著文件描述符展開。但是文件描述符這一概念往往只適用於UNIX、Linux這樣的操作系統。

       剛才說了,對於一次IO訪問(以read舉例),數據會先被拷貝到操作系統內核的緩沖區中,然後才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。所以說,當一個read操作發生時,它會經歷兩個階段:

1、等待數據准備 (Waiting for the data to be ready)

2、將數據從內核拷貝到進程中 (Copying the data from the kernel to the process)

正式因為這兩個階段,linux系統產生了下面 五種網路模式 的方案。

阻塞 I/O(blocking IO)

非阻塞 I/O(nonblocking IO)

I/O 多路復用( IO multiplexing)

非同步 I/O(asynchronous IO)

信號驅動 I/O( signal driven IO)

註:由於signal driven IO在實際中並不常用,所以我這只提及剩下的四種IO Model。

阻塞 I/O(blocking IO)

在linux中,默認情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣:

        當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:准備數據(對於網路IO來說,很多時候數據在一開始還沒有到達。比如,還沒有收到一個完整的UDP包。這個時候kernel就要等待足夠的數據到來)。這個過程需要等待,也就是說數據被拷貝到操作系統內核的緩沖區中是需要一個過程的。而在用戶進程這邊,整個進程會被阻塞(當然,是進程自己選擇的阻塞)。當kernel一直等到數據准備好了,它就會將數據從kernel中拷貝到用戶內存,然後kernel返回結果,用戶進程才解除block的狀態,重新運行起來。

所以,blocking IO的特點就是在IO執行的兩個階段都被block了(內核阻塞讀取數據,內核將數據復制到應用戶態)。

非阻塞 I/O(nonblocking IO)

linux下,可以通過設置socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:

       當用戶進程發出read操作時,如果kernel中的數據還沒有準備好,那麼它並不會block用戶進程,而是立刻返回一個error。從用戶進程角度講 ,它發起一個read操作後,並不需要等待,而是馬上就得到了一個結果。用戶進程判斷結果是一個error時,它就知道數據還沒有準備好,於是它可以再次發送read操作。一旦kernel中的數據准備好了,並且又再次收到了用戶進程的system call,那麼它馬上就將數據拷貝到了用戶內存,然後返回。

所以,nonblocking IO的特點是用戶進程需要 不斷的主動詢問 kernel數據好了沒有( 內核讀取數據時,用戶態不需要阻塞,內核將數據復制到用戶態時,需要阻塞 )。

I/O 多路復用( IO multiplexing)

         IO multiplexing就是我們說的select,poll,epoll,有些地方也稱這種IO方式為event driven IO。select/epoll的好處就在於單個process就可以同時處理多個網路連接的IO。它的基本原理就是 select,poll,epoll這個function會不斷的輪詢所負責的所有socket ,當某個socket有數據到達了,就通知用戶進程。

        當用戶 進程調用了select , 那麼整個進程會被block ,而同時,kernel會「監視」所有 select負責的socket(一個管理多個socket連接),當任何一個socket中的數據准備好了,select就會返回 。這個時候用戶進程再調用read操作, 將數據從kernel拷貝到用戶進程 。

所以,I/O 多路復用的特點是通過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就可以返回。

這個圖和blocking IO的圖其實並沒有太大的不同,事實上,還更差一些。 因為這里需要使用兩個system call (select 和 recvfrom),而blocking IO只調用了一個system call (recvfrom) 。但是,用select的優勢在於它可以同時處理多個connection。

所以,如果處理的 連接數不是很高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大 。select/epoll的優勢並不是對於單個連接能處理得更快,而是在於能處理更多的連接。)

在IO multiplexing Model中,實際中,對於每一個socket,一般都設置成為non-blocking,但是,如上圖所示,整個用戶的process其實是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。

總結:IO多路復用其實也是阻塞的,阻塞的地方在用當有socket連接有數據以後, 會阻塞知道數據從內核復制到用戶態(第二步阻塞)。

非同步 I/O(asynchronous IO)

inux下的asynchronous IO其實用得很少。先看一下它的流程:

        用戶進程發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到一個asynchronous read之後,首先它會立刻返回,所以不會對用戶進程產生任何block。然後,kernel會等待數據准備完成,然後將數據拷貝到用戶內存,當這一切都完成之後,kernel會給用戶進程發送一個signal,告訴它read操作完成了。

總結:兩個階段都不需要用戶進程干涉,內核將數據准備好以後通知用戶態去讀取

總結

blocking和non-blocking的區別

調用blocking IO會一直block住對應的進程直到操作完成,而non-blocking IO在kernel還准備數據的情況下會立刻返回。

synchronous IO和asynchronous IO的區別

在說明synchronous IO和asynchronous IO的區別之前,需要先給出兩者的定義。POSIX的定義是這樣子的:

- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;

- An asynchronous I/O operation does not cause the requesting process to be blocked;

兩者的區別就在於synchronous IO做」IO operation」的時候會將process阻塞。按照這個定義,之前所述的 blocking IO,non-blocking IO,IO multiplexing都屬於synchronous IO 。

       有人會說,non-blocking IO並沒有被block啊。這里有個非常「狡猾」的地方,定義中所指的」IO operation」是指真實的IO操作,就是例子中的recvfrom這個system call。non-blocking IO在執行recvfrom這個system call的時候,如果kernel的數據沒有準備好,這時候不會block進程。但是, 當kernel中數據准備好的時候,recvfrom會將數據從kernel拷貝到用戶內存中,這個時候進程是被block了,在這段時間內,進程是被block的。

而asynchronous IO則不一樣,當進程發起IO 操作之後,就直接返回再也不理睬了,直到kernel發送一個信號,告訴進程說IO完成。在這整個過程中,進程完全沒有被block。

⑨ 面試 linux 文件系統怎樣io到底層

前言:本文主要講解LinuxIO調度層的三種模式:cfp、deadline和noop,並給出各自的優化和適用場景建議。IO調度發生在Linux內核的IO調度層。這個層次是針對Linux的整體IO層次體系來說的。從read()或者write()系統調用的角度來說,Linux整體IO體系可以分為七層,它們分別是:VFS層:虛擬文件系統層。由於內核要跟多種文件系統打交道,而每一種文件系統所實現的數據結構和相關方法都可能不盡相同,所以,內核抽象了這一層,專門用來適配各種文件系統,並對外提供統一操作介面。文件系統層:不同的文件系統實現自己的操作過程,提供自己特有的特徵,具體不多說了,大家願意的話自己去看代碼即可。頁緩存層:負責真對page的緩存。通用塊層:由於絕大多數情況的io操作是跟塊設備打交道,所以Linux在此提供了一個類似vfs層的塊設備操作抽象層。下層對接各種不同屬性的塊設備,對上提供統一的BlockIO請求標准。IO調度層:因為絕大多數的塊設備都是類似磁碟這樣的設備,所以有必要根據這類設備的特點以及應用的不同特點來設置一些不同的調度演算法和隊列。以便在不同的應用環境下有針對性的提高磁碟的讀寫效率,這里就是大名鼎鼎的Linux電梯所起作用的地方。針對機械硬碟的各種調度方法就是在這實現的。塊設備驅動層:驅動層對外提供相對比較高級的設備操作介面,往往是C語言的,而下層對接設備本身的操作方法和規范。塊設備層:這層就是具體的物理設備了,定義了各種真對設備操作方法和規范。有一個已經整理好的[LinuxIO結構圖],非常經典,一圖勝千言:我們今天要研究的內容主要在IO調度這一層。它要解決的核心問題是,如何提高塊設備IO的整體性能?這一層也主要是針對機械硬碟結構而設計的。眾所周知,機械硬碟的存儲介質是磁碟,磁頭在碟片上移動進行磁軌定址,行為類似播放一張唱片。這種結構的特點是,順序訪問時吞吐量較高,但是如果一旦對碟片有隨機訪問,那麼大量的時間都會浪費在磁頭的移動上,這時候就會導致每次IO的響應時間變長,極大的降低IO的響應速度。磁頭在碟片上尋道的操作,類似電梯調度,實際上在最開始的時期,Linux把這個演算法命名為Linux電梯演算法,即:如果在尋道的過程中,能把順序路過的相關磁軌的數據請求都「順便」處理掉,那麼就可以在比較小影響響應速度的前提下,提高整體IO的吞吐量。這就是我們為什麼要設計IO調度演算法的原因。目前在內核中默認開啟了三種演算法/模式:noop,cfq和deadline。嚴格算應該是兩種:因為第一種叫做noop,就是空操作調度演算法,也就是沒有任何調度操作,並不對io請求進行排序,僅僅做適當的io合並的一個fifo隊列。目前內核中默認的調度演算法應該是cfq,叫做完全公平隊列調度。這個調度演算法人如其名,它試圖給所有進程提供一個完全公平的IO操作環境。註:請大家一定記住這個詞語,cfq,完全公平隊列調度,不然下文就沒法看了。cfq為每個進程創建一個同步IO調度隊列,並默認以時間片和請求數限定的方式分配IO資源,以此保證每個進程的IO資源佔用是公平的,cfq還實現了針對進程級別的優先順序調度,這個我們後面會詳細解釋。查看和修改IO調度演算法的方法是:cfq是通用伺服器比較好的IO調度演算法選擇,對桌面用戶也是比較好的選擇。但是對於很多IO壓力較大的場景就並不是很適應,尤其是IO壓力集中在某些進程上的場景。因為這種場景我們需要的滿足某個或者某幾個進程的IO響應速度,而不是讓所有的進程公平的使用IO,比如資料庫應用。deadline調度(最終期限調度)就是更適合上述場景的解決方案。deadline實現了四個隊列:其中兩個分別處理正常read和write,按扇區號排序,進行正常io的合並處理以提高吞吐量。因為IO請求可能會集中在某些磁碟位置,這樣會導致新來的請求一直被合並,可能會有其他磁碟位置的io請求被餓死。另外兩個處理超時read和write的隊列,按請求創建時間排序,如果有超時的請求出現,就放進這兩個隊列,調度演算法保證超時(達到最終期限時間)的隊列中的請求會優先被處理,防止請求被餓死。不久前,內核還是默認標配四種演算法,還有一種叫做as的演算法(Anticipatoryscheler),預測調度演算法。一個高大上的名字,搞得我一度認為Linux內核都會算命了。結果發現,無非是在基於deadline演算法做io調度的之前等一小會時間,如果這段時間內有可以合並的io請求到來,就可以合並處理,提高deadline調度的在順序讀寫情況下的數據吞吐量。其實這根本不是啥預測,我覺得不如叫撞大運調度演算法,當然這種策略在某些特定場景差效果不錯。但是在大多數場景下,這個調度不僅沒有提高吞吐量,還降低了響應速度,所以內核乾脆把它從默認配置里刪除了。畢竟Linux的宗旨是實用,而我們也就不再這個調度演算法上多費口舌了。1、cfq:完全公平隊列調度cfq是內核默認選擇的IO調度隊列,它在桌面應用場景以及大多數常見應用場景下都是很好的選擇。如何實現一個所謂的完全公平隊列(CompletelyFairQueueing)?首先我們要理解所謂的公平是對誰的公平?從操作系統的角度來說,產生操作行為的主體都是進程,所以這里的公平是針對每個進程而言的,我們要試圖讓進程可以公平的佔用IO資源。那麼如何讓進程公平的佔用IO資源?我們需要先理解什麼是IO資源。當我們衡量一個IO資源的時候,一般喜歡用的是兩個單位,一個是數據讀寫的帶寬,另一個是數據讀寫的IOPS。帶寬就是以時間為單位的讀寫數據量,比如,100Mbyte/s。而IOPS是以時間為單位的讀寫次數。在不同的讀寫情境下,這兩個單位的表現可能不一樣,但是可以確定的是,兩個單位的任何一個達到了性能上限,都會成為IO的瓶頸。從機械硬碟的結構考慮,如果讀寫是順序讀寫,那麼IO的表現是可以通過比較少的IOPS達到較大的帶寬,因為可以合並很多IO,也可以通過預讀等方式加速數據讀取效率。當IO的表現是偏向於隨機讀寫的時候,那麼IOPS就會變得更大,IO的請求的合並可能性下降,當每次io請求數據越少的時候,帶寬表現就會越低。從這里我們可以理解,針對進程的IO資源的主要表現形式有兩個:進程在單位時間內提交的IO請求個數和進程佔用IO的帶寬。其實無論哪個,都是跟進程分配的IO處理時間長度緊密相關的。有時業務可以在較少IOPS的情況下佔用較大帶寬,另外一些則可能在較大IOPS的情況下佔用較少帶寬,所以對進程佔用IO的時間進行調度才是相對最公平的。即,我不管你是IOPS高還是帶寬佔用高,到了時間咱就換下一個進程處理,你愛咋樣咋樣。所以,cfq就是試圖給所有進程分配等同的塊設備使用的時間片,進程在時間片內,可以將產生的IO請求提交給塊設備進行處理,時間片結束,進程的請求將排進它自己的隊列,等待下次調度的時候進行處理。這就是cfq的基本原理。當然,現實生活中不可能有真正的「公平」,常見的應用場景下,我們很肯能需要人為的對進程的IO佔用進行人為指定優先順序,這就像對進程的CPU佔用設置優先順序的概念一樣。所以,除了針對時間片進行公平隊列調度外,cfq還提供了優先順序支持。每個進程都可以設置一個IO優先順序,cfq會根據這個優先順序的設置情況作為調度時的重要參考因素。優先順序首先分成三大類:RT、BE、IDLE,它們分別是實時(RealTime)、最佳效果(BestTry)和閑置(Idle)三個類別,對每個類別的IO,cfq都使用不同的策略進行處理。另外,RT和BE類別中,分別又再劃分了8個子優先順序實現更細節的QOS需求,而IDLE只有一個子優先順序。另外,我們都知道內核默認對存儲的讀寫都是經過緩存(buffer/cache)的,在這種情況下,cfq是無法區分當前處理的請求是來自哪一個進程的。只有在進程使用同步方式(syncread或者syncwirte)或者直接IO(DirectIO)方式進行讀寫的時候,cfq才能區分出IO請求來自哪個進程。所以,除了針對每個進程實現的IO隊列以外,還實現了一個公共的隊列用來處理非同步請求。當前內核已經實現了針對IO資源的cgroup資源隔離,所以在以上體系的基礎上,cfq也實現了針對cgroup的調度支持。總的來說,cfq用了一系列的數據結構實現了以上所有復雜功能的支持,大家可以通過源代碼看到其相關實現,文件在源代碼目錄下的block/cfq-iosched.c。1.1cfq設計原理在此,我們對整體數據結構做一個簡要描述:首先,cfq通過一個叫做cfq_data的數據結構維護了整個調度器流程。在一個支持了cgroup功能的cfq中,全部進程被分成了若干個contralgroup進行管理。每個cgroup在cfq中都有一個cfq_group的結構進行描述,所有的cgroup都被作為一個調度對象放進一個紅黑樹中,並以vdisktime為key進行排序。vdisktime這個時間紀錄的是當前cgroup所佔用的io時間,每次對cgroup進行調度時,總是通過紅黑樹選擇當前vdisktime時間最少的cgroup進行處理,以保證所有cgroups之間的IO資源佔用「公平」。當然我們知道,cgroup是可以對blkio進行資源比例分配的,其作用原理就是,分配比例大的cgroup佔用vdisktime時間增長較慢,分配比例小的vdisktime時間增長較快,快慢與分配比例成正比。這樣就做到了不同的cgroup分配的IO比例不一樣,並且在cfq的角度看來依然是「公平「的。選擇好了需要處理的cgroup(cfq_group)之後,調度器需要決策選擇下一步的service_tree。service_tree這個數據結構對應的都是一系列的紅黑樹,主要目的是用來實現請求優先順序分類的,就是RT、BE、IDLE的分類。每一個cfq_group都維護了7個service_trees,其定義如下:其中service_tree_idle就是用來給IDLE類型的請求進行排隊用的紅黑樹。而上面二維數組,首先第一個維度針對RT和BE分別各實現了一個數組,每一個數組中都維護了三個紅黑樹,分別對應三種不同子類型的請求,分別是:SYNC、SYNC_NOIDLE以及ASYNC。我們可以認為SYNC相當於SYNC_IDLE並與SYNC_NOIDLE對應。idling是cfq在設計上為了盡量合並連續的IO請求以達到提高吞吐量的目的而加入的機制,我們可以理解為是一種「空轉」等待機制。空轉是指,當一個隊列處理一個請求結束後,會在發生調度之前空等一小會時間,如果下一個請求到來,則可以減少磁頭定址,繼續處理順序的IO請求。為了實現這個功能,cfq在service_tree這層數據結構這實現了SYNC隊列,如果請求是同步順序請求,就入隊這個servicetree,如果請求是同步隨機請求,則入隊SYNC_NOIDLE隊列,以判斷下一個請求是否是順序請求。所有的非同步寫操作請求將入隊ASYNC的servicetree,並且針對這個隊列沒有空轉等待機制。此外,cfq還對SSD這樣的硬碟有特殊調整,當cfq發現存儲設備是一個ssd硬碟這樣的隊列深度更大的設備時,所有針對單獨隊列的空轉都將不生效,所有的IO請求都將入隊SYNC_NOIDLE這個servicetree。每一個servicetree都對應了若干個cfq_queue隊列,每個cfq_queue隊列對應一個進程,這個我們後續再詳細說明。cfq_group還維護了一個在cgroup內部所有進程公用的非同步IO請求隊列,其結構如下:非同步請求也分成了RT、BE、IDLE這三類進行處理,每一類對應一個cfq_queue進行排隊。BE和RT也實現了優先順序的支持,每一個類型有IOPRIO_BE_NR這么多個優先順序,這個值定義為8,數組下標為0-7。我們目前分析的內核代碼版本為Linux4.4,可以看出,從cfq的角度來說,已經可以實現非同步IO的cgroup支持了,我們需要定義一下這里所謂非同步IO的含義,它僅僅表示從內存的buffer/cache中的數據同步到硬碟的IO請求,而不是aio(man7aio)或者linux的native非同步io以及lio機制,實際上這些所謂的「非同步」IO機制,在內核中都是同步實現的(本質上馮諾伊曼計算機沒有真正的「非同步」機制)。我們在上面已經說明過,由於進程正常情況下都是將數據先寫入buffer/cache,所以這種非同步IO都是統一由cfq_group中的async請求隊列處理的。那麼為什麼在上面的service_tree中還要實現和一個ASYNC的類型呢?這當然是為了支持區分進程的非同步IO並使之可以「完全公平」做准備嘍。實際上在最新的cgroupv2的blkio體系中,內核已經支持了針對bufferIO的cgroup限速支持,而以上這些可能容易混淆的一堆類型,都是在新的體系下需要用到的類型標記。新體系的復雜度更高了,功能也更加強大,但是大家先不要著急,正式的cgroupv2體系,在Linux4.5發布的時候會正式跟大家見面。我們繼續選擇service_tree的過程,三種優先順序類型的service_tree的選擇就是根據類型的優先順序來做選擇的,RT優先順序最高,BE其次,IDLE最低。就是說,RT里有,就會一直處理RT,RT沒了再處理BE。每個service_tree對應一個元素為cfq_queue排隊的紅黑樹,而每個cfq_queue就是內核為進程(線程)創建的請求隊列。每一個cfq_queue都會維護一個rb_key的變數,這個變數實際上就是這個隊列的IO服務時間(servicetime)。這里還是通過紅黑樹找到servicetime時間最短的那個cfq_queue進行服務,以保證「完全公平」。選擇好了cfq_queue之後,就要開始處理這個隊列里的IO請求了。這里的調度方式基本跟deadline類似。cfq_queue會對進入隊列的每一個請求進行兩次入隊,一個放進fifo中,另一個放進按訪問扇區順序作為key的紅黑樹中。默認從紅黑樹中取請求進行處理,當請求的延時時間達到deadline時,就從紅黑樹中取等待時間最長的進行處理,以保證請求不被餓死。這就是整個cfq的調度流程,當然其中還有很多細枝末節沒有交代,比如合並處理以及順序處理等等。1.2cfq的參數調整理解整個調度流程有助於我們決策如何調整cfq的相關參數。所有cfq的可調參數都可以在/sys/class/block/sda/queue/iosched/目錄下找到,當然,在你的系統上,請將sda替換為相應的磁碟名稱。我們來看一下都有什麼:這些參數部分是跟機械硬碟磁頭尋道方式有關的,如果其說明你看不懂,請先補充相關知識:back_seek_max:磁頭可以向後定址的最大范圍,默認值為16M。back_seek_penalty:向後定址的懲罰系數。這個值是跟向前定址進行比較的。以上兩個是為了防止磁頭尋道發生抖動而導致定址過慢而設置的。基本思路是這樣,一個io請求到來的時候,cfq會根據其定址位置預估一下其磁頭尋道成本。設置一個最大值back_seek_max,對於請求所訪問的扇區號在磁頭後方的請求,只要定址范圍沒有超過這個值,cfq會像向前定址的請求一樣處理它。再設置一個評估成本的系數back_seek_penalty,相對於磁頭向前定址,向後定址的距離為1/2(1/back_seek_penalty)時,cfq認為這兩個請求定址的代價是相同。這兩個參數實際上是cfq判斷請求合並處理的條件限制,凡事復合這個條件的請求,都會盡量在本次請求處理的時候一起合並處理。fifo_expire_async:設置非同步請求的超時時間。同步請求和非同步請求是區分不同隊列處理的,cfq在調度的時候一般情況都會優先處理同步請求,之後再處理非同步請求,除非非同步請求符合上述合並處理的條件限制范圍內。當本進程的隊列被調度時,cfq會優先檢查是否有非同步請求超時,就是超過fifo_expire_async參數的限制。如果有,則優先發送一個超時的請求,其餘請求仍然按照優先順序以及扇區編號大小來處理。fifo_expire_sync:這個參數跟上面的類似,區別是用來設置同步請求的超時時間。slice_idle:參數設置了一個等待時間。這讓cfq在切換cfq_queue或servicetree的時候等待一段時間,目的是提高機械硬碟的吞吐量。一般情況下,來自同一個cfq_queue或者servicetree的IO請求的定址局部性更好,所以這樣可以減少磁碟的定址次數。這個值在機械硬碟上默認為非零。當然在固態硬碟或者硬RAID設備上設置這個值為非零會降低存儲的效率,因為固態硬碟沒有磁頭定址這個概念,所以在這樣的設備上應該設置為0,關閉此功能。group_idle:這個參數也跟上一個參數類似,區別是當cfq要切換cfq_group的時候會等待一段時間。在cgroup的場景下,如果我們沿用slice_idle的方式,那麼空轉等待可能會在cgroup組內每個進程的cfq_queue切換時發生。這樣會如果這個進程一直有請求要處理的話,那麼直到這個cgroup的配額被耗盡,同組中的其它進程也可能無法被調度到。這樣會導致同組中的其它進程餓死而產生IO性能瓶頸。在這種情況下,我們可以將slice_idle=0而group_idle=8。這樣空轉等待就是以cgroup為單位進行的,而不是以cfq_queue的進程為單位進行,以防止上述問題產生。low_latency:這個是用來開啟或關閉cfq的低延時(lowlatency)模式的開關。當這個開關打開時,cfq將會根據target_latency的參數設置來對每一個進程的分片時間(slicetime)進行重新計算。這將有利於對吞吐量的公平(默認是對時間片分配的公平)。關閉這個參數(設置為0)將忽略target_latency的值。這將使系統中的進程完全按照時間片方式進行IO資源分配。這個開關默認是打開的。我們已經知道cfq設計上有「空轉」(idling)這個概念,目的是為了可以讓連續的讀寫操作盡可能多的合並處理,減少磁頭的定址操作以便增大吞吐量。如果有進程總是很快的進行順序讀寫,那麼它將因為cfq的空轉等待命中率很高而導致其它需要處理IO的進程響應速度下降,如果另一個需要調度的進程不會發出大量順序IO行為的話,系統中不同進程IO吞吐量的表現就會很不均衡。就比如,系統內存的cache中有很多臟頁要寫回時,桌面又要打開一個瀏覽器進行操作,這時臟頁寫回的後台行為就很可能會大量命中空轉時間,而導致瀏覽器的小量IO一直等待,讓用戶感覺瀏覽器運行響應速度變慢。這個low_latency主要是對這種情況進行優化的選項,當其打開時,系統會根據target_latency的配置對因為命中空轉而大量佔用IO吞吐量的進程進行限制,以達到不同進程IO佔用的吞吐量的相對均衡。這個開關比較合適在類似桌面應用的場景下打開。target_latency:當low_latency的值為開啟狀態時,cfq將根據這個值重新計算每個進程分配的IO時間片長度。quantum:這個參數用來設置每次從cfq_queue中處理多少個IO請求。在一個隊列處理事件周期中,超過這個數字的IO請求將不會被處理。這個參數只對同步的請求有效。slice_sync:當一個cfq_queue隊列被調度處理時,它可以被分配的處理總時間是通過這個值來作為一個計算參數指定的。公式為:time_slice=slice_sync+(slice_sync/5*(4-prio))。這個參數對同步請求有效。slice_async:這個值跟上一個類似,區別是對非同步請求有效。slice_async_rq:這個參數用來限制在一個slice的時間范圍內,一個隊列最多可以處理的非同步請求個數。請求被處理的最大個數還跟相關進程被設置的io優先順序有關。1.3cfq的IOPS模式我們已經知道,默認情況下cfq是以時間片方式支持的帶優先順序的調度來保證IO資源佔用的公平。高優先順序的進程將得到的時間片長度,而低優先順序的進程時間片相對較小。當我們的存儲是一個高速並且支持NCQ(原生指令隊列)的設備的時候,我們最好可以讓其可以從多個cfq隊列中處理多路的請求,以便提升NCQ的利用率。此時使用時間片的分配方式分配資源就顯得不合時宜了,因為基於時間片的分配,同一時刻最多能處理的請求隊列只有一個。這時,我們需要切換cfq的模式為IOPS模式。切換方式很簡單,就是將slice_idle=0即可。內核會自動檢測你的存儲設備是否支持NCQ,如果支持的話cfq會自動切換為IOPS模式。另外,在默認的基於優先順序的時間片方式下,我們可以使用ionice命令來調整進程的IO優先順序。進程默認分配的IO優先順序是根據進程的nice值計算而來的,計算方法可以在manionice中看到,這里不再廢話。2、deadline:最終期限調度deadline調度演算法相對cfq要簡單很多。其設計目標是:在保證請求按照設備扇區的順序進行訪問的同時,兼顧其它請求不被餓死,要在一個最終期限前被調度到。我們知道磁頭對磁碟的尋道是可以進行順序訪問和隨機訪問的,因為尋道延時時間的關系,順序訪問時IO的吞吐量更大,隨機訪問的吞吐量小。如果我們想為一個機械硬碟進行吞吐量優化的話,那麼就可以讓調度器按照盡量復合順序訪問的IO請求進行排序,之後請求以這樣的順序發送給硬碟,就可以使IO的吞吐量更大。但是這樣做也有另一個問題,就是如果此時出現了一個請求,它要訪問的磁軌離目前磁頭所在磁軌很遠,應用的請求又大量集中在目前磁軌附近。導致大量請求一直會被合並和插隊處理,而那個要訪問比較遠磁軌的請求將因為一直不能被調度而餓死。deadline就是這樣一種調度器,能在保證IO最大吞吐量的情況下,盡量使遠端請求在一個期限內被調度而不被餓死的調度器。

⑩ Linux磁碟IO流程

文件IO的分層設計

先看圖:

malloc的buf對應application buffer,用戶空間;

fwrite是系統提供的最上層介面,也是最常用的介面。它在用戶進程空間開辟一個CLib buffer,將多次小數據量相鄰寫操作(application buffer)先緩存起來,合並,最終調用write函數一次性寫入(或者將大塊數據分解多次write調用);

write函數通過調用系統調用介面,將數據從應用層到內核層,所以write會觸發內核態/用戶態切換。當數據到達page cache後,內核並不會立即把數據往下傳遞。而是返回用戶空間。數據什麼時候寫入硬碟,有內核IO調度決定,所以write是一個非同步調用;

read調用是先檢查page cache裡面是否有數據,如果有,就取出來返回用戶,如果沒有,就同步傳遞下去並等待有數據,再返回用戶,所以read是一個同步過程;

fclose隱含fflush函數,fflush只負責把數據從Clibbuffer拷貝到pagecache中返回,並沒有刷新到磁碟上,刷新到磁碟上可以使用fsync函數;

即便fsync仍有可能沒寫到磁碟上,一是磁碟有緩存,二是即便關閉緩存也可能為了跑分沒有真正關閉;

** 一致性
fwrite使用用戶進程私有空間,多線程必然需要做同步。write如果寫大小小於PIPE_BUF,是原子操作。根據已知信息,內核所做僅限於此,如果兩個進程同時寫文件,可能出現錯亂,需要實測。

** 安全性
從前面的分層設計來看,使用fsync函數可以最大限度保障安全寫入,但仍然沒有絕對的安全性。

另外一張圖

熱點內容
androidtypeface 發布:2025-07-30 00:22:23 瀏覽:212
汽輪壓縮機 發布:2025-07-30 00:14:25 瀏覽:381
安卓新建文件夾 發布:2025-07-30 00:05:06 瀏覽:535
我的存儲內存 發布:2025-07-30 00:05:04 瀏覽:687
主機上傳速度慢 發布:2025-07-30 00:00:05 瀏覽:379
javalist的排序 發布:2025-07-29 23:45:47 瀏覽:693
c語言字元占幾個位元組 發布:2025-07-29 23:34:39 瀏覽:304
阿里雲訪問慢 發布:2025-07-29 23:24:53 瀏覽:131
壓縮機能量調節 發布:2025-07-29 23:11:46 瀏覽:655
ftp上傳文件資料庫 發布:2025-07-29 23:02:59 瀏覽:593