九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
計算機底層知識拾遺(四)理解文件系統(tǒng)
                  操作系統(tǒng)的很多核心組件都是相互關聯的,比如虛擬內存管理,物理內存管理,文件系統(tǒng),緩存系統(tǒng),IO,設備管理等等,都要放在一起來看才能從整體上理解各個模塊到底是如何交互和工作的。這個系列的目的也就是從整體上來理解計算機底層硬件和操作系統(tǒng)的一些重要的組件是如何工作的,從而來指導應用層的開發(fā)。這篇講講文件系統(tǒng)的重要概念,為后面的IO系統(tǒng)做鋪墊。


文件系統(tǒng)主要有三類

1. 位于磁盤的文件系統(tǒng),在物理磁盤上存儲文件,比如NTFS, FAT, ext3, ext4

2. 虛擬文件系統(tǒng),在內核中生成,沒有物理的存儲介質

3. 網絡文件系統(tǒng),位于遠程主機上的文件系統(tǒng),通過網絡訪問


一個操作系統(tǒng)可以支持多種底層不同的文件系統(tǒng),為了給內核和用戶進程提供統(tǒng)一的文件系統(tǒng)視圖,Linux在用戶進程和底層文件系統(tǒng)之間加入了一個抽象層,即虛擬文件系統(tǒng)(Virtual File System, VFS),進程所有的文件操作都通過VFS,由VFS來適配各種底層不同的文件系統(tǒng),完成實際的文件操作。

通俗的說,VFS就是定義了一個通用文件系統(tǒng)的接口層和適配層,一方面為用戶進程提供了一組統(tǒng)一的訪問文件,目錄和其他對象的統(tǒng)一方法,另一方面又要和不同的底層文件系統(tǒng)進行適配。

VFS采用了面向對象的思路來設計它的核心組件,只是VFS是用C寫的,沒有對象的語法,只能用struct來表示。我們按照面向對象的思路來理解VFS。

它有4個主要的對象類型:

1. 超級塊對象,代表一個具體的已安裝(mount)的文件系統(tǒng)

2. inode對象,表示一個具體的文件

3. 目錄項對象,代表一個目錄項,是路徑的一部分,比如一個路徑 /home/foo/hello.txt,那么目錄項有home, foo, hello.txt

4. 打開文件對象,表示一個打開的文件,有讀寫的pos位置,也叫文件句柄,說白了就是open系統(tǒng)調用在內核創(chuàng)建的一個數據結構


VFS給每個對象都定義了一組操作對象(函數指針),給出了這些操作的默認實現,底層不同的文件系統(tǒng)可以重寫(override)VFS的操作函數來給出自己的具體操作實現,也可以復用VFS的默認實現。實際情況是底層文件系統(tǒng)部分操作由自己單獨實現,部分復用了VFS的默認實現。

操作對象有:

1. super_operations對象,針對超級塊對象,包含了內核對特定文件系統(tǒng)所能調用的方法,比如wirte_inode(),sync_fs()等

2. inode_operations對象,針對inode對象,包含了內核對特定文件所能調用的方法,比如create(), link()等

3. dentry_operations對象(directory entry),針對目錄項對象,包含了內核對特定目錄所能調用的方法,比如d_compare()和d_delete()方法等

4. file_operations對象,針對打開文件對象,包含了進程對打開文件對象所能調用的方法,比如read()和write()等


文件系統(tǒng)說白了就是文件內容和存儲系統(tǒng)對應的塊的映射關系,是來管理文件的存儲的。inode-block結構把文件分為了兩部分,inode表示元數據,block表示存儲文件內容的具體的邏輯塊。VFS沒有用單獨的對象來表示block,block的屬性在超級塊和inode塊中包含了。


下面這張圖包含了VFS的主要對象和操作對象,以及對象之間的指針指向關系,


1. 可以看到對象都維護了一個X_op指針指向它所對應的操作對象。

2. 超級塊維護了一個s_files指針指向了內核所有的打開文件對象的鏈表,這個信息是所有進程共享的

