<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          超專業(yè)解析!10分鐘帶你搞懂Linux中直接I/O原理

          共 8335字,需瀏覽 17分鐘

           ·

          2021-11-18 14:26


          導(dǎo)語?|?本文主要以一張圖為基礎(chǔ),向大家介紹Linux在I/O上做了哪些事情,即Linux中直接I/O原理,希望本文的經(jīng)驗和思路能為讀者提供一些幫助和思考。


          引言


          我們先看一張圖:



          這張圖大體上描述了Linux系統(tǒng)上,應(yīng)用程序?qū)Υ疟P上的文件進(jìn)行讀寫時,從上到下經(jīng)歷了哪些事情。這篇文章就以這張圖為基礎(chǔ),介紹Linux在I/O上做了哪些事情。



          一、文件系統(tǒng)


          (一)什么是文件系統(tǒng)


          文件系統(tǒng),本身是對存儲設(shè)備上的文件,進(jìn)行組織管理的機制。組織方式不同,就會形成不同的文件系統(tǒng)。比如常見的Ext4、XFS、ZFS以及網(wǎng)絡(luò)文件系統(tǒng)NFS等等。


          但是不同類型的文件系統(tǒng)標(biāo)準(zhǔn)和接口可能各有差異,我們在做應(yīng)用開發(fā)的時候卻很少關(guān)心系統(tǒng)調(diào)用以下的具體實現(xiàn),大部分時候都是直接系統(tǒng)調(diào)用open,?read,?write,?close來實現(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)接口,是因為內(nèi)核為我們做了大量的有技術(shù)含量的臟活累活。


          開始的那張圖看到Linux在各種不同的文件系統(tǒng)之上,虛擬了一個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 命令看最前面的字符可以看到這個文件是什么類型
          brw-r--r-- 1 root root 1, 2 425 11:03 bnod // 塊設(shè)備文件crw-r--r-- 1 root root 1, 2 425 11:04 cnod // 符號設(shè)備文件drwxr-xr-x 2 wrn3552 wrn3552 6 425 11:01 dir // 目錄-rw-r--r-- 1 wrn3552 wrn3552 0 425 11:01 file // 普通文件prw-r--r-- 1 root root 0 425 11:04 pipeline // 有名管道srwxr-xr-x 1 root root 0 425 11:06 socket.sock // socket文件lrwxrwxrwx 1 root root 4 425 11:04 softlink -> file // 軟連接-rw-r--r-- 2 wrn3552 wrn3552 0 425 11:07 hardlink // 硬鏈接(本質(zhì)也是普通文件)


          Linux文件系統(tǒng)設(shè)計了兩個數(shù)據(jù)結(jié)構(gòu)來管理這些不同種類的文件:


          • inode(index node):索引節(jié)點


          • dentry(directory entry):目錄項


          • inode


          inode是用來記錄文件的metadata,所謂metadata在Wikipedia上的描述是data of data,其實指的就是文件的各種屬性,比如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)建時間:-


          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指針:就是指向這個文件的inode。


          • 與其他dentry的關(guān)聯(lián)關(guān)系:其實就是每個文件的層級關(guān)系,哪個文件在哪個文件下面,構(gòu)成了文件系統(tǒng)的目錄結(jié)構(gòu)。


          不同于inode,dentry是由內(nèi)核維護(hù)的一個內(nèi)存數(shù)據(jù)結(jié)構(gòu),所以通常也被叫做dentry cache。



          (三)文件是如何存儲在磁盤上的



          里有張圖解釋了文件是如何存儲在磁盤上的:


          首先,磁盤再進(jìn)行文件系統(tǒng)格式化的時候,會分出來3個區(qū)Superblock、inode blocks、data blocks。(其實還有boot block,可能會包含一些bootstrap代碼,在機器啟動的時候被讀到,這里忽略)


          其中inode blocks放的都是每個文件的inode,data blocks里放的是每個文件的內(nèi)容數(shù)據(jù)。


          這里關(guān)注一下Superblock,它包含了整個文件系統(tǒng)的metadata,具體有:


          • inode/data block 總量、使用量、剩余量。


          • 文件系統(tǒng)的格式,屬主等等各種屬性。


          Superblock對于文件系統(tǒng)來說非常重要,如果Superblock損壞了,文件系統(tǒng)就掛載不了了,相應(yīng)的文件也沒辦法讀寫。


          既然Superblock這么重要,那肯定不能只有一份,壞了就沒了,它在系統(tǒng)中是有很多副本的,在Superblock損壞的時候,可以使用fsck(File System Check and repair)來恢復(fù)。


          回到上面的那張圖,可以很清晰地看到文件的各種屬性和文件的數(shù)據(jù)是如何存儲在磁盤上的:


          • dentry里包含了文件的名字、目錄結(jié)構(gòu)、inode指針。


          • inode指針指向文件特定的inode(存在inode blocks里)


          • 每個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來說,每個sector大小512Bytes。


          • 對于SSD來說每個page大小不等(和cell類型有關(guān)),經(jīng)典的大小是4KB。


          • 但是Linux覺得按照存儲介質(zhì)的最小讀寫單元來進(jìn)行讀寫可能會有效率問題,所以支持在文件系統(tǒng)格式化的時候指定block size的大小,一般是把幾個physical_block拼起來就成了一個logical block?/sys/block/sda/queue/logical_block_size。


          • 理論上應(yīng)該是logical_block_size>=physical_block_size,但是有時候我們會看到physical_block_size=4K,logical_block_size=512B情況,其實這是因為磁盤上做了一層512B的仿真(emulation)(詳情可參考512e和4Kn



          二、ZFS


          這里簡單介紹一個廣泛應(yīng)用的文件系統(tǒng)ZFS,一些數(shù)據(jù)庫應(yīng)用也會用到 ZFS。


          先看一張ZFS的層級結(jié)構(gòu)圖:



          這是一張從底向上的圖:


          • 將若干物理設(shè)備disk組成一個虛擬設(shè)備vdev(同時,disk 也是一種vdev)


          • 再將若干個虛擬設(shè)備vdev加到一個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/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)建了一個名為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)建了一個ZFS,掛載到了/mnt/srev。


          • 這里沒有指定ZFS的quota,創(chuàng)建的ZFS大小即zpool大小。


          • 對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存儲


          上面的層級圖和操作步驟可以看到ZFS是基于zpool創(chuàng)建的,zpool可以動態(tài)擴容意味著存儲空間也可以動態(tài)擴容。而且可以創(chuàng)建多個文件系統(tǒng),文件系統(tǒng)共享完整的zpool空間無需預(yù)分配。


          • 事務(wù)文件系統(tǒng)


          ZFS的寫操作是事務(wù)的,意味著要么就沒寫,要么就寫成功了,不會像其他文件系統(tǒng)那樣,應(yīng)用打開了文件,寫入還沒保存的時候斷電,導(dǎo)致文件為空。


          ZFS保證寫操作事務(wù)采用的是copy on write的方式:



          當(dāng)block B有修改變成B1的時候,普通的文件系統(tǒng)會直接在block B原地進(jìn)行修改變成B1。


          ZFS則會再另一個地方寫B(tài)1,然后再在后面安全的時候?qū)υ瓉淼腂進(jìn)行回收。這樣結(jié)果就不會出現(xiàn)B被打開而寫失敗的情況,大不了就是B1沒寫成功。


          這個特性讓ZFS在斷電后不需要執(zhí)行fsck來檢查磁盤中是否存在寫操作失敗需要恢復(fù)的情況,大大提升了應(yīng)用的可用性。


          • ARC緩存


          ZFS中的ARC(Adjustable Replacement Cache) 讀緩存淘汰算法,是基于IBM的ARP(Adaptive Replacement Cache) 演化而來。


          在一些文件系統(tǒng)中實現(xiàn)的標(biāo)準(zhǔn)LRU算法其實是有缺陷的:比如復(fù)制大文件之類的線性大量I/O操作,導(dǎo)致緩存失效率猛增(大量文件只讀一次,放到內(nèi)存不會被再讀,坐等淘汰)


          另外,緩存可以根據(jù)時間來進(jìn)行優(yōu)化(LRU,最近最多使用),也可以根據(jù)頻率進(jìn)行優(yōu)化(LFU,最近最常使用),這兩種方法各有優(yōu)劣,但是沒辦法適應(yīng)所有場景。


          ARC的設(shè)計就是嘗試在LRU和LFU之間找到一個平衡,根據(jù)當(dāng)前的I/O workload來調(diào)整用LRU多一點還是LFU多一點。


          ARC定義了4個鏈表:


          • 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)一個頁面從LRU list淘汰下來時,這個頁面的信息會放到LRU ghost表中。


          • 如果這個頁面一直沒被再次引用到,那么這個頁面的信息最終也會在LRU ghost表中被淘汰掉。


          • 如果這個頁面在LRU ghost表中未被淘汰的時候,被再一次訪問了,這時候會引起一次幽靈(phantom)命中。


          • phantom命中的時候,事實上還是要把數(shù)據(jù)從磁盤第一次放緩存。


          • 但是這時候系統(tǒng)知道剛剛被LRU表淘汰的頁面又被訪問到了,說明LRU list太小了,這時它會把LRU list長度加一,LFU長度減一。


          • 對于LFU的過程也與上述過程類似。



          三、磁盤類型


          磁盤根據(jù)不同的分類方式,有各種不一樣的類型。


          (一)磁盤的存儲介質(zhì)


          根據(jù)磁盤的存儲介質(zhì)可以分兩類(大家都很熟悉):HDD(機械硬盤)和SSD(固態(tài)硬盤)



          (二)磁盤的接口


          根據(jù)磁盤接口分類:


          • IDE (Integrated Drive Electronics)


          • SCSI (Small Computer System Interface)


          • SAS (Serial Attached SCSI)


          • SATA (Serial ATA)


          • ...


          不同的接口,往往分配不同的設(shè)備名稱。比如,IDE設(shè)備會分配一個hd前綴的設(shè)備名,SCSI和SATA設(shè)備會分配一個sd前綴的設(shè)備名。如果是多塊同類型的磁盤,就會按照a、b、c等的字母順序來編號。



          (三)Linux對磁盤的管理


          其實在Linux中,磁盤實際上是作為一個塊設(shè)備來管理的,也就是以塊為單位讀寫數(shù)據(jù),并且支持隨機讀寫。每個塊設(shè)備都會被賦予兩個設(shè)備號,分別是主、次設(shè)備號。主設(shè)備號用在驅(qū)動程序中,用來區(qū)分設(shè)備類型;而次設(shè)備號則是用來給多個同類設(shè)備編號。


          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è)備號都是8,表示它是一個sd類型的塊設(shè)備。


          • 次設(shè)備號0-10表示這些不同sd塊設(shè)備的編號。



          四、Generic Block Layer



          可以看到中間的Block Layer其實就是Generic Block Layer。


          在圖中可以看到Block Layer的I/O調(diào)度分為兩類,分別表示單隊列和多隊列的調(diào)度:


          • I/O scheduler


          • blkmq



          (一)I/O調(diào)度


          老版本的內(nèi)核里只支持單隊列的I/O scheduler,在3.16版本的內(nèi)核開始支持多隊列blkmq。


          這里介紹幾種經(jīng)典的I/O調(diào)度策略:


          • 單隊列I/O scheduler


          • NOOP:事實上是個FIFO的隊列,只做基本的請求合并。


          • CFQ:Completely Fair Queueing,完全公平調(diào)度器,給每個進(jìn)程維護(hù)一個I/O調(diào)度隊列,按照時間片來均勻分布每個進(jìn)程I/O請求。


          • DeadLine:為讀和寫請求創(chuàng)建不同的I/O隊列,確保達(dá)到deadline的請求被優(yōu)先處理。



          • 多隊列blkmq


          • bfq:Budget Fair Queueing,也是公平調(diào)度器,不過不是按時間片來分配,而是按請求的扇區(qū)數(shù)量(帶寬)


          • kyber:維護(hù)兩個隊列(同步/讀、異步/寫),同時嚴(yán)格限制發(fā)到這兩個隊列的請求數(shù)以保證相應(yīng)時間。


          • mq-deadline:多隊列版本的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)


          多隊列調(diào)度可以參考Block layer introduction part 2:the request layer



          五、性能指標(biāo)


          一般來說I/O性能指標(biāo)有這5個:


          • 使用率:ioutil,指的是磁盤處理I/O的時間百分比,ioutil只看有沒有I/O請求,不看I/O請求的大小。ioutil越高表示一直都有I/O請求,不代表磁盤無法響應(yīng)新的I/O請求。


          • IOPS:每秒的I/O請求數(shù)。


          • 吞吐量/帶寬:每秒的I/O請求大小,通常是MB/s或者GB/s為單位。


          • 響應(yīng)時間:I/O請求發(fā)出到收到響應(yīng)的時間。


          • 飽和度:指的是磁盤處理I/O的繁忙程度。這個指標(biāo)比較玄學(xué),沒有直接的數(shù)據(jù)可以表示,一般是根據(jù)平均隊列請求長度或者響應(yīng)時間跟基準(zhǔn)測試的結(jié)果進(jìn)行對比來估算(在做基準(zhǔn)測試時,還會分順序/隨機、讀/寫進(jìn)行排列組合分別去測IOPS和帶寬)


          上面的指標(biāo)除了飽和度外,其他都可以在監(jiān)控系統(tǒng)中看到。Linux也提供了一些命令來輸出不同維度的I/O狀態(tài)


          • iostat-d-x:看各個設(shè)備的I/O狀態(tài),數(shù)據(jù)來源/proc/diskstats。


          • pidstat-d:看近處的I/O。


          • iotop:類似top,按I/O大小對進(jìn)程排序。



          ?作者簡介


          王睿

          騰訊云游戲解決方案架構(gòu)師

          騰訊云游戲解決方案架構(gòu)師,畢業(yè)于中山大學(xué)。目前負(fù)責(zé)騰訊云游戲行業(yè)解決方案設(shè)計等工作,有豐富的游戲運維及開發(fā)經(jīng)驗。



          ?推薦閱讀


          碳中和的入口與出口,數(shù)字化建設(shè)該如何完成?

          基于Protobuf共享字段的分包和透傳零拷貝技術(shù),你了解嗎?

          深度解讀!新一代大數(shù)據(jù)引擎Flink厲害在哪?(附實現(xiàn)原理細(xì)節(jié))

          終于!12年后Golang支持泛型了?。▋?nèi)含10個實例)





          瀏覽 43
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本无码操逼视频 | 国精品无码一区二区三区四区五区 | 国内av免费观看 韩日精品在线观看 | 天天操天天操中 | 簧片大全免费观看视频了6666 |