Linux I/O 那些事儿
介紹 Linux IO 的一些基本原理。
作者:arraywang,騰訊 CSIG
我們先看一張圖:
這張圖大體上描述了 Linux 系統(tǒng)上,應(yīng)用程序?qū)Υ疟P上的文件進(jìn)行讀寫時(shí),從上到下經(jīng)歷了哪些事情。
這篇文章就以這張圖為基礎(chǔ),介紹 Linux 在 I/O 上做了哪些事情。
文件系統(tǒng)
什么是文件系統(tǒng)
文件系統(tǒng),本身是對存儲設(shè)備上的文件,進(jìn)行組織管理的機(jī)制。組織方式不同,就會形成不同的文件系統(tǒng)。比如常見的 Ext4、XFS、ZFS 以及網(wǎng)絡(luò)文件系統(tǒng) NFS 等等。
但是不同類型的文件系統(tǒng)標(biāo)準(zhǔn)和接口可能各有差異,我們在做應(yīng)用開發(fā)的時(shí)候卻很少關(guān)心系統(tǒng)調(diào)用以下的具體實(shí)現(xiàn),大部分時(shí)候都是直接系統(tǒng)調(diào)用 open, read, write, close 來實(shí)現(xiàn)應(yīng)用程序的功能,不會再去關(guān)注我們具體用了什么文件系統(tǒng)(UFS、XFS、Ext4、ZFS),磁盤是什么接口(IDE、SCSI,SAS,SATA 等),磁盤是什么存儲介質(zhì)(HDD、SSD)
應(yīng)用開發(fā)者之所以這么爽,各種復(fù)雜細(xì)節(jié)都不用管直接調(diào)接口,是因?yàn)閮?nèi)核為我們做了大量的有技術(shù)含量的臟活累活。開始的那張圖看到 Linux 在各種不同的文件系統(tǒng)之上,虛擬了一個(gè) VFS,目的就是統(tǒng)一各種不同文件系統(tǒng)的標(biāo)準(zhǔn)和接口,讓開發(fā)者可以使用相同的系統(tǒng)調(diào)用來使用不同的文件系統(tǒng)。
文件系統(tǒng)如何工作(VFS)
Linux 系統(tǒng)下的文件
在 Linux 中一切皆文件。不僅普通的文件和目錄,就連塊設(shè)備、套接字、管道等,也都要通過統(tǒng)一的文件系統(tǒng)來管理。
用?ls?-l?命令看最前面的字符可以看到這個(gè)文件是什么類型brw-r--r--?1?root????root????1,?2?4月??25?11:03?bnod?//?塊設(shè)備文件 crw-r--r--?1?root????root????1,?2?4月??25?11:04?cnod?//?符號設(shè)備文件 drwxr-xr-x?2?wrn3552?wrn3552????6?4月??25?11:01?dir?//?目錄 -rw-r--r--?1?wrn3552?wrn3552????0?4月??25?11:01?file?//?普通文件 prw-r--r--?1?root????root???????0?4月??25?11:04?pipeline?//?有名管道 srwxr-xr-x?1?root????root???????0?4月??25?11:06?socket.sock?//?socket文件 lrwxrwxrwx?1?root????root???????4?4月??25?11:04?softlink?->?file?//?軟連接 -rw-r--r--?2?wrn3552?wrn3552?0?4月??25?11:07?hardlink?//?硬鏈接(本質(zhì)也是普通文件)Linux 文件系統(tǒng)設(shè)計(jì)了兩個(gè)數(shù)據(jù)結(jié)構(gòu)來管理這些不同種類的文件:
inode(index node):索引節(jié)點(diǎn)
dentry(directory entry):目錄項(xiàng)
inode 和 dentry
inode
inode 是用來記錄文件的 metadata,所謂 metadata 在 Wikipedia 上的描述是 data of data,其實(shí)指的就是文件的各種屬性,比如 inode 編號、文件大小、訪問權(quán)限、修改日期、數(shù)據(jù)的位置等。
wrn3552@novadev:~/playground$?stat?file文件:file大小:0???????????????塊:0????????? IO 塊:4096 ??普通空文件 設(shè)備:fe21h/65057d ???? Inode:32828 ??????硬鏈接:2 權(quán)限:(0644/-rw-r--r--)? Uid:( 3041/ wrn3552)?? Gid:( 3041/ wrn3552) 最近訪問:2021-04-25 11:07:59.603745534 +0800 最近更改:2021-04-25 11:07:59.603745534 +0800 最近改動:2021-04-25 11:08:04.739848692 +0800 創(chuàng)建時(shí)間:-inode 和文件一一對應(yīng),它跟文件內(nèi)容一樣,都會被持久化存儲到磁盤中。所以,inode 同樣占用磁盤空間,只不過相對于文件來說它大小固定且大小不算大。
dentry
dentry 用來記錄文件的名字、inode 指針以及與其他 dentry 的關(guān)聯(lián)關(guān)系。
wrn3552@novadev:~/playground$?tree . ├──?dir │???└──?file_in_dir ├──?file └──?hardlink文件的名字:像 dir、file、hardlink、file_in_dir 這些名字是記錄在 dentry 里的
inode 指針:就是指向這個(gè)文件的 inode
與其他 dentry 的關(guān)聯(lián)關(guān)系:其實(shí)就是每個(gè)文件的層級關(guān)系,哪個(gè)文件在哪個(gè)文件下面,構(gòu)成了文件系統(tǒng)的目錄結(jié)構(gòu)
不同于 inode,dentry 是由內(nèi)核維護(hù)的一個(gè)內(nèi)存數(shù)據(jù)結(jié)構(gòu),所以通常也被叫做 dentry cache。
文件是如何存儲在磁盤上的
這里有張圖解釋了文件是如何存儲在磁盤上的,首先,磁盤再進(jìn)行文件系統(tǒng)格式化的時(shí)候,會分出來 3 個(gè)區(qū):
Superblock
inode blocks
data blocks
(其實(shí)還有 boot block,可能會包含一些 bootstrap 代碼,在機(jī)器啟動的時(shí)候被讀到,這里忽略)其中 inode blocks 放的都是每個(gè)文件的 inode,data blocks 里放的是每個(gè)文件的內(nèi)容數(shù)據(jù)。這里關(guān)注一下 superblock,它包含了整個(gè)文件系統(tǒng)的 metadata,具體有:
inode/data block 總量、使用量、剩余量
文件系統(tǒng)的格式,屬主等等各種屬性
superblock 對于文件系統(tǒng)來說非常重要,如果 superblock 損壞了,文件系統(tǒng)就掛載不了了,相應(yīng)的文件也沒辦法讀寫。既然 superblock 這么重要,那肯定不能只有一份,壞了就沒了,它在系統(tǒng)中是有很多副本的,在 superblock 損壞的時(shí)候,可以使用 fsck(File System Check and repair)來恢復(fù)。回到上面的那張圖,可以很清晰地看到文件的各種屬性和文件的數(shù)據(jù)是如何存儲在磁盤上的:
dentry 里包含了文件的名字、目錄結(jié)構(gòu)、inode 指針
inode 指針指向文件特定的 inode(存在 inode blocks 里)
每個(gè) inode 又指向 data blocks 里具體的 logical block,這里的 logical block 存的就是文件具體的數(shù)據(jù)
這里解釋一下什么是 logical block:
對于不同存儲介質(zhì)的磁盤,都有最小的讀寫單元
/sys/block/sda/queue/physical_block_size
HDD 叫做 sector(扇區(qū)),SSD 叫做 page(頁面)
對于 hdd 來說,每個(gè) sector 大小 512Bytes
對于 SSD 來說每個(gè) page 大小不等(和 cell 類型有關(guān)),經(jīng)典的大小是 4KB
但是 Linux 覺得按照存儲介質(zhì)的最小讀寫單元來進(jìn)行讀寫可能會有效率問題,所以支持在文件系統(tǒng)格式化的時(shí)候指定 block size 的大小,一般是把幾個(gè) physical_block 拼起來就成了一個(gè) logical block
/sys/block/sda/queue/logical_block_size
理論上應(yīng)該是 logical_block_size >= physical_block_size,但是有時(shí)候我們會看到 physical_block_size = 4K,logical_block_size = 512B 情況,其實(shí)這是因?yàn)榇疟P上做了一層 512B 的仿真(emulation)(詳情可參考 512e 和 4Kn)
ZFS
這里簡單介紹一個(gè)廣泛應(yīng)用的文件系統(tǒng) ZFS,一些數(shù)據(jù)庫應(yīng)用也會用到 ZFS,先看一張 zfs 的層級結(jié)構(gòu)圖:
這是一張從底向上的圖:
將若干物理設(shè)備 disk 組成一個(gè)虛擬設(shè)備 vdev(同時(shí),disk 也是一種 vdev)
再將若干個(gè)虛擬設(shè)備 vdev 加到一個(gè) zpool 里
在 zpool 的基礎(chǔ)上創(chuàng)建 zfs 并掛載(zvol 可以先不看,我們沒有用到)
ZFS 的一些操作
創(chuàng)建 zpool
root@:~?#?zpool?create?tank?raidz?/dev/ada1?/dev/ada2?/dev/ada3?raidz?/dev/ada4?/dev/ada5?/dev/ada6 root@:~?#?zpool?list?tank NAME????SIZE??ALLOC???FREE??CKPOINT??EXPANDSZ???FRAG????CAP??DEDUP??HEALTH??ALTROOT tank?????11G???824K??11.0G????????-?????????-?????0%?????0%??1.00x??ONLINE??- root@:~?#?zpool?status?tankpool:?tankstate:?ONLINEscan:?none?requested config:NAME????????STATE?????READ?WRITE?CKSUMtank????????ONLINE???????0?????0?????0raidz1-0??ONLINE???????0?????0?????0ada1????ONLINE???????0?????0?????0ada2????ONLINE???????0?????0?????0ada3????ONLINE???????0?????0?????0raidz1-1??ONLINE???????0?????0?????0ada4????ONLINE???????0?????0?????0ada5????ONLINE???????0?????0?????0ada6????ONLINE???????0?????0?????0創(chuàng)建了一個(gè)名為 tank 的 zpool
這里的 raidz 同 RAID5
除了 raidz 還支持其他方案:
創(chuàng)建 zfs
root@:~?#?zfs?create?-o?mountpoint=/mnt/srev?tank/srev root@:~?#?df?-h?tank/srev Filesystem????Size????Used???Avail?Capacity??Mounted?on tank/srev?????7.1G????117K????7.1G?????0%????/mnt/srev創(chuàng)建了一個(gè) zfs,掛載到了 /mnt/srev
這里沒有指定 zfs 的 quota,創(chuàng)建的 zfs 大小即 zpool 大小
對 zfs 設(shè)置 quota
root@:~?#?zfs?set?quota=1G?tank/srev root@:~?#?df?-h?tank/srev Filesystem????Size????Used???Avail?Capacity??Mounted?on tank/srev?????1.0G????118K????1.0G?????0%????/mnt/srevZFS 特性
Pool 存儲
上面的層級圖和操作步驟可以看到 zfs 是基于 zpool 創(chuàng)建的,zpool 可以動態(tài)擴(kuò)容意味著存儲空間也可以動態(tài)擴(kuò)容,而且可以創(chuàng)建多個(gè)文件系統(tǒng),文件系統(tǒng)共享完整的 zpool 空間無需預(yù)分配。
事務(wù)文件系統(tǒng)
zfs 的寫操作是事務(wù)的,意味著要么就沒寫,要么就寫成功了,不會像其他文件系統(tǒng)那樣,應(yīng)用打開了文件,寫入還沒保存的時(shí)候斷電,導(dǎo)致文件為空。zfs 保證寫操作事務(wù)采用的是 copy on write 的方式:
當(dāng) block B 有修改變成 B1 的時(shí)候,普通的文件系統(tǒng)會直接在 block B 原地進(jìn)行修改變成 B1
zfs 則會再另一個(gè)地方寫 B1,然后再在后面安全的時(shí)候?qū)υ瓉淼?B 進(jìn)行回收
這樣結(jié)果就不會出現(xiàn) B 被打開而寫失敗的情況,大不了就是 B1 沒寫成功
這個(gè)特性讓 zfs 在斷電后不需要執(zhí)行 fsck 來檢查磁盤中是否存在寫操作失敗需要恢復(fù)的情況,大大提升了應(yīng)用的可用性。
ARC 緩存
ZFS 中的 ARC(Adjustable Replacement Cache) 讀緩存淘汰算法,是基于 IBM 的 ARP(Adaptive Replacement Cache) 演化而來。在一些文件系統(tǒng)中實(shí)現(xiàn)的標(biāo)準(zhǔn) LRU 算法其實(shí)是有缺陷的:比如復(fù)制大文件之類的線性大量 I/O 操作,導(dǎo)致緩存失效率猛增(大量文件只讀一次,放到內(nèi)存不會被再讀,坐等淘汰)。
另外,緩存可以根據(jù)時(shí)間來進(jìn)行優(yōu)化(LRU,最近最多使用),也可以根據(jù)頻率進(jìn)行優(yōu)化(LFU,最近最常使用),這兩種方法各有優(yōu)劣,但是沒辦法適應(yīng)所有場景。
ARC 的設(shè)計(jì)就是嘗試在 LRU 和 LFU 之間找到一個(gè)平衡,根據(jù)當(dāng)前的 I/O workload 來調(diào)整用 LRU 多一點(diǎn)還是 LFU 多一點(diǎn)。
ARC 定義了 4 個(gè)鏈表:
LRU list:最近最多使用的頁面,存具體數(shù)據(jù)
LFU list:最近最常使用的頁面,存具體數(shù)據(jù)
Ghost list for LRU:最近從 LRU 表淘汰下來的頁面信息,不存具體數(shù)據(jù),只存頁面信息
Ghost list for LFU:最近從 LFU 表淘汰下來的頁面信息,不存具體數(shù)據(jù),只存頁面信息
ARC 工作流程大致如下:
LRU list 和 LFU list 填充和淘汰過程和標(biāo)準(zhǔn)算法一樣
當(dāng)一個(gè)頁面從 LRU list 淘汰下來時(shí),這個(gè)頁面的信息會放到 LRU ghost 表中
如果這個(gè)頁面一直沒被再次引用到,那么這個(gè)頁面的信息最終也會在 LRU ghost 表中被淘汰掉
如果這個(gè)頁面在 LRU ghost 表中未被淘汰的時(shí)候,被再一次訪問了,這時(shí)候會引起一次幽靈(phantom)命中
phantom 命中的時(shí)候,事實(shí)上還是要把數(shù)據(jù)從磁盤第一次放緩存
但是這時(shí)候系統(tǒng)知道剛剛被 LRU 表淘汰的頁面又被訪問到了,說明 LRU list 太小了,這時(shí)它會把 LRU list 長度加一,LFU 長度減一
對于 LFU 的過程也與上述過程類似
ZFS 參考資料
關(guān)于 ZFS 詳細(xì)介紹可以參考:
這篇文章
磁盤類型
磁盤根據(jù)不同的分類方式,有各種不一樣的類型。
磁盤的存儲介質(zhì)
根據(jù)磁盤的存儲介質(zhì)可以分兩類(大家都很熟悉):
HDD(機(jī)械硬盤)
SSD(固態(tài)硬盤)
磁盤的接口
根據(jù)磁盤接口分類:
IDE (Integrated Drive Electronics)
SCSI (Small Computer System Interface)
SAS (Serial Attached SCSI)
SATA (Serial ATA)
...
不同的接口,往往分配不同的設(shè)備名稱。比如, IDE 設(shè)備會分配一個(gè) hd 前綴的設(shè)備名,SCSI 和 SATA 設(shè)備會分配一個(gè) sd 前綴的設(shè)備名。如果是多塊同類型的磁盤,就會按照 a、b、c 等的字母順序來編號。
Linux 對磁盤的管理
其實(shí)在 Linux 中,磁盤實(shí)際上是作為一個(gè)塊設(shè)備來管理的,也就是以塊為單位讀寫數(shù)據(jù),并且支持隨機(jī)讀寫。每個(gè)塊設(shè)備都會被賦予兩個(gè)設(shè)備號,分別是主、次設(shè)備號。主設(shè)備號用在驅(qū)動程序中,用來區(qū)分設(shè)備類型;而次設(shè)備號則是用來給多個(gè)同類設(shè)備編號。
g18-"299"?on?~#?ls?-l?/dev/sda* brw-rw----?1?root?disk?8,??0?Apr?25?15:53?/dev/sda brw-rw----?1?root?disk?8,??1?Apr?25?15:53?/dev/sda1 brw-rw----?1?root?disk?8,?10?Apr?25?15:53?/dev/sda10 brw-rw----?1?root?disk?8,??2?Apr?25?15:53?/dev/sda2 brw-rw----?1?root?disk?8,??5?Apr?25?15:53?/dev/sda5 brw-rw----?1?root?disk?8,??6?Apr?25?15:53?/dev/sda6 brw-rw----?1?root?disk?8,??7?Apr?25?15:53?/dev/sda7 brw-rw----?1?root?disk?8,??8?Apr?25?15:53?/dev/sda8 brw-rw----?1?root?disk?8,??9?Apr?25?15:53?/dev/sda9這些 sda 磁盤主設(shè)備號都是 8,表示它是一個(gè) sd 類型的塊設(shè)備
次設(shè)備號 0-10 表示這些不同 sd 塊設(shè)備的編號
Generic Block Layer
和 VFS 類似,為了對上層屏蔽不同塊設(shè)備的差異,內(nèi)核在文件系統(tǒng)和塊設(shè)備之前抽象了一個(gè) Generic Block Layer(通用塊層),有時(shí)候一些人也會把下面的 I/O 調(diào)度層并到通用塊層里表述。
這兩層主要做兩件事:
跟 VFS 的功能類似。向上,為文件系統(tǒng)和應(yīng)用程序,提供訪問塊設(shè)備的標(biāo)準(zhǔn)接口;向下,把各種異構(gòu)的磁盤設(shè)備抽象為統(tǒng)一的塊設(shè)備,并提供統(tǒng)一框架來管理這些設(shè)備的驅(qū)動程序
對 I/O 請求進(jìn)行調(diào)度,通過重新排序、合并等方式,提高磁盤讀寫效率
下圖是一個(gè)完整的 I/O 棧全景圖:
可以看到中間的 Block Layer 其實(shí)就是 Generic Block Layer,在圖中可以看到 Block Layer 的 I/O 調(diào)度分為兩類,分別表示單隊(duì)列和多隊(duì)列的調(diào)度:
I/O scheduler
blkmq
I/O 調(diào)度
老版本的內(nèi)核里只支持單隊(duì)列的 I/O scheduler,在 3.16 版本的內(nèi)核開始支持多隊(duì)列 blkmq,這里介紹幾種經(jīng)典的 I/O 調(diào)度策略。
單隊(duì)列 I/O scheduler:
NOOP:事實(shí)上是個(gè) FIFO 的隊(duì)列,只做基本的請求合并
CFQ:Completely Fair Queueing,完全公平調(diào)度器,給每個(gè)進(jìn)程維護(hù)一個(gè) I/O 調(diào)度隊(duì)列,按照時(shí)間片來均勻分布每個(gè)進(jìn)程 I/O 請求,
DeadLine:為讀和寫請求創(chuàng)建不同的 I/O 隊(duì)列,確保達(dá)到 deadline 的請求被優(yōu)先處理
多隊(duì)列 blkmq:
bfq:Budget Fair Queueing,也是公平調(diào)度器,不過不是按時(shí)間片來分配,而是按請求的扇區(qū)數(shù)量(帶寬)
kyber:維護(hù)兩個(gè)隊(duì)列(同步/讀、異步/寫),同時(shí)嚴(yán)格限制發(fā)到這兩個(gè)隊(duì)列的請求數(shù)以保證相應(yīng)時(shí)間
mq-deadline:多隊(duì)列版本的 deadline
具體各種 I/O 調(diào)度策略可以參考 IOSchedulers
關(guān)于 blkmq 可以參考 Linux Multi-Queue Block IO Queueing Mechanism (blk-mq) Details
多隊(duì)列調(diào)度可以參考 Block layer introduction part 2: the request layer
性能指標(biāo)
一般來說 I/O 性能指標(biāo)有這幾個(gè):
使用率:ioutil,指的是磁盤處理 I/O 的時(shí)間百分比,ioutil 只看有沒有 I/O 請求,不看 I/O 請求的大小。ioutil 越高表示一直都有 I/O 請求,不代表磁盤無法響應(yīng)新的 I/O 請求
IOPS:每秒的 I/O 請求數(shù)
吞吐量/帶寬:每秒的 I/O 請求大小,通常是 MB/s 或者 GB/s 為單位
響應(yīng)時(shí)間:I/O 請求發(fā)出到收到響應(yīng)的時(shí)間
飽和度:指的是磁盤處理 I/O 的繁忙程度。這個(gè)指標(biāo)比較玄學(xué),沒有直接的數(shù)據(jù)可以表示,一般是根據(jù)平均隊(duì)列請求長度或者響應(yīng)時(shí)間跟基準(zhǔn)測試的結(jié)果進(jìn)行對比來估算
(在做基準(zhǔn)測試時(shí),還會分順序/隨機(jī)、讀/寫進(jìn)行排列組合分別去測 IOPS 和帶寬)
上面的指標(biāo)除了飽和度外,其他都可以在監(jiān)控系統(tǒng)中看到。Linux 也提供了一些命令來輸出不同維度的 I/O 狀態(tài):
iostat -d -x:看各個(gè)設(shè)備的 I/O 狀態(tài),數(shù)據(jù)來源 /proc/diskstats
pidstat -d:看近處的 I/O
iotop:類似 top,按 I/O 大小對進(jìn)程排序
總結(jié)
以上是生活随笔為你收集整理的Linux I/O 那些事儿的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从根本上了解异步编程体系
- 下一篇: linux修改blacklist.con