3. 目錄向對象和inode對象都維護了一個X_sb指針指向超級塊對象,從而可以獲得整個文件系統(tǒng)的元數據信息

4. 目錄項對象和inode對象各自維護了指向對方的指針,可以找到對方的數據

5. 打開文件對象維護了一個f_dentry對象,指向了它對應的目錄項對象,從而可以根據目錄項對象找到它對應的inode信息

6. task_struct表示進程對象,維護了一個files指針,指向了進程打開的文件鏈表,這個是進程單獨的視圖,進程還維護了文件描述符表(file descriptor, fd),所謂的文件描述符就是一個整數,這個數字就是文件描述符表的索引,表項里面存著對應的打開文件對象的指針,所以進程操作打開文件的系統(tǒng)調用只需要傳遞一個文件描述符即可。由內核來維護打開文件對象,進程只能看到文件描述符這個整數

7. address_space也是一個重要的對象,它表示一個文件在頁緩存中已經緩存了的物理頁,內部維護了一個樹結構來指向所有的物理頁結構page,同時維護了一個host指針指向inode對象來獲得文件的元數據。會在說頁緩存的時候再來看address_space

超級塊

先來看一下超級塊,它包含了一個文件系統(tǒng)的元數據。超級塊到底是如何存儲在磁盤上的呢?在這篇計算機底層知識拾遺(三)理解磁盤的機制 我們說了磁盤的最小物理單元是扇區(qū),一個扇區(qū)512個字節(jié)。塊就是這樣說的block,是表示磁盤數據的最小邏輯單元,1個塊一般有1kb, 2kb, 4kb, 8kb等,所以1個邏輯塊block對應多個物理扇區(qū)。整個磁盤的第一個扇區(qū)存放著計算機的引導(boot)信息MBR(Master Boot Record),MBR存放著磁盤的邏輯分區(qū)表,如果磁盤的第一個扇區(qū)壞了導致分區(qū)表丟失,那么整個計算機就啟動不了了。操作系統(tǒng)把邏輯分區(qū)也認為是單獨的邏輯磁盤,所以實際上每個邏輯分區(qū)的第一個扇區(qū)也可以存放MBR,這也是為什么一臺計算機可以安裝多個操作系統(tǒng)的原因。

除了第一個啟動扇區(qū),其他的扇區(qū)都被邏輯上劃分到不同的塊組Block Group了,如下圖所示

而每個塊組則包括了超級塊和這個塊組內的inode, block數據。一個塊組的數據在物理上也是連續(xù)的,所以實際給文件分配block時會優(yōu)先在同一個塊組分配。

我們說了一個文件系統(tǒng)只有一個超級塊,所以只有第一個塊組的第一個塊是超級塊,其他塊組都是超級塊的備份,防止超級塊損壞導致整個文件系統(tǒng)損壞。

我們可以看到塊組的結構如下:

1. 超級塊,存放著整個文件系統(tǒng)的元數據

2. 塊組描述信息,存放這該塊組的元數據,可以在后面的實例看到

3. block位圖,磁盤采用了位圖的方式來記錄哪些塊被使用了,哪些塊未被使用,位圖中的1位表示一個塊的塊號

4. inode位圖,同樣inode位圖表示了哪些inode被使用了,哪些未被使用,位圖中的1位表示一個inode的號

5. inode表,是該塊組所有的inode實際的存儲塊

6. block塊,是該塊組所有的block塊

可以用dumpe2fs命令來查看超級塊的信息和所有的塊組信息,我們來看個例子

首先用df命令來看文件系統(tǒng)是如何掛載的, 我們看到安裝文件系統(tǒng)的邏輯分區(qū)/dev/sda6 掛載在了/根目錄

然后用  sudo dumpe2fs /dev/sda6來查看超級塊和塊組信息, 超級塊的信息可以看到整個文件系統(tǒng)的inode和block數量,未使用的inode和block數量,block大小,這里是4KB

再看block group的數據,我們可以看到inode和block的數量/號是均勻分配在不同的block group里面的,同時每個block group還記錄了該組的inode和block使用情況。

