自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Read 文件一個(gè)字節(jié)實(shí)際會(huì)發(fā)生多大的磁盤IO?

存儲(chǔ) 存儲(chǔ)設(shè)備
在日常開(kāi)發(fā)中一些看似司空見(jiàn)慣的問(wèn)題上,我覺(jué)得可能大多數(shù)人其實(shí)并沒(méi)有真正理解,或者理解的不夠透徹。

[[392991]]

大家好,我是飛哥!

在日常開(kāi)發(fā)中一些看似司空見(jiàn)慣的問(wèn)題上,我覺(jué)得可能大多數(shù)人其實(shí)并沒(méi)有真正理解,或者理解的不夠透徹。不信我們來(lái)看以下一段簡(jiǎn)單的讀取文件的代碼:

上圖中的代碼僅僅只是對(duì)某個(gè)文件讀取了一個(gè)字節(jié),基于這個(gè)代碼片段我們來(lái)思考:

1、讀取文件 1 個(gè)字節(jié)是否會(huì)導(dǎo)致磁盤 IO ?

2、如果發(fā)生了磁盤 IO,那發(fā)生的是多大的 IO 呢?

大家平時(shí)用的各種語(yǔ)言 C++、PHP、Java、Go 啥的封裝層次都比較高,把很多細(xì)節(jié)都給屏蔽的比較徹底。如果想把上面的問(wèn)題搞清楚,需要剖開(kāi) Linux 的內(nèi)部來(lái)看 Linux 的 IO 棧。

一、大話 Linux IO 棧

廢話不多說(shuō),我畫(huà)了一個(gè) Linux IO 棧的簡(jiǎn)化版本。

通過(guò) IO ??梢钥吹剑覀?cè)趹?yīng)用層簡(jiǎn)單的一次 read 而已,內(nèi)核就需要 IO 引擎、VFS、PageCache、通用塊管理層、IO 調(diào)度層等許多個(gè)組件來(lái)進(jìn)行復(fù)雜配合才能完成。

那這些組件都是干啥的呢?我們挨個(gè)簡(jiǎn)單過(guò)一遍。不想看這個(gè)的同學(xué)可以直接跳到第二節(jié)的讀文件讀過(guò)程。

1.1 IO 引擎

開(kāi)發(fā)同學(xué)想要讀寫(xiě)文件的話,在 lib 庫(kù)層有很多套函數(shù)可以選擇,比如 read & write,pread & pwrite。這事實(shí)上就是在選擇 Linux 提供的 IO 引擎。

常見(jiàn)的 IO 引擎種類如下:

我們開(kāi)篇中代碼片用的 read 函數(shù)就屬于 sync 引擎。IO 引擎仍然處于上層,它需要內(nèi)核層的提供的系統(tǒng)調(diào)用、VFS、通用塊層等更底層組件的支持才能實(shí)現(xiàn)。

接著讓我們繼續(xù)深入到內(nèi)核,來(lái)介紹各個(gè)內(nèi)核組件。

1.2 系統(tǒng)調(diào)用

當(dāng)進(jìn)入到系統(tǒng)調(diào)用以后,也就進(jìn)入到了內(nèi)核層。

系統(tǒng)調(diào)用將內(nèi)核中其它組件的功能進(jìn)行封裝,然后通過(guò)接口的形式暴露給用戶進(jìn)程來(lái)訪問(wèn)。

對(duì)于我們的讀取文件的需求,系統(tǒng)調(diào)用需要依賴 VFS 內(nèi)核組件。

1.3 VFS 虛擬文件系統(tǒng)

VFS 的思想就是在 Linux 上抽象一個(gè)通用的文件系統(tǒng)模型,對(duì)我們開(kāi)發(fā)人員或者是用戶提供一組通用的接口,讓我們不用 care 具體文件系統(tǒng)的實(shí)現(xiàn)。VFS 提供的核心數(shù)據(jù)結(jié)構(gòu)有四個(gè),它們定義在內(nèi)核源代碼的 include/linux/fs.h 和 include/linux/dcache.h 中。

superblock:Linux 用來(lái)標(biāo)注具體已安裝的文件系統(tǒng)的有關(guān)信息。

inode:Linux 中的每一個(gè)文件/目錄都有一個(gè) inode,記錄其權(quán)限、修改時(shí)間等信息。

desty:目錄項(xiàng),是路徑中的一部分,所有的目錄項(xiàng)對(duì)象串起來(lái)就是一棵 Linux 下的目錄樹(shù)。

