close
MOUNT

在 的文件系統中,有個很重要的概念就是掛載,掛載大家應該都很熟悉,除了根文件系統,其他所有文件系統都要先掛載到根文件系統中的某個目錄之後才能訪問。

所謂的根文件系統就是系統啟動的時候安裝的第一個文件系統,它也是內核映像所在的文件系統。而 掛載到某個目錄 的 某個目錄 就是所謂的掛載點。

中有專門的命令來掛載文件系統,mount device dir, 為要掛載的設備文件名, 為掛載點。這裡所說的設備不是真的指單個實體設備,而是其上的邏輯設備,比如說一個磁盤上的不同分區都可以看作是不同的設備。

每個設備都有一個設備號來標識,設備號可以分為兩部分,一部分叫做主設備號 ,它用來標識某一類型的設備,比如說磁盤。另一部分叫做次設備號 ,它來標識某一具體設備,比如說磁盤上的某一具體分區。

當文件系統掛載到某個目錄後,我們就可以通過這個目錄來訪問該文件系統,很多地方就只是簡單的這樣講了一下,但其實這只能說是掛載的作用,那到底什麼是掛載,要想解決這個問題還是只能從源碼着手。下面我將根據 的代碼來講述掛載,涉及的東西比較多,我們只討論相關的部分。

