小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

深入理解Linux內(nèi)核I/O機(jī)制:探索文件系統(tǒng)與設(shè)備驅(qū)動

 深度Linux 2023-10-27 發(fā)布于湖南

Linux內(nèi)核的I/O機(jī)制是指用于處理輸入輸出操作的一系列技術(shù)和算法。這些機(jī)制和技術(shù)共同構(gòu)成了Linux內(nèi)核的I/O機(jī)制,提供了豐富而靈活的輸入輸出功能,滿足不同應(yīng)用場景下的需求。在業(yè)務(wù)執(zhí)行過程中,常伴隨大量的IO操作,如果IO操作和CPU消耗不能合理安排,將會導(dǎo)致整體業(yè)務(wù)執(zhí)行效率低下,用戶體驗(yàn)極差。比如手機(jī)啟動過程,有大量CPU消耗和IO操作。

在Linux中,I/O機(jī)制主要包括以下幾個方面:

  1. 文件系統(tǒng):Linux使用文件系統(tǒng)作為對外提供數(shù)據(jù)存儲和訪問的接口。文件系統(tǒng)可以是基于磁盤的,也可以是虛擬的,如procfs、sysfs等。通過文件系統(tǒng),應(yīng)用程序可以通過讀寫文件來進(jìn)行輸入輸出操作。

  2. 文件描述符:在Linux中,每個打開的文件都會分配一個唯一的整數(shù)標(biāo)識符,稱為文件描述符(file descriptor)。應(yīng)用程序可以使用文件描述符進(jìn)行對文件的讀寫操作。

  3. 阻塞I/O和非阻塞I/O:在進(jìn)行I/O操作時,可以選擇阻塞或非阻塞模式。阻塞I/O會使調(diào)用進(jìn)程在完成I/O操作之前被掛起,而非阻塞I/O則會立即返回,在數(shù)據(jù)未準(zhǔn)備好時可能返回一個錯誤或特殊值。

  4. 異步I/O:異步I/O是指應(yīng)用程序發(fā)起一個讀/寫請求后不需要等待其完成就可以繼續(xù)執(zhí)行其他任務(wù)。當(dāng)請求完成時,內(nèi)核會通知應(yīng)用程序并將數(shù)據(jù)復(fù)制到指定緩沖區(qū)中。

  5. 多路復(fù)用:多路復(fù)用是一種同時監(jiān)控多個輸入源(例如套接字)是否有數(shù)據(jù)可讀/可寫的機(jī)制。常見的多路復(fù)用技術(shù)有select、poll和epoll。

用Bootchart記錄android啟動過程的CPU/IO消耗如下圖:

Systemd readahead:

Systemd readahead-collect.service搜集系統(tǒng)啟動過程中的文件訪問信息,Systemd
readahead-replay.service在后續(xù)啟動過程中完成回放,即將IO操作與CPU并行;

提高效率的一個宗旨,把CPU和IO的交替等,變?yōu)镃PU和IO操作(不需要CPU參與)同時工作,充分利用系統(tǒng)資源,為解決CPU/IO并行問題,Linux提供了很多IO模型。

一、I/O模型

1.1阻塞與非阻塞

(1)阻塞: 一般來說,進(jìn)程阻塞,等待IO條件滿足才返回;

有個例外,阻塞可以被信號打斷;

若設(shè)置信號標(biāo)記,act.sa_flags |= SA_RESTART,接收信號,read阻塞不返回,但是信號響應(yīng)函數(shù)還是會調(diào)用;相當(dāng)于系統(tǒng)自動重新進(jìn)入阻塞。

用signal()函數(shù)設(shè)置信號,其調(diào)用sigaction自動設(shè)置SA_RESTART

(2)非阻塞

read/write等IO調(diào)用,IO設(shè)備沒就緒,立即返回,實(shí)際工程上用的不多;

1.2多路復(fù)用

實(shí)際業(yè)務(wù)中,一般有多個IO請求,每個請求響應(yīng)都用簡單的阻塞模型效率太低,Linux提供了多路復(fù)用的的系統(tǒng)調(diào)用:

(1) select

select()處理流程

  • a.告訴系統(tǒng),要關(guān)注哪些IO請求;

  • b.阻塞等待,直到有IO就緒,select返回;

  • c.主動查詢是哪個IO就緒,然后響應(yīng)該IO;

  • d.重新關(guān)注新的IO請求;

當(dāng)IO請求過多時,這種查詢的方式也很浪費(fèi)資源,因此Linux提供了一個新的系統(tǒng)調(diào)用

(2)epoll()

epoll與select的不同:

  • a.將注冊IO請求和等待事件觸發(fā)分離開;

  • b.返回后,直接告訴哪些IO就緒,不用再主動查詢;

當(dāng)IO數(shù)量不多時,可以用select或epoll,但當(dāng)IO非常多時,比如大型網(wǎng)絡(luò)應(yīng)用,響應(yīng)多個IO請求時,用epoll效率遠(yuǎn)高于select。

