Linux 虛擬文件系統概述

轉自:http://blog.csdn.net/u011373710/article/details/70198080node

文章梗概

本文首先以「儘可能不涉及源代碼」的方式討論Linux虛擬文件系統的存在的意義、實現方式;後續文章中以讀文件爲例從面到點更有針對性的討論其實現。在討論的過程當中有一些地方可能說的不夠全面,一是能力有限;另外一方面是但願不要陷入過多的細節之中,將注意力集中在框架的設計上。但願讀者有一些編程基礎、操做系統概念。本文討論的Linux內核版本爲2.6.24linux

正文

文件系統

經常使用的文件的讀寫的方式有同步讀寫異步讀寫內存映射。前兩種的主要區別是,同步讀寫會在進程向操做系統發出讀寫請求後被阻塞,直至所請求的操做完成時讀寫的函數纔會返回。也就是說,函數返回的時候數據已經從磁盤中讀到內存中了,或者已經從內存中寫入到磁盤上去了;異步讀寫在發出讀寫請求後,函數會當即返回並不等待操做完成,這樣的話通常有一個回調函數的存在,操做完成時操做系統會調用用戶設置的回調函數;至於內存映射使用的則是缺頁機制。這篇文章中主要圍繞同步讀寫的方式,也是最經常使用的方式,討論Linux的虛擬文件系統的實現原理。 
要想說明白虛擬文件系統的做用就必需要了解文件系統的做用。對於一個特定的文件系統來講,最主要的的設計方面就是數據在磁盤上的存儲方式,這個功能從應用程序猿的角度來看就是文件名到數據的映射 : 經過對 以文件名爲主要的參數調用特定的函數(不一樣的語言、平臺函數不一樣)完成文件的讀寫操做。而磁盤基本上能夠看作一個以扇區(一般爲521Byte)爲基本存儲單位的超大數組。若是有以下的極其簡單的文件系統,算法

極簡文件系統

扇區0存放着一整塊磁盤的屬性,如塊大小、文件系統標記;1~6中存放着文件的屬性和位置信息,每一個文件目錄項佔兩個磁盤塊,也就是說該文件系統的設計最多能夠存放三個文件;一、2中存放着文件test.txt的屬性信息(日期、大小、歸屬)和位置信息;三、4存放着豔照門1.png的屬性信息和位置信息;7~15中存放着實際的數據,不一樣的數據的顏色不一樣。如圖示的文件系統最多隻能存放三個文件,很難有實際的用途,不過用來講明文件系統的做用是很合適的,避免陷入過多的細節之中。 
加入如今用戶想要讀取查看豔照門1.png,那麼文件系統要作的事情是這樣的 :編程

  1. 產生讀入一、2塊磁盤數據的請求,將請求發送給磁盤的驅動程序,等待數據讀入到內存後,查看文件的名字,發現文件的名字並非豔照門1.png
  2. 產生讀入三、4塊磁盤數據的請求,將請求發送給磁盤的驅動程序,等待數據讀入到內存後,查看文件的名字,發現名字匹配正確。而後分析目錄項數據,得知豔照門1.png主要存儲在12~15四個磁盤塊上。
  3. 產生讀入12~15四個磁盤塊的請求,將請求發送給磁盤的驅動程序,等待數據讀入到內存後便可按照png文件的格式解析數據而後將圖像呈如今用戶面前。