由于block大小是固定,扇區(qū)的大小也是固定的,所以可以很方便地計算出某個塊號對應著哪個扇區(qū)號,知道了這個信息,磁盤控制器就能很快地根據塊號去對應的扇區(qū)讀寫數據。

要記住,對于一個設備來說,inode號和block塊號都是唯一的

超級塊的這些數據存放在第一個塊組的第一個塊,所以操作系統(tǒng)很容易就把超級塊的數據加載到內存中,用超級塊對象結構來對應超級塊的實際數據。超級塊對象是常駐內存的,并被緩存的。因為超級塊維護著整個文件系統(tǒng)的元數據信息,所以文件系統(tǒng)的任意元數據修改都要修改超級塊對象。

來看一下超級塊的數據結構,我們上面說了s_file的作用,再說一下另一個重要的字段 s_dirty,它指向了所有臟的inode鏈表,這樣當要回寫所有臟的inode到磁盤時,不需要去遍歷所有的inode,只需要通過s_dirty來遍歷臟的inode鏈表

再看一下超級塊對應的操作對象super_operations,它定義了內核可以對超級塊的操作

inode

Linux的文件系統(tǒng)把inode當做文件的唯一標識,一個文件對應一個inode,如果inode用完了,那么就不能再新建文件了。這篇Java中如何獲得文件的inode信息 說了如何在Java中獲得inode信息。

inode結構保存了一個文件的元數據信息,以及這個文件的內存所在的block,從inode可以找到這個文件所有的block。我們先看一下inode的結構定義

有幾個重要的屬性說一下

1. i_ino記錄了這個inode的編號,這個編號是唯一的

2. i_size記錄了這個文件按字節(jié)計算的大小, i_blocks記錄了按塊記錄的這個文件的塊數,這樣根據單個塊的長度就能計算出按塊計算的長度。我們用ls命令列出的文件大小通常就是按塊計算的長度,因為單個塊只能記錄屬于一個文件的內容

3. i_atime記錄了最后訪問這個文件的時間  i_mtime記錄最后修改這個文件內容的時間  i_ctime記錄了最后修改inode的時間,即修改文件元數據的時間

4. i_count是引用計數,即有多少進程訪問這個inode。當i_count為0的時候這個inode結構才能從內存中被消除。 i_nlink是指向這個文件的硬鏈接的計數,硬鏈接是不會新建inode的

5. Linux幾乎把一切設備和IO都當做文件(除了網絡設備),所以使用了一個聯合來標識設備,i_pipe指向管道,i_bdev指向inode所在的塊設備,i_cdev指向字符設備

6. i_dentry指針指向和這個inode對應的目錄項,目錄和普通文件都是文件,都有Inode,也都有d_entry結構

7. i_sb指針指向超級塊,來獲得文件系統(tǒng)的元數據

8. i_mode維護了該文件的讀寫權限信息, i_uid和i_gid記錄了用戶和組的信息

9. i_mapping指向了address_space,記錄了這個文件被映射的信息


從內存的角度來看inode,一個inode只可能處于3種狀態(tài)之一

1. inode存于內存中,沒有被任何進程引用,不處于活動使用狀態(tài),也沒有被修改過

2. inode存于內存中,正被一個或多個進程引用,即它的i_count和i_nlink都大于0,且文件內容和Inode元數據內容都沒有被修改過

3. inode處于內存中,內容或元數據被修改過,即inode是臟的


內核提供了3個全局的鏈表來管理這3種狀態(tài)的inode,inode_unused對應于第一種情況,inode_in_use對應于第二種情況,超級塊的s_dirty鏈表對應第三種情況。任何時刻內存中的inode只能在這3個鏈表之一,使用了i_list指針指向它所在的鏈表。維護這3個鏈表的好處是,當臟數據寫回到磁盤時,只需要遍歷超級塊 super_block -> s_dirty上所有的inode就行。


VFS并沒有專門的block對象來表示磁盤上塊,inode對象也沒有記錄它對應的具體的塊號,只記錄了所占的塊數。那么一個文件的內容實際存儲的塊的數據是如何記錄的呢?這個是記錄在磁盤中的,并且由磁盤控制器去管理的,磁盤控制器根據一個文件的塊號,就能找到實際物理存儲的塊的位置。