signal io方式,都是read/write阻塞,底層實(shí)現(xiàn),待IO就緒后,內(nèi)核發(fā)送信號,喚醒阻塞;

比如讀觸摸屏應(yīng)用,read被阻塞,只有觸摸屏被按下,觸發(fā)中斷程序響應(yīng),讀取觸摸屏行為數(shù)據(jù)后,內(nèi)核發(fā)送信號喚醒APP的等待,APP讀到觸摸動作信息,做相應(yīng)業(yè)務(wù)處理。

目前工程上,處理異步I/O更多用以下方法:

1.3異步IO

(1) C庫提供的Glibc-AIO:Glibc-AIO原理,aio_read()立即返回,后臺自動創(chuàng)建線程讀取io,aio_suspend()查詢IO是否完成,完成立即返回,未完成,等待;

(2) 內(nèi)核提供的Kernel-AIO:一般用來讀取硬盤數(shù)據(jù),比如數(shù)據(jù)庫讀??;

這些異步模型,天然的將IO與CPU消耗等待做并行處理;

1.4Libevent事件觸發(fā)

功能類似QT/VC的按鈕,注冊回調(diào)函數(shù),當(dāng)事件觸發(fā)時,執(zhí)行回調(diào)函數(shù)。

libevent是一個跨平臺庫,封裝底層平臺調(diào)用,提供統(tǒng)一API。Windows/Solaris/linux。

gcc xxx.c -levent

模型對比:

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【865977150】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。?/blockquote>

資料直通車:最新Linux內(nèi)核源碼資料文檔+視頻資料

內(nèi)核學(xué)習(xí)地址:Linux內(nèi)核源碼/內(nèi)存調(diào)優(yōu)/文件系統(tǒng)/進(jìn)程管理/設(shè)備驅(qū)動/網(wǎng)絡(luò)協(xié)議棧

二、EXT文件系統(tǒng)

一切都是文件,Linux通過VFS中間層,支持多種文件系統(tǒng),對APP統(tǒng)一接口,文件系統(tǒng)的本質(zhì)是將用戶數(shù)據(jù)和元數(shù)據(jù)(管理數(shù)據(jù)的數(shù)據(jù)),組織成有序的目錄結(jié)構(gòu)。

2.1EXT2文件系統(tǒng)總體存儲布局

一個磁盤可以劃為多個分區(qū),每個分區(qū)必須先用格式化工具(某種mkfs)格式化成某種格式的文件系統(tǒng),然后才能存儲文件,格式化的過程會在磁盤上寫一些管理存儲布局信息。

一個典型的ext格式化文件系統(tǒng)存儲布局如下:

啟動塊(BootBlock):大小1K,是由PC標(biāo)準(zhǔn)規(guī)定的,用來存儲磁盤分區(qū)信息和啟動信息,任何文件系統(tǒng)都不能使用啟動塊。啟動塊之后才是EXT文件系統(tǒng)的開始;

超級塊(Superblock):描述整個分區(qū)的文件系統(tǒng)信息,比如塊大小,文件系統(tǒng)版本號,上次mount時間等;

超級塊在每個塊組的開頭都有一份拷貝;

塊組描述符表(GDT, Group Descriptor Table)

由很多塊組描述符(Group Descriptor) 組成,整個分區(qū)分成多少個塊組就對應(yīng)多少個GD;

每個GD存儲一個塊組的描述信息,比如這個塊組從哪里開始是inode表,哪里開始是數(shù)據(jù)塊,空閑的inode和數(shù)據(jù)塊還有多少個等。

和超級塊類似,GDT在每個塊組開頭也有一份拷貝;

塊位圖(Block Bitmap)

用來描述整個塊組中那些塊空閑,本身占用一個塊,每個bit代表本塊組的一個塊,bit為1表示對應(yīng)塊被占用,0表示空閑;

tips

df命令統(tǒng)計(jì)整個磁盤空間非??欤?yàn)橹恍枰榭疵總€塊組的塊位圖即可;

du命令查看一個較大目錄會很慢,因?yàn)樾枰阉髡麄€目錄的所有文件;

inode位圖(inode Bitmap)

和塊位圖類似,本身占用一個塊,每個bit表示一個inode是否空閑可用;

inode表(inode Table)

每個文件對應(yīng)一個inode,用來描述文件類型,權(quán)限,大小,創(chuàng)建/修改/訪問時間等信息;

一個塊組中的所有inode組成了inode表;

Inode表占用多少個塊,格式化時就要確定,mke2fs工具默認(rèn)策略是每8K分配一個inode。

就是說當(dāng)文件全部是8K時,inode表會充分利用,當(dāng)文件過大,inode表會浪費(fèi),文件過小,inode不夠用;

硬鏈接指向同一個inode;

數(shù)據(jù)塊(Data Block)

a.常規(guī)文件:

文件的數(shù)據(jù)存儲在數(shù)據(jù)塊中;

b.目錄