這裏有幾點點須要說明一下數組

  1. 真正的讀寫磁盤的操做並非文件系統來完成的,而是特定的磁盤的驅動程序來完成的。磁盤的驅動程序須要的參數是邏輯磁盤塊號對應的內存位置是讀請求仍是寫請求。由磁盤驅動程序來完成讀寫的目的是,實現文件系統的設計和具體磁盤結構的分離。也就是說,咱們上面設計的極簡的文件系統在驅動程序的幫助下不管是在機械硬盤上面,仍是在固態硬盤上面實現方式是相同的。磁盤驅動程序更加的貼近硬件設備,更加的「瞭解」硬件。譬如,若是使用的是固態硬盤那麼驅動程序則不會對讀寫請求進行排序;若是使用的是機械硬盤,則會對讀寫請求進行排序。參考機械硬盤內部硬件結構和工做原理詳解可知對於機械硬盤,按照順序一、二、3讀取磁盤塊的速度是快於一、三、2速度的,因此磁盤驅動程序讀寫請求排序所作的事情簡單講就是將一、三、2這樣的讀寫請求排序成爲一、二、3這樣的順序,而後發送給磁盤控制器(硬件)。而固態硬盤更貴的緣由在於其讀寫過程不包含物理屬性(移動磁頭),請求一、二、3一、三、2和請求二、三、1的速度都是同樣的,因此不須要重排請求。
  2. 1 中提到的請求排序的專業術語叫作I/O調度,這一工做應該是由驅動程序完成的,只不過內核提供了一些的經常使用算法的實現,方便了驅動程序的開發而已。
  3. 一個對特定文件的讀寫請求可能會產生不少個對磁盤的讀寫請求。如上所說,要讀文件首先要把文件的固有屬性讀到內存中加以分析,而後才能去讀實際的文件。

虛擬文件系統架構

上文以比較易懂的方式說明了文件系統一個最重要的工做文件名到磁盤數據的映射,但是單從這一個方面彷佛看不出來虛擬文件系統存在的意義。那是由於,實際上文件系統要考慮不少的事情 :瀏覽器

  1. 首先要考慮的一個重要的方面就是,磁盤和內存的速度差距。衆所周知,磁盤的速度慢可是每單位空間價格更加便宜,而內存的速度基本上是磁盤的105,這之間的差距仍是很是大的。用戶讀寫文件的時候常常存在着這樣的行爲,剛剛讀寫過的文件,頗有可能再次讀寫,因此引入緩存的概念是十分必要的。緩存所作的就是在讀文件數據到內存以後,找個地方把數據存起來,內存不緊張的話,儘量長時間將數據保存在內存之中。若是再次讀文件,文件系統就去查看是否是在緩存中已經有對應的數據了,若有的話就不須要再次去磁盤上讀取數據;實在沒有的話,才須要向驅動程序發出讀請求。用戶是意識不到緩存的存在,因此從用戶的角度來看這將極大的加快了數據的讀取速度。不過要說缺點的話,這樣的設計可能會佔用比較多的內存空間,使文件系統和內存管理變得更加複雜。不過現階段的PC平臺內存並非性能瓶頸,並且爲了速度和用戶體驗這些都是值得的。
  2. 可想而知,緩存的單位指定不能是字節。若是是字節的話須要記錄文件的每一個字節緩存在內存的哪一個位置,這樣是不現實的。緩存的單位至少也是和磁盤扇區同樣大的,或者是多個連續的扇區存放在一塊。以上面的最簡文件系統爲例,能夠很輕鬆的設計一個這樣的數據結構來記錄豔照門1.png的第一塊也就是磁盤索引的12邏輯塊存放在內存的0xFFAA處、第二塊到第四塊存放在0xAABB處。爲了和內存管理模塊更好的交互,Linux中的針對文件數據的緩存的單位爲,一般大小爲4KB,也正是內存管理模塊使用的內存單位。這裏須要讀者有必定的操做系統的基礎,可以大致上理解分頁機制存在的緣由以及作了什麼事情,因爲篇幅緣由這裏只須要知道在Linux中,內核使用struct page結構體來描述內存中的每一頁。找到一頁對應的page結構體就至關於知道了對應的物理頁的物理地址、是否被佔用等等很重要的屬性。這些文字是想說明,文件系統須要和內存管理模塊協調工做,畢竟分配回收緩存的內存空間等操做都是須要內存管理模塊支持的。
  3. 第三個要考慮的部分和第一個相似,這個機制叫作「預讀機制」。相似於,在瀏覽網頁的時候有些瀏覽器可以提供預讀,看完當前的網頁點擊下一頁以後,下一頁面可以很是快的呈如今面前。這裏的預讀機制也就是這個意思,尤爲是對於一些比較大的文件。用戶第一次想要的可能就是前1kb,這個時候文件系統向驅動程序發出的讀請求是大於1kb的,多是4kb、多是1mb,多讀出來的數據就存在上面提到的緩存。能夠看出來預讀機制緩存機制是完美結合的,協同工做的。
  4. 1中提到的這個緩存指的是對文件內容的緩存,還有一種緩存是針對目錄項的緩存。回顧上文,若是讀取豔照門1.png以後,用戶又想查看文件test.txt,文件系統又要屢次讀取1~6塊磁盤來查找是否是有test.txt這個文件。這樣的話,引入一個針對文件名的緩存也是很重要的,這個緩存在Linux內核中叫作目錄項緩存。和上面的緩存相似,只不過是針對目錄項的。
  5. 權限檢測也是文件系統要作的很重要的一個工做,尤爲是在Linux系統上面,常常會遇到讀寫文件時permission denied的狀況。若是沒有管理員權限,操做系統(文件系統)是不會讓你去讀寫一些受保護的文件的,window中可能感覺不是那麼強烈。不過除了這中常規意義上的限制,權限檢測還包括:文件是否越界、是否在讀寫一個目錄項文件等操做。
  6. 每一個進程都有都會以本身特定的方式(讀、寫)打開文件,且文件讀取的當前位置也是特定於進程的,這些屬性是須要記錄的。並且對於一個文件不少時候可能會有多個進程同時去讀寫的狀況,如何協調多個進程之間的讀寫關係,不至於出現錯誤的狀況,也是文件系統設計時須要考慮的一個關鍵方面。
  7. 鎖機制、命名空間等其餘的一些部分。

