Linux输入输出I/O
Linux輸入輸出I/O
本文主要以一張圖介紹Linux在I/O上做了哪些事情,即Linux中直接I/O原理。
引言
先看一張圖:
這張圖大體上描述了Linux系統(tǒng)上,應(yīng)用程序?qū)Υ疟P上的文件進(jìn)行讀寫時(shí),從上到下經(jīng)歷了哪些事情。這篇文章就以這張圖為基礎(chǔ),介紹Linux在I/O上做了哪些事情。
一、文件系統(tǒng)
(一)什么是文件系統(tǒng)
文件系統(tǒng),本身是對(duì)存儲(chǔ)設(shè)備上的文件,進(jìn)行組織管理的機(jī)制。組織方式不同,就會(huì)形成不同的文件系統(tǒng)。比如常見(jiàn)的Ext4、XFS、ZFS以及網(wǎng)絡(luò)文件系統(tǒng)NFS等等。
但是不同類型的文件系統(tǒng)標(biāo)準(zhǔn)和接口可能各有差異,在做應(yīng)用開(kāi)發(fā)的時(shí)候卻很少關(guān)心系統(tǒng)調(diào)用以下的具體實(shí)現(xiàn),大部分時(shí)候都是直接系統(tǒng)調(diào)用open, read, write, close來(lái)實(shí)現(xiàn)應(yīng)用程序的功能,不會(huì)再去關(guān)注具體用了什么文件系統(tǒng)(UFS、XFS、Ext4、ZFS),磁盤是什么接口(IDE、SCSI,SAS,SATA等),磁盤是什么存儲(chǔ)介質(zhì)(HDD、SSD)應(yīng)用開(kāi)發(fā)者之所以這么爽,各種復(fù)雜細(xì)節(jié)都不用管直接調(diào)接口,是因?yàn)閮?nèi)核為做了大量的有技術(shù)含量的臟活累活。
開(kāi)始的那張圖看到Linux在各種不同的文件系統(tǒng)之上,虛擬了一個(gè)VFS,目的就是統(tǒng)一各種不同文件系統(tǒng)的標(biāo)準(zhǔn)和接口,讓開(kāi)發(fā)者可以使用相同的系統(tǒng)調(diào)用來(lái)使用不同的文件系統(tǒng)。
(二)文件系統(tǒng)如何工作(VFS)
? Linux系統(tǒng)下的文件
在Linux中一切皆文件。不僅普通的文件和目錄,就連塊設(shè)備、套接字、管道等,也都要通過(guò)統(tǒng)一的文件系統(tǒng)來(lái)管理。
用 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 // 符號(hào)設(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)來(lái)管理這些不同種類的文件:
? inode(index node):索引節(jié)點(diǎn)
? dentry(directory entry):目錄項(xiàng)
? inode
inode是用來(lái)記錄文件的metadata,所謂metadata在Wikipedia上的描述是data of data,其實(shí)指的就是文件的各種屬性,比如inode編號(hào)、文件大小、訪問(wèn)權(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)最近訪問(wèn):2021-04-25 11:07:59.603745534 +0800最近更改:2021-04-25 11:07:59.603745534 +0800最近改動(dòng):2021-04-25 11:08:04.739848692 +0800創(chuàng)建時(shí)間:-
inode和文件一一對(duì)應(yīng),它跟文件內(nèi)容一樣,都會(huì)被持久化存儲(chǔ)到磁盤中。所以,inode同樣占用磁盤空間,只不過(guò)相對(duì)于文件來(lái)說(shuō)它大小固定且大小不算大。
? dentry
dentry用來(lái)記錄文件的名字、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è)文件的層級(jí)關(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。
(三)文件是如何存儲(chǔ)在磁盤上的
這里有張圖解釋了文件是如何存儲(chǔ)在磁盤上的:
首先,磁盤再進(jìn)行文件系統(tǒng)格式化的時(shí)候,會(huì)分出來(lái)3個(gè)區(qū):Superblock、inode blocks、data blocks。(其實(shí)還有boot block,可能會(huì)包含一些bootstrap代碼,在機(jī)器啟動(dòng)的時(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對(duì)于文件系統(tǒng)來(lái)說(shuō)非常重要,如果Superblock損壞了,文件系統(tǒng)就掛載不了了,相應(yīng)的文件也沒(méi)辦法讀寫。
既然Superblock這么重要,那肯定不能只有一份,壞了就沒(méi)了,它在系統(tǒng)中是有很多副本的,在Superblock損壞的時(shí)候,可以使用fsck(File System Check and repair)來(lái)恢復(fù)。
回到上面的那張圖,可以很清晰地看到文件的各種屬性和文件的數(shù)據(jù)是如何存儲(chǔ)在磁盤上的:
? dentry里包含了文件的名字、目錄結(jié)構(gòu)、inode指針。
? inode指針指向文件特定的inode(存在inode blocks里)
? 每個(gè)inode又指向data blocks里具體的logical block,這里的logical block存的就是文件具體的數(shù)據(jù)。
這里解釋一下什么是logical block:
? 對(duì)于不同存儲(chǔ)介質(zhì)的磁盤,都有最小的讀寫單元 /sys/block/sda/queue/physical_block_size。
? HDD叫做sector(扇區(qū)),SSD叫做page(頁(yè)面)
? 對(duì)于hdd來(lái)說(shuō),每個(gè)sector大小512Bytes。
? 對(duì)于SSD來(lái)說(shuō)每個(gè)page大小不等(和cell類型有關(guān)),經(jīng)典的大小是4KB。
? 但是Linux覺(jué)得按照存儲(chǔ)介質(zhì)的最小讀寫單元來(lái)進(jìn)行讀寫可能會(huì)有效率問(wèn)題,所以支持在文件系統(tǒng)格式化的時(shí)候指定block size的大小,一般是把幾個(gè)physical_block拼起來(lái)就成了一個(gè)logical block /sys/block/sda/queue/logical_block_size。
? 理論上應(yīng)該是logical_block_size>=physical_block_size,但是有時(shí)候會(huì)看到physical_block_size=4K,logical_block_size=512B情況,其實(shí)這是因?yàn)榇疟P上做了一層512B的仿真(emulation)(詳情可參考512e和4Kn)
二、ZFS
這里簡(jiǎn)單介紹一個(gè)廣泛應(yīng)用的文件系統(tǒng)ZFS,一些數(shù)據(jù)庫(kù)應(yīng)用也會(huì)用到 ZFS。
先看一張ZFS的層級(jí)結(jié)構(gòu)圖:
這是一張從底向上的圖:
? 將若干物理設(shè)備disk組成一個(gè)虛擬設(shè)備vdev(同時(shí),disk 也是一種vdev)
? 再將若干個(gè)虛擬設(shè)備vdev加到一個(gè)zpool里。
? 在zpool的基礎(chǔ)上創(chuàng)建zfs并掛載(zvol可以先不看,沒(méi)有用到)
(一)ZFS的一些操作
? 創(chuàng)建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
? 創(chuàng)建了一個(gè)名為tank的zpool
? 這里的raidz同RAID5
除了raidz還支持其他方案:
? 創(chuàng)建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
? 創(chuàng)建了一個(gè)ZFS,掛載到了/mnt/srev。
? 這里沒(méi)有指定ZFS的quota,創(chuàng)建的ZFS大小即zpool大小。
? 對(duì)ZFS設(shè)置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存儲(chǔ)
上面的層級(jí)圖和操作步驟可以看到ZFS是基于zpool創(chuàng)建的,zpool可以動(dòng)態(tài)擴(kuò)容意味著存儲(chǔ)空間也可以動(dòng)態(tài)擴(kuò)容。而且可以創(chuàng)建多個(gè)文件系統(tǒng),文件系統(tǒng)共享完整的zpool空間無(wú)需預(yù)分配。
? 事務(wù)文件系統(tǒng)
ZFS的寫操作是事務(wù)的,意味著要么就沒(méi)寫,要么就寫成功了,不會(huì)像其他文件系統(tǒng)那樣,應(yīng)用打開(kāi)了文件,寫入還沒(méi)保存的時(shí)候斷電,導(dǎo)致文件為空。
ZFS保證寫操作事務(wù)采用的是copy on write的方式:
當(dāng)block B有修改變成B1的時(shí)候,普通的文件系統(tǒng)會(huì)直接在block B原地進(jìn)行修改變成B1。
ZFS則會(huì)再另一個(gè)地方寫B(tài)1,然后再在后面安全的時(shí)候?qū)υ瓉?lái)的B進(jìn)行回收。這樣結(jié)果就不會(huì)出現(xiàn)B被打開(kāi)而寫失敗的情況,大不了就是B1沒(méi)寫成功。
這個(gè)特性讓ZFS在斷電后不需要執(zhí)行fsck來(lái)檢查磁盤中是否存在寫操作失敗需要恢復(fù)的情況,大大提升了應(yīng)用的可用性。
? ARC緩存
ZFS中的ARC(Adjustable Replacement Cache) 讀緩存淘汰算法,是基于IBM的ARP(Adaptive Replacement Cache) 演化而來(lái)。
在一些文件系統(tǒng)中實(shí)現(xiàn)的標(biāo)準(zhǔn)LRU算法其實(shí)是有缺陷的:比如復(fù)制大文件之類的線性大量I/O操作,導(dǎo)致緩存失效率猛增(大量文件只讀一次,放到內(nèi)存不會(huì)被再讀,坐等淘汰)
另外,緩存可以根據(jù)時(shí)間來(lái)進(jìn)行優(yōu)化(LRU,最近最多使用),也可以根據(jù)頻率進(jìn)行優(yōu)化(LFU,最近最常使用),這兩種方法各有優(yōu)劣,但是沒(méi)辦法適應(yīng)所有場(chǎng)景。
ARC的設(shè)計(jì)就是嘗試在LRU和LFU之間找到一個(gè)平衡,根據(jù)當(dāng)前的I/O workload來(lái)調(diào)整用LRU多一點(diǎn)還是LFU多一點(diǎn)。
ARC定義了4個(gè)鏈表:
? LRU list:最近最多使用的頁(yè)面,存具體數(shù)據(jù)。
? LFU list:最近最常使用的頁(yè)面,存具體數(shù)據(jù)。
? Ghost list for LRU:最近從LRU表淘汰下來(lái)的頁(yè)面信息,不存具體數(shù)據(jù),只存頁(yè)面信息。
? Ghost list for LFU:最近從LFU表淘汰下來(lái)的頁(yè)面信息,不存具體數(shù)據(jù),只存頁(yè)面信息。
ARC工作流程大致如下:
? LRU list和LFU list填充和淘汰過(guò)程和標(biāo)準(zhǔn)算法一樣。
? 當(dāng)一個(gè)頁(yè)面從LRU list淘汰下來(lái)時(shí),這個(gè)頁(yè)面的信息會(huì)放到LRU ghost表中。
? 如果這個(gè)頁(yè)面一直沒(méi)被再次引用到,那么這個(gè)頁(yè)面的信息最終也會(huì)在LRU ghost表中被淘汰掉。
? 如果這個(gè)頁(yè)面在LRU ghost表中未被淘汰的時(shí)候,被再一次訪問(wèn)了,這時(shí)候會(huì)引起一次幽靈(phantom)命中。
? phantom命中的時(shí)候,事實(shí)上還是要把數(shù)據(jù)從磁盤第一次放緩存。
? 但是這時(shí)候系統(tǒng)知道剛剛被LRU表淘汰的頁(yè)面又被訪問(wèn)到了,說(shuō)明LRU list太小了,這時(shí)它會(huì)把LRU list長(zhǎng)度加一,LFU長(zhǎng)度減一。
? 對(duì)于LFU的過(guò)程也與上述過(guò)程類似。
三、磁盤類型
磁盤根據(jù)不同的分類方式,有各種不一樣的類型。
(一)磁盤的存儲(chǔ)介質(zhì)
根據(jù)磁盤的存儲(chǔ)介質(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è)備會(huì)分配一個(gè)hd前綴的設(shè)備名,SCSI和SATA設(shè)備會(huì)分配一個(gè)sd前綴的設(shè)備名。如果是多塊同類型的磁盤,就會(huì)按照a、b、c等的字母順序來(lái)編號(hào)。
(三)Linux對(duì)磁盤的管理
其實(shí)在Linux中,磁盤實(shí)際上是作為一個(gè)塊設(shè)備來(lái)管理的,也就是以塊為單位讀寫數(shù)據(jù),并且支持隨機(jī)讀寫。每個(gè)塊設(shè)備都會(huì)被賦予兩個(gè)設(shè)備號(hào),分別是主、次設(shè)備號(hào)。主設(shè)備號(hào)用在驅(qū)動(dòng)程序中,用來(lái)區(qū)分設(shè)備類型;而次設(shè)備號(hào)則是用來(lái)給多個(gè)同類設(shè)備編號(hào)。
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磁盤主設(shè)備號(hào)都是8,表示它是一個(gè)sd類型的塊設(shè)備。
? 次設(shè)備號(hào)0-10表示這些不同sd塊設(shè)備的編號(hào)。
四、Generic Block Layer
可以看到中間的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)核開(kāi)始支持多隊(duì)列blkmq。
這里介紹幾種經(jīng)典的I/O調(diào)度策略:
? 單隊(duì)列I/O scheduler
? NOOP:事實(shí)上是個(gè)FIFO的隊(duì)列,只做基本的請(qǐng)求合并。
? CFQ:Completely Fair Queueing,完全公平調(diào)度器,給每個(gè)進(jìn)程維護(hù)一個(gè)I/O調(diào)度隊(duì)列,按照時(shí)間片來(lái)均勻分布每個(gè)進(jìn)程I/O請(qǐng)求。
? DeadLine:為讀和寫請(qǐng)求創(chuàng)建不同的I/O隊(duì)列,確保達(dá)到deadline的請(qǐng)求被優(yōu)先處理。
? 多隊(duì)列blkmq
? bfq:Budget Fair Queueing,也是公平調(diào)度器,不過(guò)不是按時(shí)間片來(lái)分配,而是按請(qǐng)求的扇區(qū)數(shù)量(帶寬)
? kyber:維護(hù)兩個(gè)隊(duì)列(同步/讀、異步/寫),同時(shí)嚴(yán)格限制發(fā)到這兩個(gè)隊(duì)列的請(qǐng)求數(shù)以保證相應(yīng)時(shí)間。
? mq-deadline:多隊(duì)列版本的deadline。
具體各種I/O調(diào)度策略可以參考IOSchedulers
(https://wiki.ubuntu.com/Kernel/Reference/IOSchedulers)
關(guān)于blkmq可以參考Linux Multi-Queue Block IO Queueing Mechanism (blk-mq) Details_Details)
多隊(duì)列調(diào)度可以參考Block layer introduction part 2:the request layer
五、性能指標(biāo)
一般來(lái)說(shuō)I/O性能指標(biāo)有這5個(gè):
? 使用率:ioutil,指的是磁盤處理I/O的時(shí)間百分比,ioutil只看有沒(méi)有I/O請(qǐng)求,不看I/O請(qǐng)求的大小。ioutil越高表示一直都有I/O請(qǐng)求,不代表磁盤無(wú)法響應(yīng)新的I/O請(qǐng)求。
? IOPS:每秒的I/O請(qǐng)求數(shù)。
? 吞吐量/帶寬:每秒的I/O請(qǐng)求大小,通常是MB/s或者GB/s為單位。
? 響應(yīng)時(shí)間:I/O請(qǐng)求發(fā)出到收到響應(yīng)的時(shí)間。
? 飽和度:指的是磁盤處理I/O的繁忙程度。這個(gè)指標(biāo)比較玄學(xué),沒(méi)有直接的數(shù)據(jù)可以表示,一般是根據(jù)平均隊(duì)列請(qǐng)求長(zhǎng)度或者響應(yīng)時(shí)間跟基準(zhǔn)測(cè)試的結(jié)果進(jìn)行對(duì)比來(lái)估算(在做基準(zhǔn)測(cè)試時(shí),還會(huì)分順序/隨機(jī)、讀/寫進(jìn)行排列組合分別去測(cè)IOPS和帶寬)
上面的指標(biāo)除了飽和度外,其他都可以在監(jiān)控系統(tǒng)中看到。Linux也提供了一些命令來(lái)輸出不同維度的I/O狀態(tài):
? iostat-d-x:看各個(gè)設(shè)備的I/O狀態(tài),數(shù)據(jù)來(lái)源/proc/diskstats。
? pidstat-d:看近處的I/O。
? iotop:類似top,按I/O大小對(duì)進(jìn)程排序。
參考鏈接:
https://view.inews.qq.com/a/20211115A09ZSQ00
總結(jié)
以上是生活随笔為你收集整理的Linux输入输出I/O的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: c++ Factor泛型编程示例
- 下一篇: 如何将算子添加到Relay