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