數據結構m_inode,內存中的 inodestructm_inode{/********略*********/unsignedshorti_mode;//文件類型和屬性unsignedchari_mount;//是否有文件系統掛載到這兒unsignedshorti_zone[9];//索引取,如果是塊/字符設備文件i_zone[0]是設備號/********略*********/};super_block,內存中的超級塊structsuper_block{/********略*********/unsignedshorts_magic;//文件系統魔數/********略*********/unsignedshorts_dev;//設備號/********略*********/structm_inode*s_isup;//被掛載的文件系統的根目錄inodestructm_inode*s_imount;//該文件系統被安裝到此inode};

所謂的內存中的超級塊和 指的是兩者在內存中的緩存:

structsuper_blocksuper_block[NR_SUPER];#defineNR_SUPER8structm_inodeinode_table[NR_INODE];#defineNR_INODE32

可以看出在 裡面內存中最多同時存在 8 個超級塊和 32 個文件的 。這個緩存與我們平時所說的緩存差不多,當系統要想獲取一個 時,會先在 中尋找有沒有該 ,如果有的話就直接返回,如果沒有,就從設備上將 讀到 之後再返回。

相關操作

在看 掛載之前,先來看看一些操作函數。

staticstructsuper_block*read_super(intdev);

如果緩存區中沒有該設備的超級塊,則先找一個空閒的超級塊槽,然後從設備上讀取超級塊到找到的空閒超級塊槽。如果該設備的超級塊已經在緩存區中且數據有效,則直接返回該超級塊的指針。

structsuper_block*get_super(intdev);

根據設備號 從超級塊數組當中獲取超級塊。

voidput_super(intdev);

釋放指定的設備超級塊,也就是將超級塊的 字段清 0,如此使得該超級塊槽空閒出來。

structm_inode*namei(constchar*pathname);

函數根據路徑 獲取末尾文件的 ,這個函數應該是有印象的吧,在 中也有類似的函數。如果不太清楚的話,我這裡舉個例子:如果參數路徑是 /a/b/c,調用 之後就會返回文件 的

掛載

掛載的實現還是挺簡單的,來看代碼

intsys_mount(char*dev_name,char*dir_name,intrw_flag)//將名稱為dev_name的設備上的文件系統掛載到目錄dir_name上{structm_inode*dev_i,*dir_i;structsuper_block*sb;intdev;if(!(dev_i=namei(dev_name)))//沒有找到該設備return-ENOENT;dev=dev_i->i_zone[0];if(!S_ISBLK(dev_i->i_mode)){//不是塊設備iput(dev_i);return-EPERM;}iput(dev_i);//釋放該設備的inodeif(!(dir_i=namei(dir_name)))//解析獲取掛載點的inodereturn-ENOENT;if(dir_i->i_count!=1||dir_i->i_num==ROOT_INO){//如果掛載點的引用數不等於1獲取掛載點為根目錄iput(dir_i);return-EBUSY;}if(!S_ISDIR(dir_i->i_mode)){//掛載點不是目錄iput(dir_i);return-EPERM;}if(!(sb=read_super(dev))){//將設備上的文件系統的超級塊讀取到內存中iput(dir_i);return-EBUSY;}if(sb->s_imount){//如果該文件系統已掛載iput(dir_i);return-EBUSY;}if(dir_i->i_mount){//如果掛載點已經掛載了其他文件系統iput(dir_i);return-EPERM;}sb->s_imount=dir_i;//將掛載點的inode記錄到設備的超級塊中dir_i->i_mount=1;//表示該掛載點已經掛載了該文件系統dir_i->i_dirt=1;/*NOTE!wedon'tiput(dir_i)*///我們不會釋放掛載點的inodereturn0;/*wedothatinumount*///我們在umount卸載文件系統時釋放}

其流程圖為:

上圖為 的實現過程,除了各種檢查之外, 實際上只做了兩件事:

將要掛載的文件系統的超級塊讀到內存里的超級塊槽
設置該超級塊的 字段為 掛載點的 ,表示文件系統掛載到這個目錄上,設置掛載點 的 字段為 1 表示該目錄上掛載的有文件系統

另外再解釋幾點:

字符設備,提供連續的數據流,應用程序可以順序讀取數據,通常不支持隨機存取,像前面提到過的鍵盤串口都屬於字符設備。塊設備,應用程序能夠隨機訪問設備的數據,程序可以自行確定數據的位置,比如說硬盤就是典型的塊設備。很明顯地文件系統只能存放在塊設備上。
掛載點只能是個目錄文件,文件系統都是掛載到目錄上的
掛載文件系統時,掛載點這個目錄文件只能在當前引用,也就是說在掛載文件系統的時候,還有其他地方正在使用掛載點目錄的話就不對

這就是掛載的本質,有沒有感覺簡單的同時又還是模模糊糊的?為什麼將文件系統掛載到某個目錄之後,這個目錄就能表示被掛載的文件系統。解決這個問題還是要再來捋捋文件系統是如何尋找一個文件的,也就是 函數,比如說給定一個路徑 /a/b,這是一個絕對路徑,如何從最開始的根目錄尋到文件 的呢?

這個問題我在 文件系統裡面也詳細說過, 里也類似。這裡我們假設 文件都是目錄文件, 是一個普通文件。首先根目錄文件就是一個個目錄項,在其中尋找文件名為 的目錄項,從中獲取 目錄文件的 ,根據 的索引字段找到 目錄文件的數據,也是一個個目錄項,在其中尋找文件名為 的目錄項,從中獲取普通文件 的 然後返回。

上述所說的獲取某個 ,使用的函數是,,其意為從設備 中獲取編號為 的 。這個函數就會判斷編號為 的 上是否掛載的有文件系統,來看相關代碼:

structm_inode*iget(intdev,intnr){/*********略**********/inode=inode_table;//從inode表中第一個元素開始while(inode<NR_INODE+inode_table){//掃描內存里緩存的inode表if(inode->i_dev!=dev||inode->i_num!=nr){//如果設備號對不上或者inode編號對不上inode++;//下一個continue;}wait_on_inode(inode);//等待該inode解鎖if(inode->i_dev!=dev||inode->i_num!=nr){//因為等待過程中inode可能會發生變化,所以再次判斷inode=inode_table;continue;}inode->i_count++;//找到了該inode,將其引用數加1if(inode->i_mount){//如果該inode上掛載的有文件系統inti;for(i=0;i<NR_SUPER;i++)//在內存中緩存的超級塊中尋找掛載點為當前inode的超級塊if(super_block[i].s_imount==inode)//找到了,breakbreak;if(i>=NR_SUPER){//沒找到,返回printk("Mountedinodehasn'tgotsb\n");if(empty)iput(empty);returninode;}iput(inode);//釋放當前inodedev=super_block[i].s_dev;//將設備號重新設置為被掛載的文件系統所在的設備號nr=ROOT_INO;//將要尋找的inode編號重新設置為根目錄的inode編號inode=inode_table;//從內存的inode表第一個元素重新開始尋找inodecontinue;}if(empty)//釋放臨時找的空閒inodeiput(empty);returninode;//返回獲取到的inode}}

其完整的流程圖如下:

這是我根據趙炯畫的圖改編,因為沒有詳細講述 的代碼,所以主要關注虛線方框裡面的就行。

如果在 中找到相應的 ,就判斷 ,如果為真,表示該 表示的目錄文件上面掛載的有文件系統。此時這個目錄應該表示被掛載的文件系統的根目錄,所以設置 超級塊表示的設備,,原目錄就被隱藏掉了。舉個例子再說明一下,假如調用 ,本來我是要獲取 1 號設備的第 99 個 ,然後發現這個 指向的目錄上面掛載的有 2 號設備的文件系統,那麼我們就去尋找 2 號設備的根目錄 然後返回。所以看起來調用 實則調用的 ,這也就是為什麼說將文件系統掛載到某個目錄之後,這個目錄就被屏蔽了的原因所在。

到此,對文件系統的掛載應該有個很清晰的認識呢,最後來看看文件系統的卸載,基本上就是掛載的逆操作,來簡單看看:

intsys_umount(char*dev_name){structm_inode*inode;structsuper_block*sb;intdev;if(!(inode=namei(dev_name)))//解析獲取設備文件的inodereturn-ENOENT;dev=inode->i_zone[0];//對於塊/字符設備,設備號記錄在i_zone[0]if(!S_ISBLK(inode->i_mode)){//如果不是塊設備iput(inode);return-ENOTBLK;}iput(inode);//釋放設備文件的inodeif(dev==ROOT_DEV)//如果要卸載的是根文件系統return-EBUSY;if(!(sb=get_super(dev))||!(sb->s_imount))//如果沒有獲取到設備的超級塊或者如果掛載點為空return-ENOENT;if(!sb->s_imount->i_mount)//如果掛載點的掛載標識為空printk("Mountedinodehasi_mount=0\n");//檢查是否有進程在使用將要卸載文件系統上的文件for(inode=inode_table+0;inode<inode_table+NR_INODE;inode++)if(inode->i_dev==dev&&inode->i_count)return-EBUSY;sb->s_imount->i_mount=0;//掛載標識設為0iput(sb->s_imount);//釋放掛載點的inodesb->s_imount=NULL;//超級塊的掛載點字段設為空iput(sb->s_isup);//釋放被卸載的文件系統的根目錄inodesb->s_isup=NULL;//根目錄inode字段清0put_super(dev);//釋放設備超級塊sync_dev(dev);//更新的信息同步到設備return0;}

文件系統的卸載主要就是釋放超級塊,然後將一些字段值復原,具體見上面注釋就不細說了。

好了本文關於文件系統的掛載就這麼多,所以回到開頭什麼是掛載,但從實現上來說,就是將超級塊加載到內存裡面,因為超級塊就是一個文件系統的元信息集合,超級塊就能代表一個文件系統,所以將超級塊加載到內存裡面,我們就可以認為掛載了相應的文件系統。當然掛載這個機制不可能就只是靠超級塊是否在內存裡面來決定實現,還需要其他的函數來輔助,就比如說獲取 的 函數,這個函數就會來判斷當前獲取的這個 是否為掛載點,如果是,那就需要屏蔽當前這個 指向的目錄文件,然後將其替換為被掛載的文件系統的根目錄。這些總總加起來應該才算是掛載這個機制的實現,而不是說單靠一個 函數就實現了掛載的機制。

本文就到這裡了,有什麼問題還請批評指正,也歡迎大家來同我探討交流一起學習一起進步。


往期推薦



C/C++為什麼要專門設計個do…while?

為什麼空類大小是1

推薦一個學習技術的好網站

在部隊當程序員有多爽?

Linux最大並發數是多少?

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()