當(dāng)前位置:首頁 > 公眾號(hào)精選 > 程序喵大人
[導(dǎo)讀]MOUNT在的文件系統(tǒng)中,有個(gè)很重要的概念就是掛載,掛載大家應(yīng)該都很熟悉,除了根文件系統(tǒng),其他所有文件系統(tǒng)都要先掛載到根文件系統(tǒng)中的某個(gè)目錄之后才能訪問。所謂的根文件系統(tǒng)就是系統(tǒng)啟動(dòng)的時(shí)候安裝的第一個(gè)文件系統(tǒng),它也是內(nèi)核映像所在的文件系統(tǒng)。而掛載到某個(gè)目錄的某個(gè)目錄就是所謂的掛載...

MOUNT

的文件系統(tǒng)中,有個(gè)很重要的概念就是掛載,掛載大家應(yīng)該都很熟悉,除了根文件系統(tǒng),其他所有文件系統(tǒng)都要先掛載到根文件系統(tǒng)中的某個(gè)目錄之后才能訪問。


所謂的根文件系統(tǒng)就是系統(tǒng)啟動(dòng)的時(shí)候安裝的第一個(gè)文件系統(tǒng),它也是內(nèi)核映像所在的文件系統(tǒng)。而 掛載到某個(gè)目錄的 某個(gè)目錄就是所謂的掛載點(diǎn)。


中有專門的命令來掛載文件系統(tǒng),mount device dir, 為要掛載的設(shè)備文件名, 為掛載點(diǎn)。這里所說的設(shè)備不是真的指單個(gè)實(shí)體設(shè)備,而是其上的邏輯設(shè)備,比如說一個(gè)磁盤上的不同分區(qū)都可以看作是不同的設(shè)備。


每個(gè)設(shè)備都有一個(gè)設(shè)備號(hào)來標(biāo)識(shí),設(shè)備號(hào)可以分為兩部分,一部分叫做主設(shè)備號(hào) ,它用來標(biāo)識(shí)某一類型的設(shè)備,比如說磁盤。另一部分叫做次設(shè)備號(hào) ,它來標(biāo)識(shí)某一具體設(shè)備,比如說磁盤上的某一具體分區(qū)。


當(dāng)文件系統(tǒng)掛載到某個(gè)目錄后,我們就可以通過這個(gè)目錄來訪問該文件系統(tǒng),很多地方就只是簡(jiǎn)單的這樣講了一下,但其實(shí)這只能說是掛載的作用,那到底什么是掛載,要想解決這個(gè)問題還是只能從源碼著手。下面我將根據(jù) 的代碼來講述掛載,涉及的東西比較多,我們只討論相關(guān)的部分。


數(shù)據(jù)結(jié)構(gòu)

m_inode,內(nèi)存中的 inode

struct m_inode {
/********略*********/
unsigned short i_mode; //文件類型和屬性
unsigned char i_mount; //是否有文件系統(tǒng)掛載到這兒
unsigned short i_zone[9]; //索引取,如果是塊/字符設(shè)備文件i_zone[0]是設(shè)備號(hào)
/********略*********/
};

super_block,內(nèi)存中的超級(jí)塊

struct super_block {
/********略*********/
unsigned short s_magic; //文件系統(tǒng)魔數(shù)
/********略*********/
unsigned short s_dev; //設(shè)備號(hào)
/********略*********/
struct m_inode * s_isup; //被掛載的文件系統(tǒng)的根目錄inode
struct m_inode * s_imount; //該文件系統(tǒng)被安裝到此inode
};
所謂的內(nèi)存中的超級(jí)塊和 指的是兩者在內(nèi)存中的緩存:


struct super_block super_block[NR_SUPER];
#define NR_SUPER 8
struct m_inode inode_table[NR_INODE];
#define NR_INODE 32
可以看出在 里面內(nèi)存中最多同時(shí)存在 8 個(gè)超級(jí)塊和 32 個(gè)文件的 。這個(gè)緩存與我們平時(shí)所說的緩存差不多,當(dāng)系統(tǒng)要想獲取一個(gè) 時(shí),會(huì)先在 中尋找有沒有該 ,如果有的話就直接返回,如果沒有,就從設(shè)備上將 讀到 之后再返回。


相關(guān)操作

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


static struct super_block * read_super(int dev);
如果緩存區(qū)中沒有該設(shè)備的超級(jí)塊,則先找一個(gè)空閑的超級(jí)塊槽,然后從設(shè)備上讀取超級(jí)塊到找到的空閑超級(jí)塊槽。如果該設(shè)備的超級(jí)塊已經(jīng)在緩存區(qū)中且數(shù)據(jù)有效,則直接返回該超級(jí)塊的指針。


struct super_block * get_super(int dev);
根據(jù)設(shè)備號(hào) 從超級(jí)塊數(shù)組當(dāng)中獲取超級(jí)塊。


void put_super(int dev);
釋放指定的設(shè)備超級(jí)塊,也就是將超級(jí)塊的 字段清 0,如此使得該超級(jí)塊槽空閑出來。