上面描述磁盤塊組結構的圖中可以看到,每個inode號位于哪個塊組是可以很方便計算出來的,每個塊組維護了一個inode表,一個inode的長度是固定的,在是128個字節(jié),那么可以很快找到給定的inode號的inode存儲的物理區(qū)域。在磁盤上存儲的inode數據出來文件的元數據以外,還存儲了這個文件的所有block塊號。

問題來了,既然inode是128個字節(jié),記錄一個block編號就要4字節(jié),那么1個inode根本存不了幾個block編號。所以inode的設計采用了間接映射的方法

1. 1個inode存儲12個直接映射的block編號,占用48個字節(jié)

2. 1個inode存儲一個一次間接塊編號,占用4字節(jié),間接塊對應的實際物理塊不存儲文件的內容,而是用來存儲block編號,比如4KB的塊大小,就可以存儲1000個block編號

3. 1個inode存儲一個二次間接塊編號,占用4字節(jié),二次間接塊的第一層記錄第二次間接塊的編號,第二次間接塊存儲實際的block編號

4. 1個inode存儲一個三次間接塊編號,占用4字節(jié),比二次間接再多一次間接


這種間接的設計在計算機領域很常用,比如頁面也是采用了類似的結構,把一個線性結構變成一個層次結構,一方面可以壓縮存儲空間,另一方面可以表示很大的地址空間。

同樣,我們可以把這個層次結構還原成一個線性結構,可以理解成一個數組,只要提供一個文件的塊號,磁盤控制器就可以快速地定位到具體的存儲塊號的位置,再找到實際存儲的磁盤的塊,也就找到了實際存儲的磁盤扇區(qū)位置。


如果塊是1KB大小的話,inode能夠表示的單個最大文件是16G,如果是2KB的塊,能夠表示的單個最大文件是256G,如果是4KB的塊,能夠表示的單個最大文件是4TB?,F在很多服務器都是8KB的塊,可以表示的單個最大文件足夠大了


inode_operations定義的函數如下


目錄項

目錄和普通文件一樣,都是文件,都有inode,區(qū)別是目錄的塊存儲的是這個目錄下的所有的文件的inode號和文件名等信息。操作系統(tǒng)檢索一個文件,都是從根目錄開始,按層次解析路徑中的所有目錄,直到定位到文件。所以目錄的解析是一個非常頻繁的操作。


VFS抽象了目錄項對象來表示查找路徑中的目錄和文件,查找一個文件都是通過目錄項來的。內核還建立了目錄項緩存來優(yōu)化查找速度。

目錄項 d_entry的結構定義如下,它維護了目錄操作需要的元數據信息,

1. 能通過 d_inode找到該目錄項對應的目錄或文件的inode

2. 通過d_sb找到超級塊

3. 通過d_parent來找到父目錄項,從而構成一個樹形結構

4. d_name記錄了目錄/文件的名稱

和inode一樣,內核維護了兩個全局數據結構來快速尋找所有的d_entry對象,實現了d_entry對象的緩存功能

1. dentry_hashtable存放了所有活動的d_entry對象

2. dentry_unused存放了所有非活動的(d_count引用計數為0)的d_entry對象,這是一個LRU鏈表結構,可以方便地快速釋放非活動的d_entry對象

打開文件對象

所謂的打開文件對象是由open系統(tǒng)調用在內核中創(chuàng)建的,也叫文件句柄。open系統(tǒng)調用返回一個文件描述符,用戶進程所有對文件讀寫操作系統(tǒng)調用都是基于給定的文件描述符進行的,換句話說,所有對文件的讀寫操作,必須基于打開文件對象進行。

多個進程可以同時指向一個打開文件對象(fork時),多個打開文件對象可以指向同一個文件inode。

打開文件對象的結構定義如下