該目錄下所有文件名和目錄名存儲在數(shù)據(jù)塊中;

文件名保存在目錄的數(shù)據(jù)塊中,ls –l看到的其他信息保存在該文件的inode中;

目錄也是文件,是一種特殊類型的文件;

c.符號鏈接

如果目標(biāo)路徑名較短,直接保存在inode中以便查找,如果過長,分配一個數(shù)據(jù)塊保存。

d.設(shè)備文件、FIFO和socket等特殊文件

沒有數(shù)據(jù)塊,設(shè)備文件的主,次設(shè)備號保存在inode中。

2.2實(shí)例解析文件系統(tǒng)結(jié)構(gòu):

用一個文件來模擬一個磁盤;

1.創(chuàng)建一個1M文件,內(nèi)容全是0

dd if=/dev/zero of=fs count=256 bs=4k

2.對文件fs格式化

格式化后的fs文件大小依然是1M,但內(nèi)容已經(jīng)不是全零。

3.用dumpe2fs工具查看這個分區(qū)的超級塊和塊組描述表信息

(base) leon\@pc:\~/nfs/linux\$ dumpe2fs fs
dumpe2fs 1.42.13 (17-May-2015)
Filesystem volume name: <none>
Last mounted on: <not available>
Filesystem UUID: a00715b2-528b-4ca6-8c2b-953389a5ab00
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: ext_attr resize_inode dir_index filetype sparse_super
large_file
Filesystem flags: signed_directory_hash
Default mount options: user_xattr acl
Filesystem state: clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 128
Block count: 1024
Reserved block count: 51
Free blocks: 986
Free inodes: 117
First block: 1
Block size: 1024
Fragment size: 1024
Reserved GDT blocks: 3
Blocks per group: 8192
Fragments per group: 8192
Inodes per group: 128
Inode blocks per group: 16
Filesystem created: Fri Aug 21 16:48:02 2020
Last mount time: n/a
Last write time: Fri Aug 21 16:48:02 2020
Mount count: 0
Maximum mount count: -1
Last checked: Fri Aug 21 16:48:02 2020
Check interval: 0 (<none>)
Reserved blocks uid: 0 (user root)
Reserved blocks gid: 0 (group root)
First inode: 11
Inode size: 128
Default directory hash: half_md4
Directory Hash Seed: e5c519af-d42e-43b5-bc8d-c67c5a79bcbe
Group 0: (Blocks 1-1023)

主 superblock at 1, Group descriptors at 2-2
保留的GDT塊位于 3-5
Block bitmap at 6 (+5), Inode bitmap at 7 (+6)
Inode表位于 8-23 (+7)
986 free blocks, 117 free inodes, 2 directories
可用塊數(shù): 38-1023
可用inode數(shù): 12-128
(base) leon\@pc:\~/nfs/linux\$

塊大小1024字節(jié),一共1024個塊,第0塊是啟動塊,第一個塊開始才是EXT2文件系統(tǒng),

Group0占據(jù)1到1023個塊,共1023個塊。

超級塊在塊1,GDT2,預(yù)留3-5,

塊位圖在塊6,占用一個塊,1024x8=8192bit,足夠表示1023個塊,只需一個塊就夠了;

inode bitmap在塊7

inode表在8-23,占用16個塊,默認(rèn)每8K對應(yīng)一個inode,共1M/8K=128個inode。每個inode占用128字節(jié),128x128=16k

4用普通文件制作的文件系統(tǒng)也可以像磁盤分區(qū)一樣mount到某個目錄

$ sudo mount -o loop fs /mnt/

-o loop選項(xiàng)告訴mount這是一個常規(guī)文件,不是塊設(shè)備,mount會把它的數(shù)據(jù)當(dāng)作分區(qū)格式來解釋;

文件系統(tǒng)格式化后,在根目錄自動生成三個字目錄: ., …, lost+found

Lost+found目錄由e2fsck工具使用,如果在檢查磁盤時發(fā)生錯誤,就把有錯誤的塊掛在這個目錄下。

現(xiàn)在可以在/mnt 讀寫文件,umount卸載后,確保所有改動都保存在fs文件中了。

5.解讀fs二進(jìn)制文件

od  –tx1 –Ax fs

開頭行表示省略全零數(shù)據(jù)。

000000開始的1KB是啟動塊,由于不是真正的磁盤,這里全零;

000400到0007ff是1KB的超級塊,對照dumpe2fs輸出信息對比如下:

超級塊:

Ext2各字段按小端存儲。

塊組描述符

整個文件系統(tǒng)1M,每個塊1KB,一共1024個塊,除了啟動塊0,其他1-1023全部屬于group0.

Block1是超級塊,塊位圖Block6,inode位圖Block7,inode表從Block8開始,由于超級塊中指出每個塊組有128inode,每個inode大小128字節(jié),因此共占用16個塊(8-23)從Block24開始就是數(shù)據(jù)塊。

查看塊位圖,6x1024=0x1800