file:文件對(duì)象,用來(lái)和打開(kāi)它的進(jìn)程進(jìn)行交互。

圍繞這這四個(gè)核心數(shù)據(jù)結(jié)構(gòu),VFS 也都定義了一系列的操作方法。比如,inode 的操作方法定義 inode_operations,在它的里面定義了我們非常熟悉的 mkdir 和 rename 等。對(duì)于 file 對(duì)象,定義了對(duì)應(yīng)的操作方法 file_operations ,如下:

  1. // include/linux/fs.h 
  2. struct file { 
  3.     ...... 
  4.     const struct file_operations    *f_op 
  5. struct file_operations { 
  6.     ...... 
  7.     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 
  8.     ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 
  9.             ...... 
  10.     int (*mmap) (struct file *, struct vm_area_struct *); 
  11.     int (*open) (struct inode *, struct file *); 
  12.     int (*flush) (struct file *, fl_owner_t id); 

注意 VFS 是抽象的,所以它的 file_operations 里定義的 read、write 都只是函數(shù)指針, 實(shí)際中需要具體的文件系統(tǒng)來(lái)實(shí)現(xiàn),例如 ext4 等等。

1.4 Page Cache

Page Cache。它的中文譯名叫頁(yè)高速緩存。它是 Linux 內(nèi)核使用的主要磁盤高速緩存,是一個(gè)純內(nèi)存的工作組件。Linux 內(nèi)核使用搜索樹(shù)來(lái)高效管理大量的頁(yè)面。

有了它,Linux 就可以把一些磁盤上的文件數(shù)據(jù)保留在內(nèi)存中,然后來(lái)給訪問(wèn)相對(duì)比較慢的磁盤來(lái)進(jìn)行訪問(wèn)加速。

當(dāng)用戶要訪問(wèn)的文件的時(shí)候,如果要訪問(wèn)的文件 block 正好存在于 Page Cache 內(nèi),那么 Page Cache 組件直接把數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶進(jìn)程的內(nèi)存中就可以了。如果不存在,那么會(huì)申請(qǐng)一個(gè)新頁(yè),發(fā)出缺頁(yè)中斷,然后用磁盤讀取到的 block 內(nèi)容來(lái)填充它 ,下次直接使用。

看到這里,開(kāi)篇的問(wèn)題可能你就明白一半了,如果你要訪問(wèn)的文件近期訪問(wèn)過(guò),那么 Linux 大概率就是從 Page cache 內(nèi)存中的拷貝給你就完事,并不會(huì)有實(shí)際的磁盤 IO 發(fā)生。

不過(guò)有一種情況下,Pagecache 不會(huì)生效, 那就是你設(shè)置了 DIRECT_IO 標(biāo)志。

1.5 文件系統(tǒng)

Linux 下支持的文件系統(tǒng)有很多,常用的有 ext2/3/4、XFS、ZFS 等。

要用哪種文件系統(tǒng)是在格式化的時(shí)候指定的。因?yàn)槊恳粋€(gè)分區(qū)都可以單獨(dú)進(jìn)行格式化,所以一臺(tái) Linux 機(jī)器下可以同時(shí)使用多個(gè)不同的文件系統(tǒng)。

文件系統(tǒng)里提供對(duì) VFS 的具體實(shí)現(xiàn)。除了數(shù)據(jù)結(jié)構(gòu),每個(gè)文件系統(tǒng)還會(huì)定義自己的實(shí)際操作函數(shù)。例如在 ext4 中定義的 ext4_file_operations。在其中包含的VFS中定義的 read 函數(shù)的具體實(shí)現(xiàn):do_sync_read 和 do_sync_write。

  1. const struct file_operations ext4_file_operations = { 
  2.     .llseek         = ext4_llseek, 
  3.     .read           = do_sync_read, 
  4.     .write          = do_sync_write, 
  5.     .aio_read       = generic_file_aio_read, 
  6.     .aio_write      = ext4_file_write, 
  7.     ...... 

和 VFS 不同的是,這里的函數(shù)就是實(shí)實(shí)在在的實(shí)現(xiàn)了。

1.6 通用塊層

文件系統(tǒng)還要依賴更下層的通用塊層。

對(duì)上層的文件系統(tǒng),通用塊層提供一個(gè)統(tǒng)一的接口讓供文件系統(tǒng)實(shí)現(xiàn)者使用,而不用關(guān)心不同設(shè)備驅(qū)動(dòng)程序的差異,這樣實(shí)現(xiàn)出來(lái)的文件系統(tǒng)就能用于任何的塊設(shè)備。通過(guò)對(duì)設(shè)備進(jìn)行抽象后,不管是磁盤還是機(jī)械硬盤,對(duì)于文件系統(tǒng)都可以使用相同的接口對(duì)邏輯數(shù)據(jù)塊進(jìn)行讀寫(xiě)操作。

對(duì)下層。I/O 請(qǐng)求添加到設(shè)備的 I/O 請(qǐng)求隊(duì)列。它定義了一個(gè)叫 bio 的數(shù)據(jù)結(jié)構(gòu)來(lái)表示一次 IO 操作請(qǐng)求(include/linux/bio.h)

1.7 IO 調(diào)度層

當(dāng)通用塊層把 IO 請(qǐng)求實(shí)際發(fā)出以后,并不一定會(huì)立即被執(zhí)行。因?yàn)檎{(diào)度層會(huì)從全局出發(fā),盡量讓整體磁盤 IO 性能最大化。

對(duì)于機(jī)械硬盤來(lái)說(shuō),調(diào)度層會(huì)盡量讓磁頭類似電梯那樣工作,先往一個(gè)方向走,到頭再回來(lái),這樣整體效率會(huì)比較高一些。具體的算法有 deadline 和 cfg ,算法細(xì)節(jié)就不展開(kāi)了,感興趣同學(xué)可以自行搜索。

對(duì)于固態(tài)硬盤來(lái)說(shuō),隨機(jī) IO 的問(wèn)題已經(jīng)被很大程度地解決了,所以可以直接使用最簡(jiǎn)單的 noop 調(diào)度器。

在你的機(jī)器上,通過(guò)dmesg | grep -i scheduler來(lái)查看你的 Linux 支持的調(diào)度算法。

通用塊層和 IO 調(diào)度層一起為上層文件系統(tǒng)屏蔽了底層各種不同的硬盤、U盤的設(shè)備差異。

二、讀文件過(guò)程

我們已經(jīng)把 Linux IO 棧里的各個(gè)內(nèi)核組件都簡(jiǎn)單介紹一遍了?,F(xiàn)在我們?cè)購(gòu)念^整體過(guò)一下讀取文件的過(guò)程(圖中源代碼基于 Linux 3.10)

這一張長(zhǎng)圖把整個(gè) Linux 讀取文件的過(guò)程都串了一遍。

三、回顧開(kāi)篇問(wèn)題

回到開(kāi)篇的第一個(gè)問(wèn)題:讀取文件 1 個(gè)字節(jié)是否會(huì)導(dǎo)致磁盤 IO ?

從上述流程中可以看到,如果 Page Cache 命中的話,根本就沒(méi)有磁盤 IO 產(chǎn)生。

所以,大家不要覺(jué)得代碼里出現(xiàn)幾個(gè)讀寫(xiě)文件的邏輯就覺(jué)得性能會(huì)慢的不行。操作系統(tǒng)已經(jīng)替你優(yōu)化了很多很多,內(nèi)存級(jí)別的訪問(wèn)延遲大約是 ns 級(jí)別的,比機(jī)械磁盤 IO 快了好幾個(gè)數(shù)量級(jí)。如果你的內(nèi)存足夠大,或者你的文件被訪問(wèn)的足夠頻繁,其實(shí)這時(shí)候的 read 操作極少有真正的磁盤 IO 發(fā)生。

假如 Page Cache 沒(méi)有命中,那么一定會(huì)有傳動(dòng)到機(jī)械軸上進(jìn)行磁盤 IO 嗎?

其實(shí)也不一定,為什么,因?yàn)楝F(xiàn)在的磁盤本身就會(huì)帶一塊緩存。另外現(xiàn)在的服務(wù)器都會(huì)組建磁盤陣列,在磁盤陣列里的核心硬件Raid卡里也會(huì)集成RAM作為緩存。只有所有的緩存都不命中的時(shí)候,機(jī)械軸帶著磁頭才會(huì)真正工作。