回顧這麼多設計一個文件系統須要注意的地方能夠發現,上面的幾條對於每個文件系統都是須要的。也就是說,無論是上面提到的極簡文件系統仍是在實際使用中的FAT、EXT文件系統,都須要考慮上面的這些因素。爲了加快內核的開發、方便後續內核的擴展重構、使內核設計的更加優雅這才提出了虛擬文件系統的概念。虛擬文件系統作的事情就是實現了這樣一個框架,在這個框架中上面說起的這些重要因素都有了默認的實現,須要特定的文件系統實現的爲文件相對塊號到磁盤邏輯塊號的映射關係目錄項的解析方式等。 
文件相對塊號就是指的相對於文件來講是第幾塊,磁盤邏輯塊號指的是相對於磁盤來講是第幾塊。緩存

也就是說,若是想讓內核識別極簡文件系統須要該文件系統的設計人員嚴格按照虛擬文件系統的架構編寫須要的函數(都是函數指針的技術實現的),而後將文件系統註冊到內核中去。數據結構

(在這以前文件系統和虛擬文件系統的概念界限是有點模糊的,從這裏開始一直到文章的最後,文件系統只是表示磁盤上的數據存儲的結構,其餘的部分都算在虛擬文件系統裏面的 )架構

虛擬文件系統之因此沒有實現這兩個方面是由於這些性質是特定於一個文件系統的。仍是上面讀取豔照門1.png的例子,在極簡文件系統中豔照門1.png第一塊是存放在磁盤邏輯12塊中的,也就是文件相對塊號1->磁盤邏輯塊號12。而若是使用的是FAT文件系統那麼就但是其餘的映射關係。目錄項的解析方式須要特定的文件系統來實現就更好理解了,不一樣的文件系統其目錄項的字段設置、順序、長度一般是不同的,因此須要讓特定的文件系統來解析目錄項。解析完以後返回一個統一的文件的表示,也就是大名鼎鼎的inodestruct inode的字段很是之多,對於一些沒那麼複雜的文件系統來講多是一種浪費,由於它根本用不到那些複雜的字段。可是虛擬文件系統的設計理念是寧濫勿缺 : 畢竟要儘量地覆蓋全部的文件系統,多的字段你能夠不用,可是若是想用卻沒有那就麻煩大了。框架

這個時候看一下Linux虛擬文件系統的總體結構,再合適不過了。

此處輸入圖片的描述