前37位(ff ff ff ff 1f)已經(jīng)被占用,空閑塊是連續(xù)的Block38-1023=986 free blocks

查看inode位圖,7x1024=0x1c00

已用11個inode中,前10個inode是被ext2文件系統(tǒng)保留的,其中第二個inode是根目錄,第11個inode是lost+found目錄。塊組描述符也指出該組有兩個目錄,就是根目錄和lost+found目錄。

解析根目錄的inode,地址Block8*1024+inode2(1*128)=0x2080

st_mode用八進(jìn)制表示,包含了文件類型和權(quán)限,最高位4表示為文件類型目錄,755表示權(quán)限,size是大小,說明根目錄現(xiàn)在只有一個塊。Links=3表示根目錄有三個硬鏈接,分別是根目錄下的

和lost+found字目錄下的,這里的Blockcount是以512字節(jié)為一個塊統(tǒng)計(jì)的,磁盤最小讀寫單位一個扇區(qū)(Sector)512字節(jié),而不是格式化文件系統(tǒng)時指定的塊大小。所以Blockcount是磁盤的物理塊數(shù)量,而不是分區(qū)的邏輯塊數(shù)量。

根據(jù)上圖Block[0]=24塊,在文件系統(tǒng)中24x1024字節(jié)=0x6000,從od命令查找0x6000地址

目錄數(shù)據(jù)塊由許多不定長記錄組成,每條記錄描述該目錄下的一個文件;

記錄1,inode號為2,就是根目錄本身,記錄長12字節(jié),文件名長度1(“.”),類型2;

記錄2,inode號為2,也是根目錄本身,記錄長12字節(jié),文件名長度(“…”),類型2;

記錄3,inode號為11,記錄長1000字節(jié),文件名長度(”lost+found”),類型2;

debugfs命令,不需要mount就可以查看這個文件系統(tǒng)的信息

debugfs fs

stat / cd ls 等

將fs掛載,在根目錄創(chuàng)建一個hello.txt文件,寫入內(nèi)容”hello fs!”,重新解析根目錄

查看塊位圖

可見前38bit被占用,第38塊地址38x1027=0x9800

查看inode位圖,7x1024=0x1c00

由圖知,用掉了12個inode

查看根目錄的數(shù)據(jù)塊內(nèi)容

debug fs查看t.txt屬性
stat t.txt

t.txt文件inode號=12

Inode12的地址=Block8*1024+inode12(11*128)=0x2580

查看t.txt的inode

文件大小10字節(jié)=stlen(“hello fs!”),數(shù)據(jù)塊地址0x26x1024 = 0x9800

查看內(nèi)容

2.3數(shù)據(jù)塊尋址

如果一個文件很大,有多個數(shù)據(jù)塊,這些塊可能不是連續(xù)存放的,那如何尋址所有塊呢?

在上面根目錄數(shù)據(jù)塊是通過inode的索引項(xiàng)Blocks[0]找到的,實(shí)際上這樣的索引項(xiàng)一共有15個,從Blocks[0]到Blocks[14],每個索引項(xiàng)占4字節(jié),前12個索引項(xiàng)都表示塊編號,例如上面Blocks[0]保存塊24,如果塊大小是1KB,這樣就可以表示12KB的文件,剩下的三個索引項(xiàng)Blocks[12]~

Blocks[14],如果也這么用,就只能表示最大15KB文件,這遠(yuǎn)遠(yuǎn)不夠。實(shí)際上剩下的這3個索引項(xiàng)都是間接索引,Blocks[12]所指向的間接尋址塊(Indirect Block),其中存放類似Blocks[0]這種索引,再由索引項(xiàng)指向數(shù)據(jù)塊。假設(shè)塊大小是b,那么一級間接尋址加上之前的12個索引項(xiàng),最大可尋址b/4+12個數(shù)據(jù)塊=1024/4+12=268KB的文件。

同理Blocks[13]作為二級尋址,最大可尋址(b/4)*(b/4)+12=64.26MB,Blocks[14]作為三級尋址,最大可尋址(b/4)*(b/4) *(b/4)+12=16.06GB

可見,這種尋址方式對于訪問不超過12數(shù)據(jù)塊的小文件,是非常快的。訪問任意數(shù)據(jù)只需要兩次讀盤操作,一次讀Inode,一次讀數(shù)據(jù)塊。

而訪問大文件數(shù)據(jù)最多需要5次讀盤操作,inode,一級尋址塊、二級尋址塊、三級尋址塊、數(shù)據(jù)塊。

實(shí)際上磁盤中的inode和數(shù)據(jù)塊往往會被內(nèi)核緩存,讀大文件的效率也不會太低。

在EXT4,支持Extents,其描述連續(xù)數(shù)據(jù)塊的方式,可以節(jié)省元數(shù)據(jù)空間。

2.4文件和目錄操作的系統(tǒng)函數(shù)

Linux提供一些文件和目錄操作的常用系統(tǒng)函數(shù),文件操作命令比如ls,