再看開(kāi)篇的第二個(gè)問(wèn)題:如果發(fā)生了磁盤 IO,那發(fā)生的是多大的 IO 呢?

如果所有的 Cache 都沒(méi)有兜住 IO 讀請(qǐng)求,那么我們來(lái)看看實(shí)際 Linux 會(huì)讀取多大。真的按我們的需求來(lái),只去讀一個(gè)字節(jié)嗎?

整個(gè) IO 過(guò)程中涉及到了好幾個(gè)內(nèi)核組件。而每個(gè)組件之間都是采用不同長(zhǎng)度的塊來(lái)管理磁盤數(shù)據(jù)的。

  • Page Cache 是以頁(yè)為單位的,Linux 頁(yè)大小一般是 4KB
  • 文件系統(tǒng)是以塊(block)為單位來(lái)管理的。使用 dumpe2fs 可以查看,一般一個(gè)塊默認(rèn)是 4KB
  • 通用塊層是以段為單位來(lái)處理磁盤 IO 請(qǐng)求的,一個(gè)段為一個(gè)頁(yè)或者是頁(yè)的一部分
  • IO 調(diào)度程序通過(guò) DMA 方式傳輸 N 個(gè)扇區(qū)到內(nèi)存,扇區(qū)一般為 512 字節(jié)
  • 硬盤也是采用“扇區(qū)”的管理和傳輸數(shù)據(jù)的

