轉自:http://blog.csdn.net/u011373710/article/details/70198080node
本文首先以「儘可能不涉及源代碼」的方式討論Linux虛擬文件系統
的存在的意義、實現方式;後續文章中以讀文件
爲例從面到點更有針對性的討論其實現。在討論的過程當中有一些地方可能說的不夠全面,一是能力有限;另外一方面是但願不要陷入過多的細節之中,將注意力集中在框架的設計上。但願讀者有一些編程基礎、操做系統概念。本文討論的Linux內核版本爲2.6.24
。linux
經常使用的文件的讀寫的方式有同步讀寫
、異步讀寫
和內存映射
。前兩種的主要區別是,同步讀寫
會在進程向操做系統發出讀寫請求後被阻塞,直至所請求的操做完成時讀寫的函數纔會返回。也就是說,函數返回的時候數據已經從磁盤中讀到內存中了,或者已經從內存中寫入到磁盤上去了;異步讀寫
在發出讀寫請求後,函數會當即返回並不等待操做完成,這樣的話通常有一個回調函數的存在,操做完成時操做系統會調用用戶設置的回調函數;至於內存映射
使用的則是缺頁機制。這篇文章中主要圍繞同步讀寫
的方式,也是最經常使用的方式,討論Linux的虛擬文件系統的實現原理。
要想說明白虛擬文件系統的做用就必需要了解文件系統的做用。對於一個特定的文件系統來講,最主要的的設計方面就是數據在磁盤上的存儲方式
,這個功能從應用程序猿的角度來看就是文件名到數據的映射
: 經過對 以文件名爲主要的參數調用特定的函數(不一樣的語言、平臺函數不一樣)完成文件的讀寫操做。而磁盤基本上能夠看作一個以扇區
(一般爲521Byte)爲基本存儲單位的超大數組。若是有以下的極其簡單的文件系統,算法
扇區0存放着一整塊磁盤的屬性,如塊大小、文件系統標記;1~6中存放着文件的屬性和位置信息,每一個文件目錄項佔兩個磁盤塊,也就是說該文件系統的設計最多能夠存放三個文件;一、2中存放着文件test.txt
的屬性信息(日期、大小、歸屬)和位置信息;三、4存放着豔照門1.png
的屬性信息和位置信息;7~15中存放着實際的數據,不一樣的數據的顏色不一樣。如圖示的文件系統最多隻能存放三個文件,很難有實際的用途,不過用來講明文件系統的做用是很合適的,避免陷入過多的細節之中。
加入如今用戶想要讀取查看豔照門1.png
,那麼文件系統要作的事情是這樣的 :編程
豔照門1.png
。豔照門1.png
主要存儲在12~15四個磁盤塊上。png
文件的格式解析數據而後將圖像呈如今用戶面前。這裏有幾點點須要說明一下數組
- 真正的讀寫磁盤的操做並非文件系統來完成的,而是特定的磁盤的驅動程序來完成的。磁盤的驅動程序須要的參數是
邏輯磁盤塊號
、對應的內存位置
和是讀請求仍是寫請求
。由磁盤驅動程序來完成讀寫的目的是,實現文件系統的設計和具體磁盤結構的分離。也就是說,咱們上面設計的極簡的文件系統在驅動程序的幫助下不管是在機械硬盤上面,仍是在固態硬盤上面實現方式是相同的。磁盤驅動程序更加的貼近硬件設備,更加的「瞭解」硬件。譬如,若是使用的是固態硬盤那麼驅動程序則不會對讀寫請求進行排序;若是使用的是機械硬盤,則會對讀寫請求進行排序。參考機械硬盤內部硬件結構和工做原理詳解可知對於機械硬盤,按照順序一、二、3
讀取磁盤塊的速度是快於一、三、2
速度的,因此磁盤驅動程序讀寫請求排序所作的事情簡單講就是將一、三、2
這樣的讀寫請求排序成爲一、二、3
這樣的順序,而後發送給磁盤控制器
(硬件)。而固態硬盤更貴的緣由在於其讀寫過程不包含物理屬性(移動磁頭),請求一、二、3
、一、三、2
和請求二、三、1
的速度都是同樣的,因此不須要重排請求。- 1 中提到的請求排序的專業術語叫作
I/O調度
,這一工做應該是由驅動程序完成的,只不過內核提供了一些的經常使用算法的實現,方便了驅動程序的開發而已。- 一個對特定文件的讀寫請求可能會產生不少個對磁盤的讀寫請求。如上所說,要讀文件首先要把文件的固有屬性讀到內存中加以分析,而後才能去讀實際的文件。
上文以比較易懂的方式說明了文件系統一個最重要的工做文件名到磁盤數據的映射
,但是單從這一個方面彷佛看不出來虛擬文件系統存在的意義。那是由於,實際上文件系統要考慮不少的事情 :瀏覽器
磁盤扇區
同樣大的,或者是多個連續的扇區存放在一塊。以上面的最簡文件系統爲例,能夠很輕鬆的設計一個這樣的數據結構來記錄豔照門1.png的第一塊
也就是磁盤索引的12邏輯塊
存放在內存的0xFFAA處、第二塊到第四塊
存放在0xAABB處。爲了和內存管理模塊更好的交互,Linux中的針對文件數據的緩存的單位爲頁
,一般大小爲4KB,也正是內存管理模塊使用的內存單位。這裏須要讀者有必定的操做系統的基礎,可以大致上理解分頁機制存在的緣由以及作了什麼事情,因爲篇幅緣由這裏只須要知道在Linux中,內核使用struct page
結構體來描述內存中的每一頁。找到一頁對應的page結構體
就至關於知道了對應的物理頁的物理地址、是否被佔用等等很重要的屬性。這些文字是想說明,文件系統須要和內存管理模塊協調工做,畢竟分配
、回收
緩存的內存空間等操做都是須要內存管理模塊支持的。豔照門1.png
以後,用戶又想查看文件test.txt
,文件系統又要屢次讀取1~6塊磁盤來查找是否是有test.txt
這個文件。這樣的話,引入一個針對文件名的緩存也是很重要的,這個緩存在Linux內核中叫作目錄項緩存。和上面的緩存相似,只不過是針對目錄項的。permission denied
的狀況。若是沒有管理員權限,操做系統(文件系統)是不會讓你去讀寫一些受保護的文件的,window中可能感覺不是那麼強烈。不過除了這中常規意義上的限制,權限檢測還包括:文件是否越界、是否在讀寫一個目錄項文件等操做。回顧這麼多設計一個文件系統須要注意的地方能夠發現,上面的幾條對於每個文件系統都是須要的。也就是說,無論是上面提到的極簡文件系統仍是在實際使用中的FAT、EXT文件系統,都須要考慮上面的這些因素。爲了加快內核的開發、方便後續內核的擴展重構、使內核設計的更加優雅這才提出了虛擬文件系統的概念。虛擬文件系統作的事情就是實現了這樣一個框架,在這個框架中上面說起的這些重要因素都有了默認的實現,須要特定的文件系統實現的爲
文件相對塊號到磁盤邏輯塊號的映射關係
、目錄項的解析方式
等。
文件相對塊號就是指的相對於文件來講是第幾塊,磁盤邏輯塊號指的是相對於磁盤來講是第幾塊。緩存也就是說,若是想讓內核識別
極簡文件系統
須要該文件系統的設計人員嚴格按照虛擬文件系統的架構編寫須要的函數(都是函數指針的技術實現的),而後將文件系統註冊到內核中去。數據結構
(在這以前文件系統和虛擬文件系統的概念界限是有點模糊的,從這裏開始一直到文章的最後,文件系統只是表示磁盤上的數據存儲的結構,其餘的部分都算在虛擬文件系統裏面的 )架構
虛擬文件系統之因此沒有實現這兩個方面是由於這些性質是特定於一個文件系統的。仍是上面讀取
豔照門1.png
的例子,在極簡文件系統中豔照門1.png第一塊
是存放在磁盤邏輯12塊
中的,也就是文件相對塊號1->磁盤邏輯塊號12
。而若是使用的是FAT文件系統那麼就但是其餘的映射關係。目錄項的解析方式
須要特定的文件系統來實現就更好理解了,不一樣的文件系統其目錄項的字段設置、順序、長度一般是不同的,因此須要讓特定的文件系統來解析目錄項。解析完以後返回一個統一的文件的表示,也就是大名鼎鼎的inode
。struct inode
的字段很是之多,對於一些沒那麼複雜的文件系統來講多是一種浪費,由於它根本用不到那些複雜的字段。可是虛擬文件系統的設計理念是寧濫勿缺
: 畢竟要儘量地覆蓋全部的文件系統,多的字段你能夠不用,可是若是想用卻沒有那就麻煩大了。框架
這個時候看一下Linux虛擬文件系統的總體結構,再合適不過了。
虛擬文件系統(VFS,virtual file system)須要和各個實際的文件系統ext3
、… 、reiser
、proc
交互,大多數文件系統都須要虛擬文件系統提供的緩存機制(Buffer Cache),而proc
文件系統不須要緩存機制是由於其是基於內存的文件系統。那麼按照上面的討論其須要作的就是完成文件邏輯塊號
到內存中數據塊
之間的映射關係。再往下一層就是具體的設備驅動層,實際的讀寫操做都是須要設備驅動層去完成的,它下一層就是實際的物理設備了。
上面提到的特定於文件系統的操做是經過註冊的方式讓虛擬文件系統知道當前內核中支持哪些操做系統,註冊的主要參數有文件系統的名字
、解析inode的函數(解析目錄項)
、解析文件相對塊號到磁盤邏輯塊號的函數
,這都是上面討論過的關鍵點。對於一些經常使用的文件系統不用註冊也可以使用,這是由操做系統去註冊的。註冊使用的技術就是C語言中的函數指針
。註冊完成以後,就能夠經過掛載
的方式去使用一個具體的文件系統了。掛載須要的參數有被掛在的設備(本文討論中限定爲磁盤)
、該設備使用的文件系統(必須已經註冊過)
、掛載點
。若是明白前文的討論,那麼掛載也是很好理解的。以上文的極簡文件系統爲例,豔照門1.png
存放在磁盤A
中,在磁盤A
沒有被掛在以前操做系統(或者說虛擬文件系統)並不知道磁盤A
使用的什麼文件系統,因此沒有辦法去讀取它上面的數據並解析之。在用戶執行了操做以極簡文件系統掛載磁盤A到 /home/jingjinhao/片 下
以後,在訪問/home/jiangjinhao/片/豔照門1.png
的時候虛擬文件系統就會調用極簡系統註冊的函數,去執行前文討論過的尋找豔照門1.png
的過程。毫無疑問操做系統須要維護一個目錄之間的層級關係以及不一樣的文件系統之間的掛載關係,這正是struct dentry
和struct vfsmound
的做用。這之間的具體圖示關係請參考mount過程分析之六——掛載關係(圖解) 。感受本身沒有能力寫出來一篇比這還好的文章,推薦看一下這篇文章。
上文提到struct inode
、strcut dentry
和struct vfsmound
這三個數據結構都是虛擬文件系統很是重要的部分。雖然不大喜歡扣數據結構,不過爲了下文更好的討論這裏仍是儘可能從原理上羅列一下核心數據結構。
struct address_space
這個數據結構是對上文討論過的緩存
的抽象。該數據結構能夠提供查找緩存、添加緩存的方法,也就是說對於一個文件找到了其對應的struct address_space
就可以增刪改查
緩存的內容。暫時沒必要關心起底層的實現是鏈表、數組仍是樹(實際上是基數樹),不管是什麼其提供的功能老是不變的,只不過速度上可能會有差異。查找使用的參數是文件相對頁號
,成功返回對應的物理頁幀描述結構struct page
的指針(上文描述過),沒有找到的話返回null
。這裏的文件相對頁號很好理解,舉例來講在頁大小爲4KB的狀況下,0~4KB對也相對頁號爲0,4KB~8Kb對應的相對頁號爲1,以此類推。struct inode
是對一個文件的抽象,因此其中包含的主要字段有 : 文件的大小、日期、全部者等固有屬性;指向緩存的指針struct address_space *
;指向塊設備驅動程序的指針block_device*
,由於文件系統並不負責實際的讀寫,須要依靠驅動程序的幫助;一些鎖。這幾大類字段,在上文的討論下都是比較好理解的,須要說明的一點就是inode
中是沒有文件的名字這個字段,文件的名字包含在下面的dentry
中,因此取而代之的是指向對應文件的struct dentry
的指針。這並非說必定不能把文件名存儲在inode
中,只不過當前虛擬文件系統的設計使然,再在inode
中存儲的話就有點囉嗦了。struct dentry
首先實現了對目錄層次結構的抽象,以下圖內存中每一個打開的的每一個節點都對應一個struct dentry
的實例,須要強調的不只僅目錄有對應的detry
實例,普通的文件也有對應的detry
,只不過普通文件的detry
實例沒有子節點罷了。只有打開的文件或目錄纔有對應的節點,因此內存中樹結構的完整度是≤磁盤上樹結構的完整度的;沒有在inode
中而是在detry
存儲文件的名字的一大緣由struct detry
負責創建起前文討論過的目錄項緩存
(以Hash表的方式)。也就是說在打開一個文件的時候,虛擬文件系統會首先經過文件名
查找是否存在一個打開的detry
了,若是有的話就大可返回了;detry
最後一個重要的做用就是結合struct vfsmount
完成了掛載操做的數據結構的支持。上圖中的示例在vfsmount
的視圖中以下圖示 detry
的支持的。struct file
結構體。該結構體是對一次文件操做的抽象,剛剛提到的幾個方面外,file
中還包含了預讀相關的一些字段。每一個進程控制塊struct task
中都包含一個struct file *
的數組,進程打開的每一個文件對應其中的一項,這也解釋了爲何fopen
返回的是一個無符號整型了(數組的索引)。最多隻能存三個文件
這相似的屬性,這個結構體叫作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); }
其中比較重要的下篇文章可能用到的爲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加載根文件系統? 多是這樣太慢了。