cp,mv等都是基于這些系統(tǒng)調(diào)用實(shí)現(xiàn)的。

stat:

讀取文件的inode, 把inode中的各種文件屬性填入struct stat結(jié)構(gòu)體返回;

假如讀一個文件/opt/file,其查找順序是:

  • 1.讀出inode表中第2項(xiàng),也就是根目錄的inode,從中找出根目錄數(shù)據(jù)塊的位置

  • 2.從根目錄的數(shù)據(jù)塊中找出文件名為opt的記錄,從記錄中讀出它的inode號

  • 3. 讀出opt目錄的inode,從中找出它的數(shù)據(jù)塊的位置

  • 4. 從opt目錄的數(shù)據(jù)塊中找出文件名為file的記錄,從記錄中讀出它的inode號

  • 5.讀出file文件的inode

還有另外兩個類似stat的函數(shù):fstat(2)函數(shù),lstat(2)函數(shù)

access(2):

檢查執(zhí)行當(dāng)前進(jìn)程的用戶是否有權(quán)限訪問某個文件,access去取出文件inode中的st_mode字段,比較訪問權(quán)限,返回0表示允許訪問,-1不允許。

chmod(2)和fchmod(2):

改變文件的訪問權(quán)限,也就是修改inode中的st_mode字段。

chmod(1)命令是基于chmod(2)實(shí)現(xiàn)的。

chown(2)/fchown(2)/lchown(2):

改變文件的所有者和組,也就是修改inode中的User和Group字段。

utime(2):

改邊文件訪問時間和修改時間,也就是修改inode中的atime和mtime字段。touch(1)命令是基于utime實(shí)現(xiàn)的。

truncate(2)/ftruncate(2):

截?cái)辔募?,修改inode中的Blocks索引項(xiàng)以及塊位圖中的bit.

link(2):

創(chuàng)建硬鏈接,就是在目錄的數(shù)據(jù)塊中添加一條記錄,其中的inode號字段與源文件相同。

syslink(2):

創(chuàng)建符號鏈接,需要創(chuàng)建一個新的inode,其中st_mode字段的文件類型是符號鏈接。指向路徑名,不是inode,替換掉同名文件,符號鏈接依然可以正常訪問。

ln(1)命令是基于link和symlink函數(shù)實(shí)現(xiàn)的。

unlink(2):

刪除一個鏈接,如果是符號鏈接則釋放符號鏈接的inode和數(shù)據(jù)塊,清除inode位圖和塊位圖中相應(yīng)位。如果是硬鏈接,從目錄的數(shù)據(jù)塊中清除文件名記錄,如果當(dāng)前文件的硬鏈接數(shù)已經(jīng)是1,還要刪除它,同時釋放inode和數(shù)據(jù)塊,清除inode位圖和塊位圖相應(yīng)位,這時文件就真的刪除了。

rename(2):

修改文件名,就是修改目錄數(shù)據(jù)塊中的文件名記錄,如果新舊文件名不在一個目錄下,則需要從原目錄數(shù)據(jù)中清楚記錄,然后添加到新目錄的數(shù)據(jù)塊中。mv(1)命令是基于rename實(shí)現(xiàn)的。

readlink(2):

從符號鏈接的inode或數(shù)據(jù)塊中讀出保存的數(shù)據(jù)。

rmdir(2):

刪除一個目錄,目錄必須是空的(只含.和…)才能刪除,釋放它的inode和數(shù)據(jù)塊,清除inode位圖和塊位圖的相應(yīng)位,清除父目錄數(shù)據(jù)塊中的記錄,父目錄的硬鏈接數(shù)減1,rmdir(1)命令是基于rmdir函數(shù)實(shí)現(xiàn)的。

opendir(3)/readdir(3)/closedir(3):

用于遍歷目錄數(shù)據(jù)塊中的記錄。

目錄,是一個特殊的文件,其存放inode號與文件名的映射關(guān)系;

2.5VFS

Linux支持各種文件系統(tǒng)格式,ext2,ext3,ext4,fat,ntfs,yaffs等,內(nèi)核在不同的文件系統(tǒng)格式之上做了一個抽象層,使得文件目錄訪問等概念成為抽象層概念,對APP提供統(tǒng)一訪問接口,由底層驅(qū)動去實(shí)現(xiàn)不同文件系統(tǒng)的差異,這個抽象層叫虛擬文件系統(tǒng)(VFS, Virtual Filesystem)。

File,dentry,inode,super_block這幾個結(jié)構(gòu)體組成了VFS的核心概念。

icache/dcache

訪問過的文件或目錄,內(nèi)核都會做cache;

inode_cachep = kmem_cache_create()
dentry_cache=KMEM_CACHE()

這兩個函數(shù)申請的slab可以回收,內(nèi)存自動釋放;

Linux配置回收優(yōu)先級

(1).free pagecache:

echo 1 >> /proc/sys/vm/drop_caches