struct m_inode * namei(const char * pathname);
函數(shù)根據(jù)路徑 獲取末尾文件的 ,這個(gè)函數(shù)應(yīng)該是有印象的吧,在 中也有類似的函數(shù)。如果不太清楚的話,我這里舉個(gè)例子:如果參數(shù)路徑是 /a/b/c,調(diào)用 之后就會(huì)返回文件


掛載

掛載的實(shí)現(xiàn)還是挺簡(jiǎn)單的,來看代碼


int sys_mount(char * dev_name, char * dir_name, int rw_flag) //將名稱為dev_name的設(shè)備上的文件系統(tǒng)掛載到目錄dir_name上
{
struct m_inode * dev_i, * dir_i;
struct super_block * sb;
int dev;

if (!(dev_i=namei(dev_name))) //沒有找到該設(shè)備
return -ENOENT;
dev = dev_i->i_zone[0];
if (!S_ISBLK(dev_i->i_mode)) { //不是塊設(shè)備
iput(dev_i);
return -EPERM;
}
iput(dev_i); //釋放該設(shè)備的inode
if (!(dir_i=namei(dir_name))) //解析獲取掛載點(diǎn)的inode
return -ENOENT;
if (dir_i->i_count != 1 || dir_i->i_num == ROOT_INO) { //如果掛載點(diǎn)的引用數(shù)不等于1獲取掛載點(diǎn)為根目錄
iput(dir_i);
return -EBUSY;
}
if (!S_ISDIR(dir_i->i_mode)) { //掛載點(diǎn)不是目錄
iput(dir_i);
return -EPERM;
}
if (!(sb=read_super(dev))) { //將設(shè)備上的文件系統(tǒng)的超級(jí)塊讀取到內(nèi)存中
iput(dir_i);
return -EBUSY;
}
if (sb->s_imount) { //如果該文件系統(tǒng)已掛載
iput(dir_i);
return -EBUSY;
}
if (dir_i->i_mount) { //如果掛載點(diǎn)已經(jīng)掛載了其他文件系統(tǒng)
iput(dir_i);
return -EPERM;
}
sb->s_imount=dir_i; //將掛載點(diǎn)的inode記錄到設(shè)備的超級(jí)塊中
dir_i->i_mount=1; //表示該掛載點(diǎn)已經(jīng)掛載了該文件系統(tǒng)
dir_i->i_dirt=1; /* NOTE! we don't iput(dir_i) */ //我們不會(huì)釋放掛載點(diǎn)的inode
return 0; /* we do that in umount */ //我們?cè)趗mount卸載文件系統(tǒng)時(shí)釋放
}
其流程圖為:


上圖為 的實(shí)現(xiàn)過程,除了各種檢查之外, 實(shí)際上只做了兩件事:


  1. 將要掛載的文件系統(tǒng)的超級(jí)塊讀到內(nèi)存里的超級(jí)塊槽
  2. 設(shè)置該超級(jí)塊的 字段為 掛載點(diǎn)的 ,表示文件系統(tǒng)掛載到這個(gè)目錄上,設(shè)置掛載點(diǎn) 字段為 1 表示該目錄上掛載的有文件系統(tǒng)
另外再解釋幾點(diǎn):


  1. 字符設(shè)備,提供連續(xù)的數(shù)據(jù)流,應(yīng)用程序可以順序讀取數(shù)據(jù),通常不支持隨機(jī)存取,像前面提到過的鍵盤串口都屬于字符設(shè)備。塊設(shè)備,應(yīng)用程序能夠隨機(jī)訪問設(shè)備的數(shù)據(jù),程序可以自行確定數(shù)據(jù)的位置,比如說硬盤就是典型的塊設(shè)備。很明顯地文件系統(tǒng)只能存放在塊設(shè)備上。
  2. 掛載點(diǎn)只能是個(gè)目錄文件,文件系統(tǒng)都是掛載到目錄上的
  3. 掛載文件系統(tǒng)時(shí),掛載點(diǎn)這個(gè)目錄文件只能在當(dāng)前引用,也就是說在掛載文件系統(tǒng)的時(shí)候,還有其他地方正在使用掛載點(diǎn)目錄的話就不對(duì)
這就是掛載的本質(zhì),有沒有感覺簡(jiǎn)單的同時(shí)又還是模模糊糊的?為什么將文件系統(tǒng)掛載到某個(gè)目錄之后,這個(gè)目錄就能表示被掛載的文件系統(tǒng)。解決這個(gè)問題還是要再來捋捋文件系統(tǒng)是如何尋找一個(gè)文件的,也就是 函數(shù),比如說給定一個(gè)路徑 /a/b,這是一個(gè)絕對(duì)路徑,如何從最開始的根目錄尋到文件 的呢?


這個(gè)問題我在 文件系統(tǒng)里面也詳細(xì)說過, 里也類似。這里我們假設(shè) 文件都是目錄文件, 是一個(gè)普通文件。首先根目錄文件就是一個(gè)個(gè)目錄項(xiàng),在其中尋找文件名為 的目錄項(xiàng),從中獲取 目錄文件的 ,根據(jù) 的索引字段找到 目錄文件的數(shù)據(jù),也是一個(gè)個(gè)目錄項(xiàng),在其中尋找文件名為 的目錄項(xiàng),從中獲取普通文件 然后返回。


