淺談 Linux IO

來源于:360云計算
1
前言
Linux IO是文件存儲的基礎。本文參考了網(wǎng)上博主的一些文章,主要總結了LinuxIO的基礎知識。
2
linux IO棧
Linux文件IO采用分層的設計。分層有兩個好處:
架構清晰;
功能解耦;

Linux文件IO的時候,從通用性和性能的角度考慮,采用了一個折中的方案來滿足我們日常寫磁盤。
例如:
void foo() {char *buf = malloc(MAX_SIZE);strncpy(buf, src, MAX_SIZE);fwrite(buf, MAX_SIZE, 1, fp);fclose(fp);}
上面代碼的說明如下:
malloc的buf對應圖中的application buffer。調用fwrite之后,操作系統(tǒng)將數(shù)據(jù)從application buffer拷貝到libc buffer,即c庫標準IO buffer。fwrite 返回后,數(shù)據(jù)還保存在libc buffer,如果這個時候進程退出,這些數(shù)據(jù)將會丟失,沒有寫到磁盤上。
當調用fclose的時候,fclose只會刷新libc buffer到page cache,如果確保數(shù)據(jù)寫到磁盤,kernel buffer也必須flush。例如使用sync,fsync。除了fclose方法外,還有一個主動刷新接口fflush函數(shù),但是fflush函數(shù)只是將數(shù)據(jù)從libc buffer拷貝到page cache,并沒有刷新到磁盤上,從page cache刷新到磁盤上,可以通過調用fsync完成。
sync會告訴OS,你要將這些數(shù)據(jù)刷新到磁盤上,但并不能真正保證確實已經寫到磁盤上了。屬于盡力而為的操作。( According to the standard specification (e.g., POSIX.1-2001), sync() schedules the writes, but may return before the actual writing is done. )sync會將page cache數(shù)據(jù)送到 disk cache層,至于什么時候寫入物理磁盤介質上,則由磁盤控制器自己決定。
從上面可以看到,一個常用的fwrite執(zhí)行過程,需要經歷多次數(shù)據(jù)拷貝才能最終到達磁盤。
如果我們不想通過fwrite+fflush這種方式,而是想直接寫到page cache。這就是我們linux常用的系統(tǒng)調用read/write函數(shù)。調用write函數(shù)時,實際上是直接通過系統(tǒng)調用將數(shù)據(jù)從application buffer拷貝到page cache中。但是我們知道,系統(tǒng)調用read/write會觸發(fā)用戶態(tài)到內核態(tài)的轉換這將會一定性能消耗。
如果我們想繞過page cache,直接將數(shù)據(jù)送到磁盤設備上怎么辦?可以通過open的時候攜帶O_DIRECT屬性。這時write該文件,就是直接寫到設備上。
如果我們想直接寫到磁盤扇區(qū)有沒有辦法?這就是RAW設備寫,繞開了文件系統(tǒng),直接寫扇區(qū),例如:fdisk,dd之類的操作就是。
3
IO調用鏈
fwrite是系統(tǒng)提供的最上層接口,也是最常用的接口。它在用戶進程空間開辟了一個buffer,將多次小數(shù)據(jù)量相鄰寫操作先緩存起來,然后合并,最終調用write系統(tǒng)調用一次性寫入(或者將大塊數(shù)據(jù)分解多次write調用)。
write函數(shù)通過調用系統(tǒng)調用接口,將數(shù)據(jù)從應用層拷貝到內核,因此write會觸發(fā)用戶態(tài)到內核態(tài)的切換。當數(shù)據(jù)到達page cache后,內核并不會立即將數(shù)據(jù)往下傳遞,而是返回用戶空間。數(shù)據(jù)什么時候寫入磁盤,是由內核IO調度決定,所以write是個異步調用。這一點和read不同,read調用先檢查page cache里面是否有數(shù)據(jù),如果有,就取回來并返回給用戶,如果沒有,就同步等待下去并等待有數(shù)據(jù)再返回給用戶。所以read是個同步過程。如果想把write的異步過程改成同步過程,可以將open文件的時候,攜帶O_SYNC。
數(shù)據(jù)到了page cache之后,內核有pdflush線程不停的檢測臟頁,判斷是否要寫回到磁盤中。然后把需要寫回的頁提交到IO隊列(即:IO調度隊列),由IO調用隊列的調度策略決定何時寫回。
4
IO調度層
加入IO調度隊列的任務并不一定會立即執(zhí)行,調度層會從全局出發(fā),盡量讓整體磁盤IO性能最大化。大致的工作方式是讓磁頭類似電梯那樣工作,先往一個方向走,走到盡頭再回來,這樣磁盤效率會比較高;磁盤是單向旋轉的,不會反復逆時針轉動,因為磁頭尋道時間比較耗時。
內核中有多種IO調度算法,例如:noop,deadline和cfg,在你機器上,通過dmesg | grep -i scheduler 命令可以查看你的linux支持的算法。當磁盤是SSD時,采用的是隨機讀寫,并沒有磁道、磁頭,應用于傳統(tǒng)機械磁盤的調度算法反而不適用。調度算法中的noop算法,適合配置SSD硬盤。
任務從IO隊列出來后,就到了驅動層,驅動層通過DMA,將數(shù)據(jù)寫入磁盤cache。
至于磁盤cache何時寫入磁盤介質,這是由磁盤控制器自己決定。如果想確認要寫到磁盤介質上,就調用fsync函數(shù)。
5
一致性和安全性
5.1 安全性
從上可以看到,數(shù)據(jù)沒有到達磁盤介質之前,可能處于不同的物理內存cache中,那么這個時候如果出現(xiàn)進程退出,內核掛掉,物理機器掉電的情況,數(shù)據(jù)會丟失嗎?
當進程退出后:在application buffer或者libc buffer的數(shù)據(jù)會丟失。如果數(shù)據(jù)到了page cache,此時進程退出,即使數(shù)據(jù)還沒有到達硬盤,數(shù)據(jù)也不會丟失。
當內核掛掉:只要數(shù)據(jù)還沒有到disk cache,都將會丟失。
物理機掉電:此時所有數(shù)據(jù)都會丟失。
5.2?一致性