(2)free reclaimable slab objects (includes dentries and inodes)

echo 2 >> /proc/sys/vm/drop_caches

(3)free slab objects and pagecache:

echo 3 >> /proc/sys/vm/drop_caches

fuse

Linux支持用戶空間實(shí)現(xiàn)文件系統(tǒng),fuse實(shí)際上是把內(nèi)核空間實(shí)現(xiàn)的VFS支持接口,放到用戶層實(shí)現(xiàn)。

三、文件系統(tǒng)一致性

3.1掉電與文件系統(tǒng)一致性

由上一節(jié)文件系統(tǒng)的布局分析可知,當(dāng)操作一個文件時,比如往/a目錄下添加一個b,即添加/a/b文件,需要修改inode bitmap, inode table, block bitmap, data block。

這一系列的操作是非原子的,假如任何一個環(huán)節(jié)掉電,造成某些步驟丟失,就會造成數(shù)據(jù)的不完整,文件將無法正常訪問。

3.2append一個文件的全流程

而硬件是不可能原子執(zhí)行的,因此會造成不一致性。

3.3 模擬文件系統(tǒng)不一致性案例

(1) 做一個image,用來模擬磁盤

dd if=/dev/zero of=image bs=1024 count=4096

(2).格式化為ext4文件系統(tǒng)

mkfs.ext4 -b 4096 image

(3).mount到test目錄,寫入一個ok.txt文件

sudo mount -o loop image test/
cd test/
sudo touch ok.txt
cd ..
sudo umount test

(4).查看磁盤詳細(xì)信息

(base) leon\@pc:\~/io\$ dumpe2fs image
dumpe2fs 1.42.13 (17-May-2015)
Filesystem volume name: <none>
Last mounted on: /home/leon/io/test
Filesystem UUID: 759835e3-9508-4c57-b511-9c4f7e13f0ad
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)



Inode count: 1024
Block count: 1024
Reserved block count: 51
Free blocks: 982
Free inodes: 1012
First block: 0
Block size: 4096
Fragment size: 4096
Blocks per group: 32768
Fragments per group: 32768
Inodes per group: 1024



First inode: 11
Inode size: 128
Default directory hash: half_md4
Directory Hash Seed: 9cf91d57-8528-4d39-b6ba-5f8e2e86fcb7
Group 0: (Blocks 0-1023) [ITABLE_ZEROED]
Checksum 0x240a, unused inodes 1012
主 superblock at 0, Group descriptors at 1-1
Block bitmap at 2 (+2), Inode bitmap at 18 (+18)
Inode表位于 34-65 (+34)
982 free blocks, 1012 free inodes, 2 directories, 1012個未使用的inodes
可用塊數(shù): 8-17, 19-33, 67-1023
可用inode數(shù): 13-1024

(base) leon\@pc:\~/io\$

可以看到inode bitmap在18個塊。

(5).查看inodebitmap塊

dd if=image bs=4096 skip=18 | hexdump -C -n 32

由于ext4默認(rèn)用掉11個inode,新創(chuàng)建的ok.txt文件后,inode bitmap用掉12位。

(6).現(xiàn)在模擬掉電,修改inode bitmap

vim -b image
:%!xxd –g 1
:%!xxd –r

找到inode bitmap塊對應(yīng)地址4096*18=0x12000

將bitmap改為ff 07

電導(dǎo)致的不一致性,會出現(xiàn)各種奇怪的問題,甚至都無法修復(fù);

任何軟件的手段只能保持一致性,無法保證不丟失數(shù)據(jù)。

fsck

人為破壞data block,用fsck修復(fù),修復(fù)原理,掃描bitmap和inode table的一致性。早期Linux/Windows系統(tǒng)異常掉電后啟動,都用fsck修復(fù)磁盤,速度很慢,為提高速度,新系統(tǒng)都采用日志系統(tǒng)方式。

3.4文件系統(tǒng)的日志

將要修改的行為,記錄為一個日志,若操作磁盤過程掉電,開機(jī)根據(jù)日志回放,將磁盤操作全部重做一遍。磁盤操作完成,刪除日志。

優(yōu)點(diǎn):保持文件系統(tǒng)的一致性,也提高速度。

EXT2/3/4都采用日志系統(tǒng)。

日志的幾個階段:

  • 1.開始寫日志

  • 2.日志區(qū)寫完日志,commited;

  • 3.執(zhí)行完一條日志,磁盤操作完成,checkpoint。

  • 4.操作完成,free日志。

完整的日志方式,相當(dāng)于每個數(shù)據(jù)都寫了兩遍,讓系統(tǒng)變很慢,實(shí)際工程上會根據(jù)數(shù)據(jù)情況,做部分日志,即日志方式分為三種:速度遞增,安全性遞減

data=journal: 完整日志;

data = ordered: 只寫元數(shù)據(jù),且先寫完數(shù)據(jù)塊,再寫元數(shù)據(jù)

data=writeback 只寫元數(shù)據(jù),循序不確定;ubuntu默認(rèn)方式;