虛擬文件系統(VFS,virtual file system)須要和各個實際的文件系統ext3、… 、reiserproc交互,大多數文件系統都須要虛擬文件系統提供的緩存機制(Buffer Cache),而proc文件系統不須要緩存機制是由於其是基於內存的文件系統。那麼按照上面的討論其須要作的就是完成文件邏輯塊號內存中數據塊之間的映射關係。再往下一層就是具體的設備驅動層,實際的讀寫操做都是須要設備驅動層去完成的,它下一層就是實際的物理設備了。

虛擬文件系統如何知道可用的文件系統有哪些的

上面提到的特定於文件系統的操做是經過註冊的方式讓虛擬文件系統知道當前內核中支持哪些操做系統,註冊的主要參數有文件系統的名字解析inode的函數(解析目錄項)解析文件相對塊號到磁盤邏輯塊號的函數,這都是上面討論過的關鍵點。對於一些經常使用的文件系統不用註冊也可以使用,這是由操做系統去註冊的。註冊使用的技術就是C語言中的函數指針。註冊完成以後,就能夠經過掛載的方式去使用一個具體的文件系統了。掛載須要的參數有被掛在的設備(本文討論中限定爲磁盤)該設備使用的文件系統(必須已經註冊過)掛載點。若是明白前文的討論,那麼掛載也是很好理解的。以上文的極簡文件系統爲例,豔照門1.png存放在磁盤A中,在磁盤A沒有被掛在以前操做系統(或者說虛擬文件系統)並不知道磁盤A使用的什麼文件系統,因此沒有辦法去讀取它上面的數據並解析之。在用戶執行了操做以極簡文件系統掛載磁盤A到 /home/jingjinhao/片 下以後,在訪問/home/jiangjinhao/片/豔照門1.png的時候虛擬文件系統就會調用極簡系統註冊的函數,去執行前文討論過的尋找豔照門1.png的過程。毫無疑問操做系統須要維護一個目錄之間的層級關係以及不一樣的文件系統之間的掛載關係,這正是struct dentrystruct vfsmound的做用。這之間的具體圖示關係請參考mount過程分析之六——掛載關係(圖解) 。感受本身沒有能力寫出來一篇比這還好的文章,推薦看一下這篇文章。

不太喜歡的環節

上文提到struct inodestrcut dentrystruct vfsmound這三個數據結構都是虛擬文件系統很是重要的部分。雖然不大喜歡扣數據結構,不過爲了下文更好的討論這裏仍是儘可能從原理上羅列一下核心數據結構。

  1. struct address_space這個數據結構是對上文討論過的緩存的抽象。該數據結構能夠提供查找緩存、添加緩存的方法,也就是說對於一個文件找到了其對應的struct address_space就可以增刪改查緩存的內容。暫時沒必要關心起底層的實現是鏈表、數組仍是樹(實際上是基數樹),不管是什麼其提供的功能老是不變的,只不過速度上可能會有差異。查找使用的參數是文件相對頁號,成功返回對應的物理頁幀描述結構struct page的指針(上文描述過),沒有找到的話返回null。這裏的文件相對頁號很好理解,舉例來講在頁大小爲4KB的狀況下,0~4KB對也相對頁號爲0,4KB~8Kb對應的相對頁號爲1,以此類推。
  2. struct inode是對一個文件的抽象,因此其中包含的主要字段有 : 文件的大小、日期、全部者等固有屬性;指向緩存的指針struct address_space *;指向塊設備驅動程序的指針block_device*,由於文件系統並不負責實際的讀寫,須要依靠驅動程序的幫助;一些鎖。這幾大類字段,在上文的討論下都是比較好理解的,須要說明的一點就是inode中是沒有文件的名字這個字段,文件的名字包含在下面的dentry中,因此取而代之的是指向對應文件的struct dentry的指針。這並非說必定不能把文件名存儲在inode中,只不過當前虛擬文件系統的設計使然,再在inode中存儲的話就有點囉嗦了。
  3. struct dentry首先實現了對目錄層次結構的抽象,以下圖目錄層次結構內存中每一個打開的的每一個節點都對應一個struct dentry的實例,須要強調的不只僅目錄有對應的detry實例,普通的文件也有對應的detry,只不過普通文件的detry實例沒有子節點罷了。只有打開的文件或目錄纔有對應的節點,因此內存中樹結構的完整度是磁盤上樹結構的完整度的;沒有在inode中而是在detry存儲文件的名字的一大緣由struct detry負責創建起前文討論過的目錄項緩存(以Hash表的方式)。也就是說在打開一個文件的時候,虛擬文件系統會首先經過文件名查找是否存在一個打開的detry了,若是有的話就大可返回了;detry最後一個重要的做用就是結合struct vfsmount完成了掛載操做的數據結構的支持。上圖中的示例在vfsmount的視圖中以下圖示 vfsmount視圖 
    此外硬連接的實現也是須要detry的支持的。
  4. 正如上面所說虛擬文件系統須要記錄某個進程操做一個文件的方式、當前位置等屬性,這些屬性是特定於一個進程的,一個文件可能同時存在多種被打開的狀態,由此引入了struct file結構體。該結構體是對一次文件操做的抽象,剛剛提到的幾個方面外,file中還包含了預讀相關的一些字段。每一個進程控制塊struct task中都包含一個struct file *的數組,進程打開的每一個文件對應其中的一項,這也解釋了爲何fopen返回的是一個無符號整型了(數組的索引)。
  5. 除了上面討論的四大方面的屬性,還有一個方面的屬性能夠用一個單獨的數據結構抽象出來,這就是特定於一個文件系統的屬性,譬如極簡文件系統的最多隻能存三個文件這相似的屬性,這個結構體叫作struct super_block。以極簡文件系統爲例其對應着磁盤邏輯地址0的塊中的數據。