可以看到,雖然我們從用戶角度確實(shí)是只讀了 1 個(gè)字節(jié)(開(kāi)篇的代碼中我們只給這次磁盤IO留了一個(gè)字節(jié)的緩存區(qū))。但是在整個(gè)內(nèi)核工作流中,最小的工作單位是磁盤的扇區(qū),為512字節(jié),比1個(gè)字節(jié)要大的多。

另外 block、page cache 等高層組件工作單位更大。其中 Page Cache 的大小是一個(gè)內(nèi)存頁(yè) 4KB。所以一般一次磁盤讀取是多個(gè)扇區(qū)(512字節(jié))一起進(jìn)行的。假設(shè)通用塊層 IO 的段就是一個(gè)內(nèi)存頁(yè)的話,一次磁盤 IO 就是 4 KB(8 個(gè) 512 字節(jié)的扇區(qū))一起進(jìn)行讀取。

另外我們沒(méi)有講到的是還有一套復(fù)雜的預(yù)讀取的策略。所以,在實(shí)踐中,可能比 8 更多的扇區(qū)來(lái)一起被傳輸?shù)絻?nèi)存中。

最后,啰嗦幾句

操作系統(tǒng)的本意是做到讓你簡(jiǎn)單可依賴, 讓你盡量把它當(dāng)成一個(gè)黑盒。你想要一個(gè)字節(jié),它就給你一個(gè)字節(jié),但是自己默默干了許許多多的活兒。

 

我們雖然國(guó)內(nèi)絕大多數(shù)開(kāi)發(fā)都不是搞底層的,但如果你十分關(guān)注你的應(yīng)用程序的性能,你應(yīng)該明白操作系統(tǒng)的什么時(shí)候悄悄提高了你的性能,是怎么來(lái)提高的。以便在將來(lái)某一個(gè)時(shí)候你的線上服務(wù)器扛不住快要掛掉的時(shí)候,你能迅速找出問(wèn)題所在。

 

責(zé)任編輯:武曉燕 來(lái)源: 開(kāi)發(fā)內(nèi)功修煉
相關(guān)推薦

2023-02-16 14:19:07

IP地址UDP

2023-06-01 07:49:51

2022-03-02 11:39:53

物聯(lián)網(wǎng)科技

2018-01-19 12:56:19

Linux進(jìn)程

2023-04-25 15:46:51

Python字符串

2020-09-18 14:23:50

字符

2022-07-25 12:01:10

終端Linux

2022-11-24 08:01:57

bash腳本字符串

2016-04-08 15:13:29

人工智能阿里小Ai

2021-12-08 12:05:21

MySQ磁盤數(shù)據(jù)庫(kù)

2021-12-27 08:24:08

漏洞網(wǎng)絡(luò)安全

2022-06-28 11:30:38

廣電5G套餐專網(wǎng)

2021-08-19 17:27:41

IT數(shù)據(jù)中心災(zāi)難

2021-07-26 10:58:07

Chromebook谷歌更新

2013-05-21 17:42:39

打車AppO2O

2010-10-09 13:41:42

MySQL字符串

2010-09-16 10:56:46

sqlserver建表

2021-09-13 09:43:34

云計(jì)算云服務(wù)混合云

2024-01-08 08:00:00

2023-08-26 07:44:13

系統(tǒng)內(nèi)存虛擬
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)