這樣日志就分為5個階段:

3.5文件系統(tǒng)的調(diào)試工具

創(chuàng)建一個文件t.txt,df –h

(base) leon@pc:~/io$ **sudo debugfs -R 'stat /home/leon/io/t.txt' /dev/sda7**

sudo: 無法解析主機(jī):pc: 連接超時
debugfs 1.42.13 (17-May-2015)
Inode: 13896517 Type: regular Mode: 0664 Flags: 0x80000
Generation: 507799365 Version: 0x00000000:00000001
User: 1000 Group: 1000 Size: 17
File ACL: 0 Directory ACL: 0
Links: 1 Blockcount: 8
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x5f40e439:2182bba0 -- Sat Aug 22 17:24:09 2020
atime: 0x5f40e43b:c9589800 -- Sat Aug 22 17:24:11 2020
mtime: 0x5f40e439:2182bba0 -- Sat Aug 22 17:24:09 2020
crtime: 0x5f40e439:208e98b8 -- Sat Aug 22 17:24:09 2020
Size of extra inode fields: 32
EXTENTS:
(0):55636171
(END)

得到文件的數(shù)據(jù)塊55636171

查看數(shù)據(jù)塊內(nèi)容

sudo **blkcat** /dev/sda7 55636171
sudo dd if=/dev/sda of=1 skip=$((55636171*8+824123392)) bs=512c count=1
sudo debugfs -R 'icheck 55636171' /dev/sda7

debugfs 根據(jù)塊號查inode號

sudo debugfs -R 'icheck 55636171' /dev/sda7

根據(jù)inode號,查文件路徑

sudo debugfs -R 'ncheck 13896517' /dev/sda7

3.6Copy On Write文件系統(tǒng): btrfs

不用日志,實(shí)現(xiàn)文件系統(tǒng)一致性。每次寫磁盤時,先將更新數(shù)據(jù)寫入一個新的block,當(dāng)新數(shù)據(jù)寫入成功之后,再更新相關(guān)的數(shù)據(jù)結(jié)構(gòu)指向新block。

COW穩(wěn)健性系統(tǒng)的實(shí)現(xiàn)方式,有利于實(shí)現(xiàn)子卷和snapshot,類似git的思想:

四、塊I/O流程與I/O調(diào)度器

4.1一個塊IO的一生

從page cache到bio到request,當(dāng)APP打開一個文件,內(nèi)核為文件維護(hù)一個pagecache(磁盤的一個副本);

讀寫文件時如果page cache命中,只會讀寫內(nèi)存不操作磁盤;沒有命中,才操作磁盤。

在內(nèi)核用file結(jié)構(gòu)體表示,可見其包含一個inode結(jié)構(gòu)體,一個地址空間;

相關(guān)的幾個結(jié)構(gòu)體在內(nèi)核對應(yīng)關(guān)系如下:

可見,當(dāng)多個進(jìn)程同時打開同一個文件時,不同的file結(jié)構(gòu)體對應(yīng)同一個inode和同一個地址空間,地址空間是由一顆radixtree維護(hù)(即pagecache),讀寫文件時,查看對應(yīng)內(nèi)存頁在page cache中是否命中,若命中直接從內(nèi)存空間讀寫;若不命中,申請一個內(nèi)存頁,從磁盤讀入數(shù)據(jù),掛到page cache的radix tree中。

另外,page cache與磁盤的同步由地址空間操作函數(shù)readpage/writepage完成

對磁盤訪問,有兩種方法:

  • a.裸磁盤直接訪問;

  • b.通過文件系統(tǒng)訪問;

它們在內(nèi)核page cached對應(yīng)關(guān)系如下:

一個address_space對應(yīng)一個inode

free命令統(tǒng)計(jì)的buffer/cached,只是統(tǒng)計(jì)上的區(qū)別;

buffer=操作裸分區(qū)的地址空間+文件系統(tǒng)的元數(shù)據(jù)地址空間;

cached=文件系統(tǒng)的地址空間(page cached)緩存;

但是對同一個磁盤,裸磁盤和文件系統(tǒng)兩種方式同時操作的時候,同一個數(shù)據(jù)塊會被映射到不同的address_space,會產(chǎn)生同步的問題;

在用dd的時候,最好不要操作文件系統(tǒng)數(shù)據(jù):

4.2O_DIRECT和O_SYNC

直接操作裸磁盤分區(qū)用O_DIRECT,內(nèi)核沒有任何cache,直接操作磁盤;用戶可以根據(jù)數(shù)據(jù)特點(diǎn),在用戶空間做cache。O_DIRECT申請內(nèi)存要用posix_memalign接口;

而O_SYNC依然通過page cache,但是會立即寫入同步磁盤;

App通過page cache訪問文件和直接操作裸磁盤的模型,與CPU通過cache訪問內(nèi)存和DMA直接訪問內(nèi)存的模型非常類似;