經過上面的討論就能夠經過下圖來縱觀虛擬文件系統的結構了,該圖引自深刻Linux內核架構中文版418頁,請暫時忽略各個*_operations,其他的不外乎剛剛討論過的五個結構體,但願讀者可以認真看一下這個圖片。 
虛擬文件系統總體結構

圖中的幾個*_operations都是一些函數指針的結構體,註冊文件系統的精髓就是將本身實現的功能函數以函數指針的形式傳遞給虛擬文件系統。 
譬如對於ext2文件系統其對應的address_space_operations

const struct address_space_operations ext2_aops = { .readpage = ext2_readpage, .readpages = ext2_readpages, .writepage = ext2_writepage, .sync_page = block_sync_page, .write_begin = ext2_write_begin, .write_end = generic_write_end, .bmap = ext2_bmap, .direct_IO = ext2_direct_IO, .writepages = ext2_writepages, .migratepage = buffer_migrate_page, }; //重 static int ext2_readpage(struct file *file, struct page *page) { //@ page 要讀的相對於文件的頁號 //特定於ext2文件系統的 相對文件塊號->磁盤邏輯塊號 映射關係 函數 return mpage_readpage(page, ext2_get_block); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

其中比較重要的下篇文章可能用到的爲ext2_readpage函數,該函數直接調用了mpage_readpage,這個函數是虛擬文件系統提供的,ext2_get_block是ext2文件系統提供的。下篇文章還有相關的討論,這裏再也不贅述。 
另外一個比較重要的函數爲inode_operations->look_up = ext2_lookup這個函數就上文一再強調的解析目錄項的函數。


引用

本文使用的引用基本上都在文中以超鏈的方式給出了,侵刪。還須要聲明一下,更多的是對現有博客和書籍的補充,具體的實現請參照Linux三本經典書籍

 

 

 

https://blog.csdn.net/bullbat/article/details/7245582

Linux虛擬文件系統(內核初始化)

 

linux 啓動(boot)時,須要加載根文件系統,根文件系統中保存一些系統運行所必須的驅動,模塊文件等。可是加載根文件系統自己就須要一個模塊文件,因此須要先訪問到這個模塊文件,因此須要一個文件系統來進行訪問,ramdisk就是用來作這個的。boot時內核先加載這個ramdisk,它是和內核同樣,都是BootLoader經過低級命令加載的,其中儲存着加載根文件系統所需的驅動,而後加載根文件系統。 爲何不直接經過Bootloader加載根文件系統? 多是這樣太慢了。

相關文章
相關標籤/搜索