Q:同一個進程A中,多次open同一個文件,然后write數(shù)據(jù)會怎么樣?
fd1 = open("file", O_RDWR|O_TRUNC);fd2 = open("file", O_RDWR|O_TRUNC);while (1) {write(fd1, "hello \n", 6);write(fd2, "world \n", 6);}
A:先寫的數(shù)據(jù)是會被覆蓋的。原因在于同一進程中不同的文件描述符(fd),各自對應一個獨立的打開文件表,在打開文件表中有屬于自己的文件位移量,開始都是0,然后各自從0開始寫,每寫一個字節(jié)向后移動一個字節(jié),寫的位置是重疊的,因此會覆蓋。
如何解決被覆蓋的問題呢?
必須為每個open指定O_APPEND。文件長度是共享的,當文件被寫入數(shù)據(jù)后,文件長度就會被更新,指定O_APPEND之后,使用不同fd寫數(shù)據(jù)時,都會使用文件長度更新自己的文件位移量,保證每次都是在文件的最末尾寫數(shù)據(jù),這樣就不會出現(xiàn)覆蓋。
這里補充一點:同一個進程中多次open同一個文件時,文件描述符是不同的,在同一進程中某個文件描述符被占用,在close之前,是不會被再次分配。
Q:兩個進程A和進程B,open同一個文件,然后write數(shù)據(jù)會怎么樣?
A:先寫的數(shù)據(jù)是會被覆蓋的。覆蓋的原因也是各自有獨立的文件位移量。同樣指定O_APPEND,使用文件長度更新文件位移量,保證各自操作時,都在文件尾部操作,不會出現(xiàn)相互覆蓋的情況。
這里補充一點:不同進程打開同一個文件時,各自使用的fd可能是相等的,之所以相同,是因為不同進程有自己單獨的文件描述符池,默認是0~1023,各自分配各自的,是有可能分配到相等的fd
Q:A進程寫,B進程讀取會寫臟嗎?
A:會的。讀取進程對文件內容變化毫無感知,只是按部就班的讀取,直到文件結束EOF。
5.3?讀文件過程

Linux read文件的流程大致如下:
lib中的read函數(shù)首先進入系統(tǒng)調用sys_read;
接著sys_read再進入VFS中的vfs_read、generic_file_read等函數(shù);
接著VFS中的generic_file_read會判斷是否緩存命中,如果命中則返回;
如果沒有命中,內核在page cache中分配一個新頁框,發(fā)出缺頁中斷;
內核向通用塊層發(fā)起IO請求,塊設備屏蔽disk、U盤等差異;
通用塊層將bio代表的IO請求發(fā)送到IO請求隊列中;
IO調度層通過電梯算法來調度隊列中的請求;
驅動程序向磁盤控制器發(fā)出讀取命令控制,DMA方式直接填充到page cache中的新頁框;
控制器發(fā)出中斷通知;
內核將用戶需要的數(shù)據(jù)填充到用戶內存中;
然后你的進程被喚醒;
6
性能問題
磁盤的尋道時間時相當?shù)穆骄鶎さ来蟾旁?0ms,也就是每秒只能100-200次尋道。
磁盤轉速也是影響性能的關鍵,如果是15000rpm,大概每秒500轉。一般情況下,盤片轉太快,磁頭跟不上,所以需要多轉幾圈才能完全讀出磁道內容。
機械硬盤順序寫?0~30MB左右,順序讀取速率一般?0~50MB左右,性能好的可以達到100多MB;SSD讀取達到0~400MB左右。
相關參考文章
http://blog.chinaunix.net/uid-27105712-id-3270102.html?page=2
https://zhuanlan.zhihu.com/p/138371910
https://meik2333.com/posts/linux-many-proc-write-file/
https://blog.csdn.net/qq_43648751/article/details/104151401
關注「開源Linux」加星標,提升IT技能