這里page cache是內(nèi)存,file是磁盤分區(qū)數(shù)據(jù)塊。當(dāng)有一個進(jìn)程啟用O_DIRECT模式,其他進(jìn)程最好也用O_DIRECT;

4.3BIO 流程blktrace

對于一個pagecache地址空間,指向的是page頁,文件系統(tǒng)ext4讀取inode,將page轉(zhuǎn)化為磁盤數(shù)據(jù)塊,變成BIO請求;

BIO最終要轉(zhuǎn)化成request,然后request被塊設(shè)備驅(qū)動程序調(diào)用完成;

Bio經(jīng)過三個隊(duì)列變成request,三進(jìn)三出

step1:原地蓄勢

把bio轉(zhuǎn)化為request,把request放入進(jìn)程本進(jìn)程的plug隊(duì)列;蓄勢多個request后,再進(jìn)行泄洪。

可能合并相鄰bio為一個request;

Bio數(shù)量>=request數(shù)量;

多個進(jìn)程,泄洪request到電梯;

step2.電梯排序

進(jìn)程本地的plug隊(duì)列的request進(jìn)入到電梯隊(duì)列,進(jìn)行再次的合并、排序,執(zhí)行QoS的排隊(duì),之后按照QoS的結(jié)果,分發(fā)給dispatch隊(duì)列。電梯內(nèi)部的實(shí)現(xiàn),可以有各種各樣的隊(duì)列。

比如兩個進(jìn)程需要讀鄰近的數(shù)據(jù)塊,可以合并為一個request

電梯調(diào)度層可以做QoS,設(shè)定進(jìn)程訪問IO的優(yōu)先級;

step3.分發(fā)執(zhí)行dispatch

電梯分發(fā)的request,被設(shè)備驅(qū)動的request_fn()挨個取出來,派發(fā)真正的硬件讀寫命令到硬盤。這個分發(fā)的隊(duì)列,一般就是我們在塊設(shè)備驅(qū)動里面見到的request_queue了。request_queue完成真正的硬件操作;

4.4工具ftrace

do.sh

#!/bin/bash

debugfs=/sys/kernel/debug
echo nop > $debugfs/tracing/current_tracer
echo 0 > $debugfs/tracing/tracing_on
echo `pidof read` > $debugfs/tracing/set_ftrace_pid
echo function_graph > $debugfs/tracing/current_tracer
echo vfs_read > $debugfs/tracing/set_graph_function
echo 1 > $debugfs/tracing/tracing_on

執(zhí)行./read讀文件

查看trace過程

sudo cat /sys/kernel/debug/tracing/trace > t.txt

用VIM查看t.txt,用.funcgrahp.vim插件打開可以合并/展開對應(yīng)函數(shù)調(diào)用

vim -S ~/.funcgrahp.vim

4.5IO調(diào)度算法,CFQ和ionice

查看當(dāng)前系統(tǒng)的IO調(diào)度算法

(base) leon\@pc:/sys/block/sda/queue\$ cat scheduler

noop deadline [cfq]

修改調(diào)度算法:

sudo echo cfg > scheduler

CFQ調(diào)度算法:類似進(jìn)程調(diào)度的CFS算法

ionice –help
ionice –c 2 –n 0 dd if=/dev/sda of=/dev/null & //設(shè)置nice值=0
ionice –c 2 –n 8 dd if=/dev/sda of=/dev/null & //設(shè)置nice值=8

iotop查看IO讀寫速度,有明顯差異

改成deadline策略

echo deadline > scheduler

此時盡管優(yōu)先級不同,但兩個進(jìn)程IO速度相近了;

4.6cgroup與IO

cd /sys/fs/cgroup/blkio

a.創(chuàng)建群組A,B

Mkdir A;makedir B

cat blkiolweight //默認(rèn)是500

分別把兩個進(jìn)程放到A和B執(zhí)行

cgexec -g blkio:A dd if=/dev/sda of=/dev/null iflag=direct &
cgexec -g blkio:B dd if=/dev/sda of=/dev/null iflag=direct &

查看io占用iotop

默認(rèn)是相近

b.修改群權(quán)重

echo 50 > blkio.weight

兩個進(jìn)程優(yōu)先級一樣,但I(xiàn)O速度差別很大;

c.cgroup還可以控制閥門,限制最大讀速度:

8:0磁盤的主次設(shè)備號

echo "8:0 1048576" > /sys/fs/cgroup/blkio/A/blkio.throttle.read_bps_device

帶cache讀寫,這里的限速不起作用;direct方式才生效;

cgroup v2版本支持帶cache限速

4.6IO性能調(diào)試:iotop, iostat

blktrace跟蹤硬盤的各個讀寫點(diǎn)

blktrace –d /dev/sda –o - |blkparse –I –
dd if=main.c of=t.txt oflag=sync
debugfs –R 'ickeck xxxx’ /dev/sda1
debugfs –R 'nckeck inode’ /dev/sda1
blkcat /dev/sda1 xxx

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多