上述所說的獲取某個(gè) ,使用的函數(shù)是,,其意為從設(shè)備 中獲取編號(hào)為 。這個(gè)函數(shù)就會(huì)判斷編號(hào)為 上是否掛載的有文件系統(tǒng),來看相關(guān)代碼:


struct m_inode * iget(int dev, int nr){
/*********略**********/
inode = inode_table; //從inode表中第一個(gè)元素開始
while (inode < NR_INODE inode_table) { //掃描內(nèi)存里緩存的inode表
if (inode->i_dev != dev || inode->i_num != nr) { //如果設(shè)備號(hào)對(duì)不上或者inode編號(hào)對(duì)不上
inode ; //下一個(gè)
continue;
}
wait_on_inode(inode); //等待該inode解鎖
if (inode->i_dev != dev || inode->i_num != nr) { //因?yàn)榈却^程中inode可能會(huì)發(fā)生變化,所以再次判斷
inode = inode_table;
continue;
}
inode->i_count ; //找到了該inode,將其引用數(shù)加1
if (inode->i_mount) { //如果該inode上掛載的有文件系統(tǒng)
int i;

for (i = 0 ; i//在內(nèi)存中緩存的超級(jí)塊中尋找掛載點(diǎn)為當(dāng)前inode的超級(jí)塊
if (super_block[i].s_imount==inode) //找到了,break
break;
if (i >= NR_SUPER) { //沒找到,返回
printk("Mounted inode hasn't got sb\n");
if (empty)
iput(empty);
return inode;
}
iput(inode); //釋放當(dāng)前inode
dev = super_block[i].s_dev; //將設(shè)備號(hào)重新設(shè)置為被掛載的文件系統(tǒng)所在的設(shè)備號(hào)
nr = ROOT_INO; //將要尋找的inode編號(hào)重新設(shè)置為根目錄的inode編號(hào)
inode = inode_table; //從內(nèi)存的inode表第一個(gè)元素重新開始尋找inode
continue;
}
if (empty) //釋放臨時(shí)找的空閑inode
iput(empty);
return inode; //返回獲取到的inode
}
}
其完整的流程圖如下:


這是我根據(jù)趙炯畫的圖改編,因?yàn)闆]有詳細(xì)講述 的代碼,所以主要關(guān)注虛線方框里面的就行。


如果在 中找到相應(yīng)的 ,就判斷 ,如果為真,表示該 表示的目錄文件上面掛載的有文件系統(tǒng)。此時(shí)這個(gè)目錄應(yīng)該表示被掛載的文件系統(tǒng)的根目錄,所以設(shè)置 超級(jí)塊表示的設(shè)備,,原目錄就被隱藏掉了。舉個(gè)例子再說明一下,假如調(diào)用 ,本來我是要獲取 1 號(hào)設(shè)備的第 99 個(gè) ,然后發(fā)現(xiàn)這個(gè) 指向的目錄上面掛載的有 2 號(hào)設(shè)備的文件系統(tǒng),那么我們就去尋找 2 號(hào)設(shè)備的根目錄 然后返回。所以看起來調(diào)用 實(shí)則調(diào)用的 ,這也就是為什么說將文件系統(tǒng)掛載到某個(gè)目錄之后,這個(gè)目錄就被屏蔽了的原因所在。


到此,對(duì)文件系統(tǒng)的掛載應(yīng)該有個(gè)很清晰的認(rèn)識(shí)呢,最后來看看文件系統(tǒng)的卸載,基本上就是掛載的逆操作,來簡(jiǎn)單看看:


int sys_umount(char * dev_name)
{
struct m_inode * inode;
struct super_block * sb;
int dev;

if (!(inode=namei(dev_name))) //解析獲取設(shè)備文件的inode
return -ENOENT;
dev = inode->i_zone[0]; //對(duì)于塊/字符設(shè)備,設(shè)備號(hào)記錄在i_zone[0]
if (!S_ISBLK(inode->i_mode)) { //如果不是塊設(shè)備
iput(inode);
return -ENOTBLK;
}
iput(inode); //釋放設(shè)備文件的inode
if (dev==ROOT_DEV) //如果要卸載的是根文件系統(tǒng)
return -EBUSY;
if (!(sb=get_super(dev)) || !(sb->s_imount)) //如果沒有獲取到設(shè)備的超級(jí)塊或者如果掛載點(diǎn)為空
return -ENOENT;
if (!sb->s_imount->i_mount) //如果掛載點(diǎn)的掛載標(biāo)識(shí)為空
printk("Mounted inode has i_mount=0\n");
//檢查是否有進(jìn)程在使用將要卸載文件系統(tǒng)上的文件
for (inode=inode_table 0 ; inode if (inode->i_dev==dev 
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。

程序喵大人

185 篇文章

關(guān)注

發(fā)布文章

編輯精選

技術(shù)子站

關(guān)閉