1. f_dentry指向了打開文件對應的目錄項,目錄項又指向了對應的inode,從而把打開文件和inode關聯起來。打開文件對象沒有實際對應的磁盤數據,所以它也不需要表示打開文件對象是否臟,是否需要寫回等標志位

2. f_pos表示文件當前的偏移量

3. f_count表示文件對象的使用計數

4. 文件鎖相關的屬性

從打開文件對象的結構定義可以看出,它只是用來表示打開一個文件的狀態(tài)的抽象,實際文件內容的讀寫是通過read(), write()系統(tǒng)調用完成的,數據的修改存放在頁緩存中,后面會專門講頁緩存的機制。


我們之前說了內核使用 task_struct來表示單個進程的描述符,每個進程維護了它的打開文件信息和文件描述符信息,我們來看一下進程相關的打開文件信息是如何表示的。

task_struct中維護了一個 files_struct的指針來指向它的文件描述符表和打開的文件對象信息

下面看看files_struct的結構

1. fd_array數組是file結構的數組,即表示打開文件對象的數組。NR_OPEN_DEFAULT在64位機器下默認是64,它的目的是方便快速找到64個最初的打開文件對象

2. next_fd用來存儲下一個要生成文件描述符的編號

3. 當進程要打開多于64個文件時怎么辦呢,比如網絡編程中,每個socket請求就是一個打開的文件,如何處理多于64的情況呢,這就得依靠fdtable這個結構,也就是常說的文件描述符表

看一下fdtable 文件描述符表的結構定義

1. max_fds表示當前進程可以打開的最大的文件描述符的數量,這個值不是固定的,是可以調節(jié)的(Rlimit)

2. fd是一個指針數組,指向所有打開的文件,數組的索引就是所謂的文件描述符fd(file descriptor)

3. open_fds是一個用位圖表示的當前打開的文件描述符,可以方便快速遍歷空閑的文件描述符

4. next指針可以指向下一個fdtable結構,這樣文件描述符表可以表示成鏈表結構,也就是支持動態(tài)擴展的,可以保證單個進程可以打開足夠多的文件


進程維護的文件描述符信息和打開文件信息可以用下圖表示

mount

最后再說說文件系統(tǒng)的 mount操作到底做了什么工作。內核維護了一個樹形結構表示文件系統(tǒng)層次結構,文件系統(tǒng)可以掛載到樹形結構之上

理論上目樹上的每個目錄都可以成為裝載點,裝載點可以用來把新的文件系統(tǒng)加入到文件系統(tǒng)的目錄樹上。裝載動作由 mount系統(tǒng)調用完成。每個文件系統(tǒng)都有一個根目錄,當它裝載到裝載點時,會把根目錄的內容替換到裝載點。每個裝載的文件系統(tǒng)都維護了一個vfsmount的結構

1. mnt_mountpoint記錄了該文件系統(tǒng)的裝載點在父文件系統(tǒng)中的dentry對象,即它對應的目錄

2. mnt_root記錄了當前文件系統(tǒng)的根目錄的dentry對象,實際上它和mnt_mountpoint都指向同一個dentry對象,即裝載點

3. mnt_sb指向了這個文件系統(tǒng)的超級塊,我們知道超級塊記錄了這個文件系統(tǒng)的元數據信息

4. mnt_parent指向了父文件系統(tǒng)的vfsmount對象,可以獲得父文件系統(tǒng)的一些裝載信息

關于文件操作相關的更多內容會單獨說文件IO的時候再涉及。


參考資料

《Linux內核設計與實現》

《深入Linux內核架構》

《鳥叔的Linux私房菜》




本站僅提供存儲服務,所有內容均由用戶發(fā)布,如發(fā)現有害或侵權內容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
快速了解操作系統(tǒng)的文件系統(tǒng)設計(在Linux中內核將所有文件組織成一個樹形結構)
fd 與file的關系
理解linux虛擬文件系統(tǒng)VFS
干貨!大話EXT4文件系統(tǒng)完整版
VFS及LINUX中文件系統(tǒng)
聊聊磁盤I/O那些事
更多類似文章 >>
生活服務
熱點新聞
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯系客服!

聯系客服