中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

網(wǎng)絡(luò)服務(wù)器功能的概述技術(shù)優(yōu)化seo

網(wǎng)絡(luò)服務(wù)器功能的概述,技術(shù)優(yōu)化seo,h5網(wǎng)站怎么做,電商網(wǎng)站建設(shè)規(guī)劃虛擬文件系統(tǒng)(VFS)的作用 虛擬文件系統(tǒng)(Virtual Filesystem)也可以稱之為虛擬文件系統(tǒng)轉(zhuǎn)換(Virtual Filesystem Switch,VFS), 是一個(gè)內(nèi)核軟件層, 用來處理與Unix標(biāo)準(zhǔn)文件系統(tǒng)相關(guān)的所有系統(tǒng)調(diào)用。 其健壯性表現(xiàn)在能為各種文件系統(tǒng)提供一個(gè)通用的接口。VFS支持的文件…

虛擬文件系統(tǒng)(VFS)的作用

虛擬文件系統(tǒng)(Virtual Filesystem)也可以稱之為虛擬文件系統(tǒng)轉(zhuǎn)換(Virtual Filesystem Switch,VFS),
是一個(gè)內(nèi)核軟件層,
用來處理與Unix標(biāo)準(zhǔn)文件系統(tǒng)相關(guān)的所有系統(tǒng)調(diào)用。
其健壯性表現(xiàn)在能為各種文件系統(tǒng)提供一個(gè)通用的接口。

在這里插入圖片描述

VFS支持的文件系統(tǒng)可以劃分為三種主要類型:
1.磁盤文件系統(tǒng)
這些文件系統(tǒng)管理在本地磁盤分區(qū)中可用的存儲(chǔ)空間
或者其他可以起到磁盤作用的設(shè)備(比如說一個(gè)USB閃存)。
VFS支持的基于磁盤的某些著名文件系統(tǒng)還有:

在這里插入圖片描述
在這里插入圖片描述

2.網(wǎng)絡(luò)文件系統(tǒng)
這些文件系統(tǒng)允許輕易地訪問屬于其他網(wǎng)絡(luò)計(jì)算機(jī)的文件系統(tǒng)所包含的文件。
虛擬文件系統(tǒng)所支持的一些著名的網(wǎng)絡(luò)文件系統(tǒng)有:
NFS、Coda、AFS(Andrew文件系統(tǒng))、
CIFS(用于Microsoft Windows的通用網(wǎng)絡(luò)文件系統(tǒng))
以及NCP(Novell 公司的NetWare Core Protocol)。
3.特殊文件系統(tǒng)
這些文件系統(tǒng)不管理本地或者遠(yuǎn)程磁盤空間。
/proc文件系統(tǒng)是特殊文件系統(tǒng)的一個(gè)典型范例(參見稍后“特殊文件系統(tǒng)“一節(jié))。根目錄包含在根文件系統(tǒng)(root filesystem)中,
在Linux中這個(gè)根文件系統(tǒng)通常就是Ext2或Ext3類型。
其他所有的文件系統(tǒng)都可以被“安裝“在根文件系統(tǒng)的子目錄中基于磁盤的文件系統(tǒng)通常存放在硬件塊設(shè)備中,
如硬盤、軟盤或者CD-ROM。
Linux VFS 的一個(gè)有用特點(diǎn)是能夠處理如/dev/loop0這樣的虛擬塊設(shè)備,
這種設(shè)備可以用來安裝普通文件所在的文件系統(tǒng)。
作為一種可能的應(yīng)用,用戶可以保護(hù)自己的私有文件系統(tǒng),
這可以通過把自己文件系統(tǒng)的加密版本存放在一個(gè)普通文件中來實(shí)現(xiàn)。

通用文件模型

VFS所隱含的主要思想在于引入了一個(gè)通用的文件模型(common file model),
這個(gè)模型能夠表示所有支持的文件系統(tǒng)。
該模型嚴(yán)格反映傳統(tǒng)Unix文件系統(tǒng)提供的文件模型。
這并不奇怪,因?yàn)長inux希望以最小的額外開銷運(yùn)行它的本地文件系統(tǒng)。
不過,要實(shí)現(xiàn)每個(gè)具體的文件系統(tǒng),
必須將其物理組織結(jié)構(gòu)轉(zhuǎn)換為虛擬文件系統(tǒng)的通用文件模型。例如,在通用文件模型中,每個(gè)目錄被看作一個(gè)文件,
可以包含若干文件和其他的子目錄。
但是,存在幾個(gè)非Unix的基于磁盤的文件系統(tǒng),
它們利用文件分配表(File Allocation Table,FAT)存放每個(gè)文件在目錄樹中的位置,
在這些文件系統(tǒng)中,存放的是目錄而不是文件。
為了符合VFS的通用文件模型,
對上述基于FAT的文件系統(tǒng)的實(shí)現(xiàn),
Linux必須在必要時(shí)能夠快速建立對應(yīng)于目錄的文件。
這樣的文件只作為內(nèi)核內(nèi)存的對象而存在。從本質(zhì)上說,
Linux內(nèi)核不能對一個(gè)特定的函數(shù)進(jìn)行硬編碼來執(zhí)行諸如read()或ioct1()這樣的操作,
而是對每個(gè)操作都必須使用一個(gè)指針,指向要訪問的具體文件系統(tǒng)的適當(dāng)函數(shù)。
為了進(jìn)一步說明這一概念,
參見圖12-1,其中顯示了內(nèi)核如何把read()轉(zhuǎn)換為專對MS-DOS文件系統(tǒng)的一個(gè)調(diào)用。
應(yīng)用程序?qū)ead()的調(diào)用引起內(nèi)核調(diào)用相應(yīng)的sys_read()服務(wù)例程,
這與其他系統(tǒng)調(diào)用完全類似。
我們在本章后面會(huì)看到,
文件在內(nèi)核內(nèi)存中是由一個(gè)file數(shù)據(jù)結(jié)構(gòu)來表示的。
這種數(shù)據(jù)結(jié)構(gòu)中包含一個(gè)稱為f_op的字段,
該字段中包含一個(gè)指向?qū)S-DOS文件的函數(shù)指針,
當(dāng)然還包括讀文件的函數(shù)。
sys_read()查找到指向該函數(shù)的指針,并調(diào)用它。
這樣一來,應(yīng)用程序的read()就被轉(zhuǎn)化為相對間接的調(diào)用:
file->f_op->read(...);
與之類似,write()操作也會(huì)引發(fā)一個(gè)與輸出文件相關(guān)的Ext2寫函數(shù)的執(zhí)行。
簡而言之,內(nèi)核負(fù)責(zé)把一組合適的指針分配給與每個(gè)打開文件相關(guān)的file變量,
然后負(fù)責(zé)調(diào)用針對每個(gè)具體文件系統(tǒng)的函數(shù)(由f_op字段指向)。

在這里插入圖片描述
在這里插入圖片描述

如圖12-2所示是一個(gè)簡單的示例,說明進(jìn)程怎樣與文件進(jìn)行交互。
三個(gè)不同進(jìn)程已經(jīng)打開同一個(gè)文件,
其中兩個(gè)進(jìn)程使用同一個(gè)硬鏈接。
在這種情況下,其中的每個(gè)進(jìn)程都使用自己的文件對象,
但只需要兩個(gè)目錄項(xiàng)對象,
每個(gè)硬鏈接對應(yīng)一個(gè)目錄項(xiàng)對象。
這兩個(gè)目錄項(xiàng)對象指向同一個(gè)索引節(jié)點(diǎn)對象,
該索引節(jié)點(diǎn)對象標(biāo)識(shí)超級塊對象,以及隨后的普通磁盤文件。

在這里插入圖片描述

VFS除了能為所有文件系統(tǒng)的實(shí)現(xiàn)提供一個(gè)通用接口外,
還具有另一個(gè)與系統(tǒng)性能相關(guān)的重要作用。
最近最常使用的目錄項(xiàng)對象被放在所謂目錄項(xiàng)高速緩存(dentrycache)的磁盤高速緩存中,
以加速從文件路徑名到最后一個(gè)路徑分量的索引節(jié)點(diǎn)的轉(zhuǎn)換過程。一般說來,磁盤高速緩存(diskcache)屬于軟件機(jī)制,
它允許內(nèi)核將原本存在磁盤上的某些信息保存在RAM中,
以便對這些數(shù)據(jù)的進(jìn)一步訪問能快速進(jìn)行,而不必慢速訪問磁盤本身。注意,磁盤高速緩存不同于硬件高速緩存或內(nèi)存高速緩存,
后兩者都與磁盤或其他設(shè)備無關(guān)。
硬件高速緩存是一個(gè)快速靜態(tài)RAM,
它加快了直接對慢速動(dòng)態(tài)RAM的請求(參見第二章中的“硬件高速緩存”一節(jié))。
內(nèi)存高速緩存是一種軟件機(jī)制,
引入它是為了繞過內(nèi)核內(nèi)存分配器(參見第八章中的“slab分配器”一節(jié))。除了目錄項(xiàng)高速緩存和索引結(jié)點(diǎn)高速緩存之外,
Linux還使用其他磁盤高速緩存。
其中最重要的一種就是所謂的頁高速緩存,我們將在第十五章中進(jìn)行詳細(xì)介紹。

VFS所處理的系統(tǒng)調(diào)用

表12-1列出了VFS的系統(tǒng)調(diào)用,
這些系統(tǒng)調(diào)用涉及文件系統(tǒng)、普通文件、目錄文件以及符號鏈接文件。
另外還有少數(shù)幾個(gè)由VFS處理的其他系統(tǒng)調(diào)用,
諸如ioperm()、ioct1()、pipe()和mknod(),涉及設(shè)備文件和管道文件,
這些將在后續(xù)章節(jié)中討論。
最后一組由VFS處理的系統(tǒng)調(diào)用,
諸如socket()、connect()和bind()屬于套接字系統(tǒng)調(diào)用,
并用于實(shí)現(xiàn)網(wǎng)絡(luò)功能。
與表12-1列出的系統(tǒng)調(diào)用對應(yīng)的一些內(nèi)核服務(wù)例程,
我們會(huì)在本章或第十八章中陸續(xù)進(jìn)行討論。

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

前面我們已經(jīng)提到,
VFS是應(yīng)用程序和具體文件系統(tǒng)之間的一層。
不過,在某些情況下,一個(gè)文件操作可能由VFS本身去執(zhí)行,無需調(diào)用低層函數(shù)。
例如,當(dāng)某個(gè)進(jìn)程關(guān)閉一個(gè)打開的文件時(shí),并不需要涉及磁盤上的相應(yīng)文件,
因此VFS只需釋放對應(yīng)的文件對象。
類似地,當(dāng)系統(tǒng)調(diào)用lseek()修改一個(gè)文件指針,
而這個(gè)文件指針是打開文件與進(jìn)程交互所涉及的一個(gè)屬性時(shí),
VFS就只需修改對應(yīng)的文件對象,而不必訪問磁盤上的文件,
因此,無需調(diào)用具體文件系統(tǒng)的函數(shù)。
從某種意義上說,
可以把VFS看成“通用“文件系統(tǒng),它在必要時(shí)依賴某種具體文件系統(tǒng)。

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

每個(gè)VFS對象都存放在一個(gè)適當(dāng)?shù)臄?shù)據(jù)結(jié)構(gòu)中,
其中包括對象的屬性和指向?qū)ο蠓椒ū淼闹羔槨?內(nèi)核可以動(dòng)態(tài)地修改對象的方法,
因此可以為對象建立專用的行為。下面幾節(jié)詳細(xì)介紹VFS的對象及其內(nèi)在關(guān)系。

超級塊對象

超級塊對象由super_block結(jié)構(gòu)組成,表12-2列舉了其中的字段。

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

所有超級塊對象都以雙向循環(huán)鏈表的形式鏈接在一起。
鏈表中第一個(gè)元素用super_blocks變量來表示,
而超級塊對象的s_list字段存放指向鏈表相鄰元素的指針。
sb_lock自旋鎖保護(hù)鏈表免受多處理器系統(tǒng)上的同時(shí)訪問。s_fs_info字段指向?qū)儆诰唧w文件系統(tǒng)的超級塊信息;
例如,我們在第十八章將會(huì)看到,
假如超級塊對象指的是Ext2文件系統(tǒng),
該字段就指向ext2_sb_info數(shù)據(jù)結(jié)構(gòu),
該結(jié)構(gòu)包括磁盤分配位掩碼和其他與VFS的通用文件模型無關(guān)的數(shù)據(jù)。
通常,為了效率起見,由s_fs_info字段所指向的數(shù)據(jù)被復(fù)制到內(nèi)存。
任何基于磁盤的文件系統(tǒng)都需要訪問和更改自己的磁盤分配位圖,
以便分配或釋放磁盤塊。
VFS允許這些文件系統(tǒng)直接對內(nèi)存超級塊的s_fs_info字段進(jìn)行操作,
而無需訪問磁盤。但是,這種方法帶來一個(gè)新問題:
有可能VFS超級塊最終不再與磁盤上相應(yīng)的超級塊同步。
因此,有必要引入一個(gè)s_dirt標(biāo)志來表示該超級塊是否是臟的
——那磁盤上的數(shù)據(jù)是否必須要更新。
缺乏同步還會(huì)導(dǎo)致產(chǎn)生我們熟悉的一個(gè)問題:
當(dāng)一臺(tái)機(jī)器的電源突然斷開而用戶來不及正常關(guān)閉系統(tǒng)時(shí),
就會(huì)出現(xiàn)文件系統(tǒng)崩潰。
我們將會(huì)在第十五章的“把臟頁寫入磁盤“一節(jié)中看到,
Linux是通過周期性地將所有“臟“的超級塊寫回磁盤來減少該問題帶來的危害。與超級塊關(guān)聯(lián)的方法就是所謂的超級塊操作。
這些操作是由數(shù)據(jù)結(jié)構(gòu)super_operations 來描述的,
該結(jié)構(gòu)的起始地址存放在超級塊的s_op字段中。
每個(gè)具體的文件系統(tǒng)都可以定義自己的超級塊操作。
當(dāng)VFS需要調(diào)用其中一個(gè)操作時(shí),比如說read_inode(),
它執(zhí)行下列操作:
sb->s_op->read_inode(inode);
這里sb存放所涉及超級塊對象的地址。
super_operations表的read_inode字段存放這一函數(shù)的地址,
因此,這一函數(shù)被直接調(diào)用。
讓我們簡要描述一下超級塊操作,
其中實(shí)現(xiàn)了一些高級操作,
比如刪除文件或安裝磁盤。
下面這些操作按照它們在super_operation表中出現(xiàn)的順序來排列:

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

前述的方法對所有可能的文件系統(tǒng)類型均是可用的。
但是,只有其中的一個(gè)子集應(yīng)用到每個(gè)具體的文件系統(tǒng);
未實(shí)現(xiàn)的方法對應(yīng)的字段置為NULL。
注意,系統(tǒng)沒有定義get_super方法來讀超級塊,
那么,內(nèi)核如何能夠調(diào)用一個(gè)對象的方法而從磁盤讀出該對象?
我們將在描述文件系統(tǒng)類型的另一個(gè)對象中找到等價(jià)的get_sb方法
(參見后面的“文件系統(tǒng)類型注冊“一節(jié))。

索引節(jié)點(diǎn)對象

文件系統(tǒng)處理文件所需要的所有信息都放在一個(gè)名為索引節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)中。
文件名可以隨時(shí)更改,但是索引節(jié)點(diǎn)對文件是唯一的,
并且隨文件的存在而存在。
內(nèi)存中的索引節(jié)點(diǎn)對象由一個(gè)inode數(shù)據(jù)結(jié)構(gòu)組成,其字段如表12-3所示。

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

每個(gè)索引節(jié)點(diǎn)對象都會(huì)復(fù)制磁盤索引節(jié)點(diǎn)包含的一些數(shù)據(jù),
比如分配給文件的磁盤塊數(shù)。
如果i_state字段的值等于I_DIRTY_SYNC、I_DIRTY_DATASYNC或I_DIRTY_PAGES,該索引節(jié)點(diǎn)就是“臟“的,
也就是說,對應(yīng)的磁盤索引節(jié)點(diǎn)必須被更新。
I_DIRTY宏可以用來立即檢查這三個(gè)標(biāo)志的值(詳細(xì)內(nèi)容參見后面)。
i_state字段的其他值有I_LOCK(涉及的索引節(jié)點(diǎn)對象處于I/O傳送中)、I_FREEING(索引節(jié)點(diǎn)對象正在被釋放)、
I_CLEAR(索引節(jié)點(diǎn)對象的內(nèi)容不再有意義)以及I_NEW(索引節(jié)點(diǎn)對象已經(jīng)分配但還沒有用從磁盤索引節(jié)點(diǎn)讀取來的數(shù)據(jù)填充)。每個(gè)索引節(jié)點(diǎn)對象總是出現(xiàn)在下列雙向循環(huán)鏈表的某個(gè)鏈表中(所有情況下,指向相鄰元素的指針存放在i_list字段中):
1.有效未使用的索引節(jié)點(diǎn)鏈表,
典型的如那些鏡像有效的磁盤索引節(jié)點(diǎn),且當(dāng)前未被任何進(jìn)程使用。
這些索引節(jié)點(diǎn)不為臟,且它們的i_count字段置為0。
鏈表中的首元素和尾元素是由變量inode_unused的next字段和prev字段分別指向的。
這個(gè)鏈表用作磁盤高速緩存。
2.正在使用的索引節(jié)點(diǎn)鏈表,也就是那些鏡像有效的磁盤索引節(jié)點(diǎn),
且當(dāng)前被某些進(jìn)程使用。
這些索引節(jié)點(diǎn)不為臟,但它們的i_count字段為正數(shù)。
鏈表中的首元素和尾元素是由變量inode_in_use引用的。
3.臟索引節(jié)點(diǎn)的鏈表。
鏈表中的首元素和尾元素是由相應(yīng)超級塊對象的s_dirty字段引用的。
這些鏈表都是通過適當(dāng)?shù)乃饕?jié)點(diǎn)對象的i_list字段鏈接在一起的。此外,每個(gè)索引節(jié)點(diǎn)對象也包含在每文件系統(tǒng)(per-filesystem)的雙向循環(huán)鏈表中,
鏈表的頭存放在超級塊對象的s_inodes字段中;
索引節(jié)點(diǎn)對象的i_sb_list字段存放了指向鏈表相鄰元素的指針。最后,索引節(jié)點(diǎn)對象也存放在一個(gè)稱為inode_hashtable的散列表中。
散列表加快了對索引節(jié)點(diǎn)對象的搜索,
前提是系統(tǒng)內(nèi)核要知道索引節(jié)點(diǎn)號及文件所在文件系統(tǒng)對應(yīng)的超級塊對象的地址。
由于散列技術(shù)可能引發(fā)沖突,
所以索引節(jié)點(diǎn)對象包含一個(gè)i_hash字段,
該字段中包含向前和向后的兩個(gè)指針,
分別指向散列到同一地址的前一個(gè)索引節(jié)點(diǎn)和后一個(gè)索引節(jié)點(diǎn);
該字段因此創(chuàng)建了由這些索引節(jié)點(diǎn)組成的一個(gè)雙向鏈表。與索引節(jié)點(diǎn)對象關(guān)聯(lián)的方法也叫索引節(jié)點(diǎn)操作。
它們由inode_operations結(jié)構(gòu)來描述,該結(jié)構(gòu)的地址存放在i_op字段中。
以下是索引節(jié)點(diǎn)的操作,以它們在inode_operations表中出現(xiàn)的次序來排列:

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

上述列舉的方法對所有可能的索引節(jié)點(diǎn)和文件系統(tǒng)類型都是可用的。
不過,只有其中的一個(gè)子集應(yīng)用到某一特定的索引節(jié)點(diǎn)和文件系統(tǒng);
未實(shí)現(xiàn)的方法對應(yīng)的字段被置為NULL。

文件對象

文件對象描述進(jìn)程怎樣與一個(gè)打開的文件進(jìn)行交互。
文件對象是在文件被打開時(shí)創(chuàng)建的,由一個(gè)file結(jié)構(gòu)組成,
其中包含的字段如表12-4所示。
注意,文件對象在磁盤上沒有對應(yīng)的映像,因此file結(jié)構(gòu)中沒有設(shè)置“臟“字段來表示文件對象是否已被修改。

在這里插入圖片描述
在這里插入圖片描述

存放在文件對象中的主要信息是文件指針,
即文件中當(dāng)前的位置,下一個(gè)操作將在該位置發(fā)生。
由于幾個(gè)進(jìn)程可能同時(shí)訪問同一文件,因此文件指針必須存放在文件對象而不是索引節(jié)點(diǎn)對象中。
文件對象通過一個(gè)名為filp的slab高速緩存分配,
filp描述符地址存放在filp_cachep 變量中。
由于分配的文件對象數(shù)目是有限的,因此files_stat變量在其max_files字段中指定了可分配文件對象的最大數(shù)目,
也就是系統(tǒng)可同時(shí)訪問的最大文件數(shù)(注4)?!霸谑褂谩拔募ο蟀谟删唧w文件系統(tǒng)的超級塊所確立的幾個(gè)鏈表中。
每個(gè)超級塊對象把文件對象鏈表的頭存放在s_files字段中;
因此,屬于不同文件系統(tǒng)的文件對象就包含在不同的鏈表中。
鏈表中分別指向前一個(gè)元素和后一個(gè)元素的指針都存放在文件對象的f_list字段中。
files_lock自旋鎖保護(hù)超級塊的s_files鏈表免受多處理器系統(tǒng)上的同時(shí)訪問。文件對象的f_count字段是一個(gè)引用計(jì)數(shù)器:
它記錄使用文件對象的進(jìn)程數(shù)(記住,以CLONE_FILES標(biāo)志創(chuàng)建的輕量級進(jìn)程共享打開文件表,因此它們可以使用相同的文件對象)。
當(dāng)內(nèi)核本身使用該文件對象時(shí)也要增加計(jì)數(shù)器的值——例如,把對象插入鏈表中或發(fā)出dup()系統(tǒng)調(diào)用時(shí)。當(dāng)VFS代表進(jìn)程必須打開一個(gè)文件時(shí),
它調(diào)用get_empty_filp()函數(shù)來分配一個(gè)新的文件對象。
該函數(shù)調(diào)用kmem_cache_alloc()從filp高速緩存中獲得一個(gè)空閑的文件對象,
然后初始化這個(gè)對象的字段,如下所示:
memset(f,0,sizeof(*f));
INIT_LIST_HEAD(&f->f_ep_links);
spin_lock_init(&f->f_ep_lock);
atomic_set(&f->f_count,1);
f->f_uid = current->fsuid;
f->f_gid = current->fsgid;
f->f_owmer.lock = RW_LOCK_UNLOCKED;INIT_LIST_HEAD(&f->f_list〉;
f->f_maxcount = INT_MAX;
正如在“通用文件模型“一節(jié)中討論過的那樣,
每個(gè)文件系統(tǒng)都有其自己的文件操作集合,
執(zhí)行諸如讀寫文件這樣的操作。
當(dāng)內(nèi)核將一個(gè)索引節(jié)點(diǎn)從磁盤裝入內(nèi)存時(shí),
就會(huì)把指向這些文件操作的指針存放在file_operations結(jié)構(gòu)中,
而該結(jié)構(gòu)的地址存放在該索引節(jié)點(diǎn)對象的i_fop字段中。
當(dāng)進(jìn)程打開這個(gè)文件時(shí),
VFS就用存放在索引節(jié)點(diǎn)中的這個(gè)地址初始化新文件對象的f_op字段,
使得對文件操作的后續(xù)調(diào)用能夠使用這些函數(shù)。
如果需要,VFS隨后也可以通過在f_op字段存放一個(gè)新值而修改文件操作的集合。
下面的列表描述了文件的操作,以它們在file_operations表中出現(xiàn)的次序來排列:

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

以上描述的方法對所有可能的文件類型都是可用的。
不過,對于一個(gè)具體的文件類型,只使用其中的一個(gè)子集;
那些未實(shí)現(xiàn)的方法對應(yīng)的字段被置為NULL。

目錄項(xiàng)對象

在“通用文件模型“一節(jié)中我們曾提到,
VFS把每個(gè)目錄看作由若干子目錄和文件組成的一個(gè)普通文件。
在第十八章我們將會(huì)討論如何在具體的文件系統(tǒng)上實(shí)現(xiàn)目錄。
然而,一旦目錄項(xiàng)被讀入內(nèi)存,VFS就把它轉(zhuǎn)換成基于dentry結(jié)構(gòu)的一個(gè)目錄項(xiàng)對象,
該結(jié)構(gòu)的字段如表12-5所示。
對于進(jìn)程查找的路徑名中的每個(gè)分量,內(nèi)核都為其創(chuàng)建一個(gè)目錄項(xiàng)對象;
目錄項(xiàng)對象將每個(gè)分量與其對應(yīng)的索引節(jié)點(diǎn)相聯(lián)系。
例如,在查找路徑名/tmp/test時(shí),
內(nèi)核為根目錄“1“創(chuàng)建一個(gè)目錄項(xiàng)對象,
為根目錄下的tmp項(xiàng)創(chuàng)建一個(gè)第二級目錄項(xiàng)對象,
為/tmp目錄下的test項(xiàng)創(chuàng)建一個(gè)第三級目錄項(xiàng)對象。請注意,目錄項(xiàng)對象在磁盤上并沒有對應(yīng)的映像,
因此在dentry結(jié)構(gòu)中不包含指出該對象已被修改的字段。
目錄項(xiàng)對象存放在名為dentry_cache的slab分配器高速緩存中。
因此,目錄項(xiàng)對象的創(chuàng)建和刪除是通過調(diào)用kmem_cache_alloc()和kmem_cache_free()實(shí)現(xiàn)的。

在這里插入圖片描述
在這里插入圖片描述

每個(gè)目錄項(xiàng)對象可以處于以下四種狀態(tài)之一:
1.空閑狀態(tài)(free)
處于該狀態(tài)的目錄項(xiàng)對象不包括有效的信息,且還沒有被VFS使用。對應(yīng)的內(nèi)存區(qū)由slab分配器進(jìn)行處理。
2.未使用狀態(tài)(unused)
處于該狀態(tài)的目錄項(xiàng)對象當(dāng)前還沒有被內(nèi)核使用。
該對象的引用計(jì)數(shù)器d_count的值為0,但其d_inode字段仍然指向關(guān)聯(lián)的索引節(jié)點(diǎn)。
該目錄項(xiàng)對象包含有效的信息,但為了在必要時(shí)回收內(nèi)存,它的內(nèi)容可能被丟棄。
3.正在使用狀態(tài)(in use)
處于該狀態(tài)的目錄項(xiàng)對象當(dāng)前正在被內(nèi)核使用。
該對象的引用計(jì)數(shù)器d_count的值為正數(shù),其d_inode字段指向關(guān)聯(lián)的索引節(jié)點(diǎn)對象。
該目錄項(xiàng)對象包含有效的信息,并且不能被丟棄。
4.負(fù)狀態(tài)(negative)
與目錄項(xiàng)關(guān)聯(lián)的索引節(jié)點(diǎn)不復(fù)存在,那是因?yàn)橄鄳?yīng)的磁盤索引節(jié)點(diǎn)已被刪除,
或者因?yàn)槟夸涰?xiàng)對象是通過解析一個(gè)不存在文件的路徑名創(chuàng)建的。
目錄項(xiàng)對象的d_inode字段被置為NULL,但該對象仍然被保存在目錄項(xiàng)高速緩存中,
以便后續(xù)對同一文件目錄名的查找操作能夠快速完成。
術(shù)語“負(fù)狀態(tài)“容易使人誤解,因?yàn)楦静簧婕叭魏呜?fù)值。與目錄項(xiàng)對象關(guān)聯(lián)的方法稱為目錄項(xiàng)操作。
這些方法由dentry_operations結(jié)構(gòu)加以描述,該結(jié)構(gòu)的地址存放在目錄項(xiàng)對象的d_op字段中。
盡管一些文件系統(tǒng)定義了它們自己的目錄項(xiàng)方法,
但是這些字段通常為NULL,
而VFS使用缺省函數(shù)代替這些方法。
以下按照其在dentry_operations表中出現(xiàn)的順序來列舉一些方法。

在這里插入圖片描述
在這里插入圖片描述

目錄項(xiàng)高速緩存

由于從磁盤讀入一個(gè)目錄項(xiàng)并構(gòu)造相應(yīng)的目錄項(xiàng)對象需要花費(fèi)大量的時(shí)間,
所以,在完成對目錄項(xiàng)對象的操作后,可能后面還要使用它,
因此仍在內(nèi)存中保留它有重要的意義。
例如,我們經(jīng)常需要編輯文件,隨后編譯它,或者編輯并打印它,
或者復(fù)制它并編輯這個(gè)拷貝,在諸如此類的情況中,同一個(gè)文件需要被反復(fù)訪問。為了最大限度地提高處理這些目錄項(xiàng)對象的效率,Linux使用目錄項(xiàng)高速緩存,它由兩種類型的數(shù)據(jù)結(jié)構(gòu)組成:

在這里插入圖片描述

目錄項(xiàng)高速緩存的作用還相當(dāng)于索引節(jié)點(diǎn)高速緩存(inode cache)的控制器。
在內(nèi)核內(nèi)存中,并不丟棄與未用目錄項(xiàng)相關(guān)的索引節(jié)點(diǎn),
這是由于目錄項(xiàng)高速緩存仍在使用它們。因此,這些索引節(jié)點(diǎn)對象保存在RAM中,并能夠借助相應(yīng)的目錄項(xiàng)快速引用它們。
所有“未使用“目錄項(xiàng)對象都存放在一個(gè)“最近最少使用(Least Recently used,LRU)“的雙向鏈表中,
該鏈表按照插入的時(shí)間排序。
換句話說,最后釋放的目錄項(xiàng)對象放在鏈表的首部,
所以最近最少使用的目錄項(xiàng)對象總是靠近鏈表的尾部。
一旦目錄項(xiàng)高速緩存的空間開始變小,內(nèi)核就從鏈表的尾部刪除元素,
使得最近最常使用的對象得以保留。
LRU鏈表的首元素和尾元素的地址存放在list_head類型的dentry_unused變量的next字段和prev字段中。
目錄項(xiàng)對象的d_1ru字段包含指向鏈表中相鄰目錄項(xiàng)的指針。每個(gè)“正在使用“的目錄項(xiàng)對象都被插入一個(gè)雙向鏈表中,
該鏈表由相應(yīng)索引節(jié)點(diǎn)對象的i_dentry字段所指向(由于每個(gè)索引節(jié)點(diǎn)可能與若干硬鏈接關(guān)聯(lián),所以需要一個(gè)鏈表)。
目錄項(xiàng)對象的d_alias字段存放鏈表中相鄰元素的地址。這兩個(gè)字段的類型都是struct list_head。當(dāng)指向相應(yīng)文件的最后一個(gè)硬鏈接被刪除后,
一個(gè)“正在使用“的目錄項(xiàng)對象可能變成“負(fù)“狀態(tài)。
在這種情況下,該目錄項(xiàng)對象被移到“未使用“目錄項(xiàng)對象組成的LRU鏈表中。
每當(dāng)內(nèi)核縮減目錄項(xiàng)高速緩存時(shí),“負(fù)“狀態(tài)目錄項(xiàng)對象就朝著LRU鏈表的尾部移動(dòng),
這樣一來,這些對象就逐漸被釋放(參見第十七章中的“回收可壓縮磁盤高速緩存的頁“一節(jié))。散列表是由dentry_hashtable數(shù)組實(shí)現(xiàn)的。數(shù)組中的每個(gè)元素是一個(gè)指向鏈表的指針,
這種鏈表就是把具有相同散列表值的目錄項(xiàng)進(jìn)行散列而形成的。
該數(shù)組的長度取決于系統(tǒng)已安裝RAM的數(shù)量;
缺省值是每兆字節(jié)RAM包含256個(gè)元素。
目錄項(xiàng)對象的d_hash 字段包含指向具有相同散列值的鏈表中的相鄰元素。
散列函數(shù)產(chǎn)生的值是由目錄的目錄項(xiàng)對象及文件名計(jì)算出來的。dcache_lock自旋鎖保護(hù)目錄項(xiàng)高速緩存數(shù)據(jù)結(jié)構(gòu)免受多處理器系統(tǒng)上的同時(shí)訪問。
d_lookup()函數(shù)在散列表中查找給定的父目錄項(xiàng)對象和文件名;
為了避免發(fā)生競爭,使用順序鎖(seqlock)(參見第五章中的“順序鎖“一節(jié))。
__d_lookup()函數(shù)與之類似,不過它假定不會(huì)發(fā)生競爭,因此不使用順序鎖。

與進(jìn)程相關(guān)的文件

每個(gè)進(jìn)程都有它自己當(dāng)前的工作目錄和它自己的根目錄。
這僅僅是內(nèi)核用來表示進(jìn)程與文件系統(tǒng)相互作用所必須維護(hù)的數(shù)據(jù)中的兩個(gè)例子。
類型為fs_struc的整個(gè)數(shù)據(jù)結(jié)構(gòu)就用于此目的(參見表12-6),且每個(gè)進(jìn)程描述符的fs字段就指向進(jìn)程的fs_struc結(jié)構(gòu)。

在這里插入圖片描述

第二個(gè)表表示進(jìn)程當(dāng)前打開的文件表的地址存放于進(jìn)程描述符的files字段。該表的類型為files_struct結(jié)構(gòu),它的各個(gè)字段如表12-7所示。

在這里插入圖片描述
在這里插入圖片描述

fd字段指向文件對象的指針數(shù)組。該數(shù)組的長度存放在max_fds字段中。
通常,fd字段指向files_struct結(jié)構(gòu)的fd_array字段,
該字段包括32個(gè)文件對象指針。
如果進(jìn)程打開的文件數(shù)目多于32,內(nèi)核就分配一個(gè)新的、更大的文件指針數(shù)組,
并將其地址存放在fd字段中,內(nèi)核同時(shí)也更新max_fds字段的值。對于在fd數(shù)組中有元素的每個(gè)文件來說,數(shù)組的索引就是文件描述符(file descriptor)。
通常,數(shù)組的第一個(gè)元素(索引為0)是進(jìn)程的標(biāo)準(zhǔn)輸入文件,
數(shù)組的第二個(gè)元素(索引為1)是進(jìn)程的標(biāo)準(zhǔn)輸出文件,
數(shù)組的第三個(gè)元素(索引為2)是進(jìn)程的標(biāo)準(zhǔn)錯(cuò)誤文件(參見圖12-3)。
Unix進(jìn)程將文件描述符作為主文件標(biāo)識(shí)符。
請注意,借助于dup()、dup2()和fcntl()系統(tǒng)調(diào)用,
兩個(gè)文件描述符可以指向同一個(gè)打開的文件,
也就是說,數(shù)組的兩個(gè)元素可能指向同一個(gè)文件對象。
當(dāng)用戶使用shell結(jié)構(gòu)(如2>&1)將標(biāo)準(zhǔn)錯(cuò)誤文件重定向到標(biāo)準(zhǔn)輸出文件上時(shí),
用戶總能看到這一點(diǎn)。進(jìn)程不能使用多于NR_OPEN(通常為1048576)個(gè)文件描述符。
內(nèi)核也在進(jìn)程描述符的signal->rlim[RLIMIT_NOFILE]結(jié)構(gòu)上強(qiáng)制動(dòng)態(tài)限制文件描述符的最大數(shù);
這個(gè)值通常為1024,但是如果進(jìn)程具有超級用戶特權(quán),就可以增大這個(gè)值。open_fds字段最初包含open_fds_init字段的地址,
open_fds_init字段表示當(dāng)前已打開文件的文件描述符的位圖。
max_fdset字段存放位圖中的位數(shù)。
由于fd_set數(shù)據(jù)結(jié)構(gòu)有1024位,所以通常不需要擴(kuò)大位圖的大小。
不過,如果確有必要的話,內(nèi)核仍能動(dòng)態(tài)增加位圖的大小,這非常類似于文件對象的數(shù)組的情形。
當(dāng)內(nèi)核開始使用一個(gè)文件對象時(shí),內(nèi)核提供fget()函數(shù)以供調(diào)用。
這個(gè)函數(shù)接收文件描述符fd作為參數(shù),返回在current->files->fd[fd]中的地址,即對應(yīng)文件對象的地址,
如果沒有任何文件與fd對應(yīng),則返回NULL。
在第一種情況下,fget()使文件對象引用計(jì)數(shù)器f_count的值增1。

在這里插入圖片描述

當(dāng)內(nèi)核控制路徑完成對文件對象的使用時(shí),調(diào)用內(nèi)核提供的fput()函數(shù)。
該函數(shù)將文件對象的地址作為參數(shù),并減少文件對象引用計(jì)數(shù)器f_count的值。
另外,如果這個(gè)字段變?yōu)?,
該函數(shù)就調(diào)用文件操作的release方法(如果已定義),減少索引節(jié)點(diǎn)對象的i_write count字段的值(如果該文件是可寫的),
將文件對象從超級塊鏈表中移走,釋放文件對象給slab分配器,
最后減少相關(guān)的文件系統(tǒng)描述符的目錄項(xiàng)對象的引用計(jì)數(shù)器的值(參見“文件系統(tǒng)安裝”一節(jié))。fget_light()和fget_light()函數(shù)是fget()和fput()的快速版本:
內(nèi)核要使用它們,前提是能夠安全地假設(shè)當(dāng)前進(jìn)程已經(jīng)擁有文件對象,
即進(jìn)程先前已經(jīng)增加了文件對象引用計(jì)數(shù)器的值。
例如,它們由接收一個(gè)文件描述符作為參數(shù)的系統(tǒng)調(diào)用服務(wù)例程使用,
這是由于先前的open()系統(tǒng)調(diào)用已經(jīng)增加了文件對象引用計(jì)數(shù)器的值。

文件系統(tǒng)類型

Linux內(nèi)核支持很多不同的文件系統(tǒng)類型。
在下面的內(nèi)容中,我們介紹一些特殊的文件系統(tǒng)類型,它們在Linux內(nèi)核的內(nèi)部設(shè)計(jì)中具有非常重要的作用。
接下來,我們將討論文件系統(tǒng)注冊——也就是通常在系統(tǒng)初始化期間并且在使用文件系統(tǒng)類型之前必須執(zhí)行的基本操作。
一旦文件系統(tǒng)被注冊,其特定的函數(shù)對內(nèi)核就是可用的,因此文件系統(tǒng)類型可以安裝在系統(tǒng)的目錄樹上。

特殊文件系統(tǒng)

當(dāng)網(wǎng)絡(luò)和磁盤文件系統(tǒng)能夠使用戶處理存放在內(nèi)核之外的信息時(shí),
特殊文件系統(tǒng)可以為系統(tǒng)程序員和管理員提供一種容易的方式來操作內(nèi)核的數(shù)據(jù)結(jié)構(gòu)并實(shí)現(xiàn)操作系統(tǒng)的特殊特征。
表12-8列出了Linux中所用的最常用的特殊文件系統(tǒng);對于其中的每個(gè)文件系統(tǒng),表中給出了它的安裝點(diǎn)和簡短描述。
注意,有幾個(gè)文件系統(tǒng)沒有固定的安裝點(diǎn)(表中的關(guān)鍵詞“任意”)。
這些文件系統(tǒng)可以由用戶自由地安裝和使用。
此外,一些特殊文件系統(tǒng)根本沒有安裝點(diǎn)(表中的關(guān)鍵詞“無”)。
它們不是用于與用戶交互,但是內(nèi)核可以用它們來很容易地重新使用VFS層的某些代碼;
例如,我們在第十九章會(huì)看到,有了pipefs特殊文件系統(tǒng),就可以把管道和FIFO文件以相同的方式對待。

在這里插入圖片描述

特殊文件系統(tǒng)不限于物理塊設(shè)備。
然而,內(nèi)核給每個(gè)安裝的特殊文件系統(tǒng)分配一個(gè)虛擬的塊設(shè)備,
讓其主設(shè)備號為0而次設(shè)備號具有任意值(每個(gè)特殊文件系統(tǒng)有不同的值)。
set_anon_super()函數(shù)用于初始化特殊文件系統(tǒng)的超級塊;
該函數(shù)本質(zhì)上獲得一個(gè)未使用的次設(shè)備號dev,
然后用主設(shè)備號0和次設(shè)備號dev設(shè)置新超級塊的s_dev字段。
而另一個(gè)kill_anon_super()函數(shù)移走特殊文件系統(tǒng)的超級塊。
unnamed_dev_idr變量包含指向一個(gè)輔助結(jié)構(gòu)(記錄當(dāng)前在用的次設(shè)備號)的指針。
盡管有些內(nèi)核設(shè)計(jì)者不喜歡虛擬塊設(shè)備標(biāo)識(shí)符,但是這些標(biāo)識(shí)符有助于內(nèi)核以統(tǒng)一的方式處理特殊文件系統(tǒng)和普通文件系統(tǒng)。
我們在后面“安裝普通文件系統(tǒng)“一節(jié)會(huì)看到內(nèi)核如何定義和初始化一個(gè)特殊文件系統(tǒng)的實(shí)際例子。

文件系統(tǒng)類型注冊

通常,用戶在為自己的系統(tǒng)編譯內(nèi)核時(shí)可以把Linux配置為能夠識(shí)別所有需要的文件系統(tǒng)。
但是,文件系統(tǒng)的源代碼實(shí)際上要么包含在內(nèi)核映像中,
要么作為一個(gè)模塊被動(dòng)態(tài)裝入(參見附錄二)。
VFS必須對代碼目前已在內(nèi)核中的所有文件系統(tǒng)的類型進(jìn)行跟蹤。
這就是通過進(jìn)行文件系統(tǒng)類型注冊來實(shí)現(xiàn)的。
每個(gè)注冊的文件系統(tǒng)都用一個(gè)類型為file_system_type的對象來表示,該對象的所有字段在表12-9中列出。

在這里插入圖片描述

所有文件系統(tǒng)類型的對象都插入到一個(gè)單向鏈表中。
由變量file_systems指向鏈表的第一個(gè)元素,
而結(jié)構(gòu)中的next字段指向鏈表的下一個(gè)元素。
file_systems_lock讀/寫自旋鎖保護(hù)整個(gè)鏈表免受同時(shí)訪問。fs_supers字段表示給定類型的已安裝文件系統(tǒng)所對應(yīng)的超級塊鏈表的頭(第一個(gè)偽元素)。
鏈表元素的向后和向前鏈接存放在超級塊對象的s_instances字段中。
get_sb字段指向依賴于文件系統(tǒng)類型的函數(shù),
該函數(shù)分配一個(gè)新的超級塊對象并初始化它(如果需要,可讀磁盤)。
而kill_sb字段指向刪除超級塊的函數(shù)。
fs_flags字段存放幾個(gè)標(biāo)志,如表12-10所示。

在這里插入圖片描述

在系統(tǒng)初始化期間,調(diào)用register_filesystem()函數(shù)來注冊編譯時(shí)指定的每個(gè)文件系統(tǒng);
該函數(shù)把相應(yīng)的file_system_type對象插入到文件系統(tǒng)類型的鏈表中。
當(dāng)實(shí)現(xiàn)了文件系統(tǒng)的模塊被裝入時(shí),也要調(diào)用register_filesystem()函數(shù)。
在這種情況下,當(dāng)該模塊被卸載時(shí),對應(yīng)的文件系統(tǒng)也可以被注銷(調(diào)用unregister_filesystem()函數(shù))。
get_fs_type()函數(shù)(接收文件系統(tǒng)名作為它的參數(shù))掃描已注冊的文件系統(tǒng)鏈表以查找文件系統(tǒng)類型的name字段,
并返回指向相應(yīng)的file_system_type對象(如果存在)的指針。

文件系統(tǒng)處理

就像每個(gè)傳統(tǒng)的Unix系統(tǒng)一樣,
Linux也使用系統(tǒng)的根文件系統(tǒng)(system's rootfilesystem):
它由內(nèi)核在引導(dǎo)階段直接安裝,并擁有系統(tǒng)初始化腳本以及最基本的系統(tǒng)程序。
其他文件系統(tǒng)要么由初始化腳本安裝,要么由用戶直接安裝在已安裝文件系統(tǒng)的目錄上。
作為一個(gè)目錄樹,每個(gè)文件系統(tǒng)都擁有自己的根目錄(root directory)。
安裝文件系統(tǒng)的這個(gè)目錄稱之為安裝點(diǎn)(mount point)。
已安裝文件系統(tǒng)屬于安裝點(diǎn)目錄的一個(gè)子文件系統(tǒng)。
例如,
/proc虛擬文件系統(tǒng)是系統(tǒng)的根文件系統(tǒng)的孩子(且系統(tǒng)的根文件系統(tǒng)是/proc的父親)。
已安裝文件系統(tǒng)的根目錄隱藏了父文件系統(tǒng)的安裝點(diǎn)目錄原來的內(nèi)容,
而且父文件系統(tǒng)的整個(gè)子樹位于安裝點(diǎn)之下。文件系統(tǒng)的根目錄有可能不同于進(jìn)程的根目錄:
正如我們在前面“與文件相關(guān)的進(jìn)程“一節(jié)所見,
進(jìn)程的根目錄是與“/“路徑對應(yīng)的目錄。
缺省情況下,進(jìn)程的根目錄與系統(tǒng)的根文件系統(tǒng)的根目錄一致
(更準(zhǔn)確地說是與進(jìn)程的命名空間中的根文件系統(tǒng)的根目錄一致,這一點(diǎn)將在下一節(jié)描述),
但是可以通過調(diào)用chroot()系統(tǒng)調(diào)用改變進(jìn)程的根目錄。

命名空間

在傳統(tǒng)的Unix系統(tǒng)中,只有一個(gè)已安裝文件系統(tǒng)樹:
從系統(tǒng)的根文件系統(tǒng)開始,
每個(gè)進(jìn)程通過指定合適的路徑名可以訪問已安裝文件系統(tǒng)中的任何文件。
從這個(gè)方面考慮,Linux 2.6更加的精確:每個(gè)進(jìn)程可擁有自己的已安裝文件系統(tǒng)樹——叫做進(jìn)程的命名空間(namespace)。通常大多數(shù)進(jìn)程共享同一個(gè)命名空間,即位于系統(tǒng)的根文件系統(tǒng)且被init進(jìn)程使用的已安裝文件系統(tǒng)樹。
不過,如果clone()系統(tǒng)調(diào)用以CLONE_NEWNS標(biāo)志創(chuàng)建一個(gè)新進(jìn)程,
那么進(jìn)程將獲取一個(gè)新的命名空間(參見第三章的“clone()、fork()及vfork()系統(tǒng)調(diào)用“一節(jié))。
這個(gè)新的命名空間隨后由子進(jìn)程繼承(如果父進(jìn)程沒有以CLONE_NEWNS標(biāo)志創(chuàng)建這些子進(jìn)程)。
當(dāng)進(jìn)程安裝或卸載一個(gè)文件系統(tǒng)時(shí),僅修改它的命名空間。
因此,所做的修改對共享同一命名空間的所有進(jìn)程都是可見的,
并且也只對它們可見。
進(jìn)程甚至可通過使用Linux 特有的pivot_root()系統(tǒng)調(diào)用來改變它的命名空間的根文件系統(tǒng)。
進(jìn)程的命名空間由進(jìn)程描述符的namespace字段指向的namespace結(jié)構(gòu)描述。該結(jié)構(gòu)的字段如表12-11所示。

在這里插入圖片描述

list字段是雙向循環(huán)鏈表的頭,該表聚集了屬于命名空間的所有已安裝文件系統(tǒng)。
root 字段表示已安裝文件系統(tǒng),它是這個(gè)命名空間的已安裝文件系統(tǒng)樹的根。
正如我們在下一節(jié)將看到的,已安裝文件系統(tǒng)由vfsmount結(jié)構(gòu)描述。

文件系統(tǒng)安裝

在大多數(shù)傳統(tǒng)的類Unix內(nèi)核中,每個(gè)文件系統(tǒng)只能安裝一次。
假定存放在/dev/fd0軟磁盤上的Ext2文件系統(tǒng)通過如下命令安裝在/flp:
mount -t ext2 /dev/fd0 /flp
在用umount命令卸載該文件系統(tǒng)前,所有其他作用于/dev/fd0的安裝命令都會(huì)失敗。
然而,Linux有所不同:
同一個(gè)文件系統(tǒng)被安裝多次是可能的。
當(dāng)然,如果一個(gè)文件系統(tǒng)被安裝了n次,
那么它的根目錄就可通過n個(gè)安裝點(diǎn)來訪問。
盡管同一文件系統(tǒng)可以通過不同的安裝點(diǎn)來訪問,但是文件系統(tǒng)的的確確是唯一的。
因此,不管一個(gè)文件系統(tǒng)被安裝了多少次,都僅有一個(gè)超級塊對象。安裝的文件系統(tǒng)形成一個(gè)層次:
一個(gè)文件系統(tǒng)的安裝點(diǎn)可能成為第二個(gè)文件系統(tǒng)的目錄,而第二個(gè)文件系統(tǒng)又安裝在第三個(gè)文件系統(tǒng)之上,等等(注6)。
把多個(gè)安裝堆疊在一個(gè)單獨(dú)的安裝點(diǎn)上也是可能的。
盡管已經(jīng)使用先前安裝下的文件和目錄的進(jìn)程可以繼續(xù)使用,但在同一安裝點(diǎn)上的新安裝隱藏前一個(gè)安裝的文件系統(tǒng)。
當(dāng)最頂層的安裝被刪除時(shí),下一層的安裝再一次變?yōu)榭梢姷摹?你可以想像,跟蹤已安裝的文件系統(tǒng)很快會(huì)變?yōu)橐粓鰫簤簟?對于每個(gè)安裝操作,內(nèi)核必須在內(nèi)存中保存安裝點(diǎn)和安裝標(biāo)志,
以及要安裝文件系統(tǒng)與其他已安裝文件系統(tǒng)之間的關(guān)系。
這樣的信息保存在已安裝文件系統(tǒng)描述符中;
每個(gè)描述符是一個(gè)具有vfsmount 類型的數(shù)據(jù)結(jié)構(gòu),其字段如表12-12所示。

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

vfsmount數(shù)據(jù)結(jié)構(gòu)保存在幾個(gè)雙向循環(huán)鏈表中:
1.由父文件系統(tǒng)vfsmount描述符的地址和安裝點(diǎn)目錄的目錄項(xiàng)對象的地址索引的散列表。
散列表存放在mount_hashtable數(shù)組中,其大小取決于系統(tǒng)中RAM的容量。
表中每一項(xiàng)是具有同一散列值的所有描述符形成的雙向循環(huán)鏈表的頭。
描述符的mnt_hash字段包含指向鏈表中相鄰元素的指針。
2.對于每一個(gè)命名空間,所有屬于此命名空間的已安裝的文件系統(tǒng)描述符形成了一個(gè)雙向循環(huán)鏈表。
namespace結(jié)構(gòu)的list字段存放鏈表的頭,vfsmount描述符的mnt_list字段包含鏈表中指向相鄰元素的指針。
3.對于每一個(gè)已安裝的文件系統(tǒng),所有已安裝的子文件系統(tǒng)形成了一個(gè)雙向循環(huán)鏈表。
每個(gè)鏈表的頭存放在已安裝的文件系統(tǒng)描述符的mnt_mounts字段;
此外,描述符的mnt_child字段存放指向鏈表中相鄰元素的指針vfsmount_lock自旋鎖保護(hù)已安裝文件系統(tǒng)對象的鏈表免受同時(shí)訪問。
描述符的mnt_flags字段存放幾個(gè)標(biāo)志的值,用以指定如何處理已安裝文件系統(tǒng)中的某些種類的文件。
這些標(biāo)志可通過mount命令的選項(xiàng)進(jìn)行設(shè)置,其標(biāo)志如表12-13所示。

在這里插入圖片描述

下列函數(shù)處理已安裝文件系統(tǒng)描述符:

在這里插入圖片描述

安裝普通文件系統(tǒng)

我們現(xiàn)在描述安裝一個(gè)文件系統(tǒng)時(shí)內(nèi)核所要執(zhí)行的操作。
我們首先考慮一個(gè)文件系統(tǒng)將被安裝在一個(gè)已安裝文件系統(tǒng)之上的情形(在這里我們把這種新文件系統(tǒng)看作“普通的”)。
mount()系統(tǒng)調(diào)用被用來安裝一個(gè)普通文件系統(tǒng);它的服務(wù)例程sys_mount()作用于以下參數(shù):

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

sys_mount()函數(shù)把參數(shù)的值拷貝到臨時(shí)內(nèi)核緩沖區(qū),獲取大內(nèi)核鎖,并調(diào)用do_mount()函數(shù)。
一旦do_mount()返回,則這個(gè)服務(wù)例程釋放大內(nèi)核鎖并釋放臨時(shí)內(nèi)核緩沖區(qū)。
do_mount()函數(shù)通過執(zhí)行下列操作處理真正的安裝操作:
1.  如果安裝標(biāo)志MS_NOSUID、MS_NODEV或MS_NOEXEC中任一個(gè)被設(shè)置,
則清除它們,并在已安裝文件系統(tǒng)對象中設(shè)置相應(yīng)的標(biāo)志(MNT_NOSUID、MNT_NODEV、MNT_NOEXEC)。
2.   調(diào)用path_lookup()查找安裝點(diǎn)的路徑名;
該函數(shù)把路徑名查找的結(jié)果存放在nameidata類型的局部變量nd中(參見后面的“路徑名查找”一節(jié))。
3.  檢查安裝標(biāo)志以決定必須做什么。尤其是:
a.如果MS_REMOUNT標(biāo)志被指定,其目的通常是改變超級塊對象s_flags字段的安裝標(biāo)志,
以及已安裝文件系統(tǒng)對象mnt_flags字段的安裝文件系統(tǒng)標(biāo)志。
do_remount()函數(shù)執(zhí)行這些改變。
b.否則,檢查MS_BIND標(biāo)志。
如果它被指定,則用戶要求在在系統(tǒng)目錄樹的另一個(gè)安裝點(diǎn)上的文件或目錄能夠可見。
c.否則,檢查MS_MOVE標(biāo)志。
如果它被指定,則用戶要求改變已安裝文件系統(tǒng)的安裝點(diǎn)。
do_move_mount()函數(shù)原子地完成這一任務(wù)。
d.  否則,調(diào)用do_new_mount()。
這是最普通的情況。
當(dāng)用戶要求安裝一個(gè)特殊文件系統(tǒng)或存放在磁盤分區(qū)中的普通文件系統(tǒng)時(shí),觸發(fā)該函數(shù)。
它調(diào)用do_kern_mount()函數(shù),給它傳遞的參數(shù)為文件系統(tǒng)類型、安裝標(biāo)志以及塊設(shè)備名。
do_kern_mount()處理實(shí)際的安裝操作并返回一個(gè)新安裝文件系統(tǒng)描述符的地址(如下描述)。
然后,do_new_mount()調(diào)用do_add_mount(),后者本質(zhì)上執(zhí)行下列操作;
(1)獲得當(dāng)前進(jìn)程的寫信號量namespace->sem,因?yàn)楹瘮?shù)要更改namespace結(jié)構(gòu)。
(2)do_kern_mount()函數(shù)可能讓當(dāng)前進(jìn)程睡眠;
同時(shí),另一個(gè)進(jìn)程可能在完全相同的安裝點(diǎn)上安裝文件系統(tǒng)或者甚至更改根文件系統(tǒng)(current->namespace->root)。
驗(yàn)證在該安裝點(diǎn)上最近安裝的文件系統(tǒng)是否仍指向當(dāng)前的namespace;
如果不是,則釋放讀/寫信號量并返回一個(gè)錯(cuò)誤碼。
(3)如果要安裝的文件系統(tǒng)已經(jīng)被安裝在由系統(tǒng)調(diào)用的參數(shù)所指定的安裝點(diǎn)上,
或該安裝點(diǎn)是一個(gè)符號鏈接,則釋放讀/寫信號量并返回一個(gè)錯(cuò)誤碼。
(4)初始化由do_kern_mount()分配的新安裝文件系統(tǒng)對象的mnt_flags字段的標(biāo)志。
(5)調(diào)用graft_tree()把新安裝的文件系統(tǒng)對象插入到namespace鏈表、散列表及父文件系統(tǒng)的子鏈表中。
(6)釋放namespace->sem讀/寫信號量并返回。
4.  調(diào)用path_release()終止安裝點(diǎn)的路徑名查找(參見后面的“路徑名查找“一節(jié))并返回0。

do_kern_mount()函數(shù)

安裝操作的核心是do_kern_mount()函數(shù),它檢查文件系統(tǒng)類型標(biāo)志以決定安裝操作是如何完成的。該函數(shù)接收下列參數(shù):

在這里插入圖片描述

本質(zhì)上,該函數(shù)通過執(zhí)行下列操作實(shí)現(xiàn)實(shí)際的安裝操作:
1.  調(diào)用get_fs_type()在文件系統(tǒng)類型鏈表中搜索并確定存放在fstype參數(shù)中的名字的位置;
返回局部變量type中對應(yīng)file_system_type描述符的地址。
2.   調(diào)用alloc_vfsmnt()分配一個(gè)新的已安裝文件系統(tǒng)的描述符,
并將它的地址存放在mnt局部變量中。
3.  調(diào)用依賴于文件系統(tǒng)的type->get_sb()函數(shù)分配,并初始化一個(gè)新的超級塊(參見下面)。
4.  用新超級塊對象的地址初始化mnt->mnt_sb字段。
5.  將mnt->mnt_root字段初始化為與文件系統(tǒng)根目錄對應(yīng)的目錄項(xiàng)對象的地址,
并增加該目錄項(xiàng)對象的引用計(jì)數(shù)器值。
6.  用mnt中的值初始化mnt->mnt_parent字段
(對于普通文件系統(tǒng),當(dāng)graft_tree()把已安裝文件系統(tǒng)的描述符插入到合適的鏈表中時(shí),要把mnt_parent字段置為合適的值;
參見do_mount()的第3d5步)。
7.  用current->namespace中的值初始化mnt->mnt_namespace字段。
8.  釋放超級塊對象的讀/寫信號量s_umount(在第3步中分配對象時(shí)獲得)。
9.  返回已安裝文件系統(tǒng)對象的地址mnt。

分配超級塊對象

文件系統(tǒng)對象的get_sb方法通常是由單行函數(shù)實(shí)現(xiàn)的。例如,在Ext2文件系統(tǒng)中該方法的實(shí)現(xiàn)如下:
struct super_block * ext2_get_sb(struct file_system_type *type, int flags,const char *dev_name,void *data)
{return get_sb_bdev(type,flags,dev_name,data,ext2_fill_super);
}
get_sb_bdev()VFS函數(shù)分配并初始化一個(gè)新的適合于磁盤文件系統(tǒng)的超級塊;
它接收ext2_fill_super()函數(shù)的地址,該函數(shù)從Ext2磁盤分區(qū)讀取磁盤超級塊。
為了分配適合于特殊文件系統(tǒng)的超級塊,
VFS也提供
get_sb_pseudo()函數(shù)(對于沒有安裝點(diǎn)的特殊文件系統(tǒng),例如pipefs)、
get_sb_single()函數(shù)(對于具有唯一安裝點(diǎn)的特殊文件系統(tǒng),例如sysfs)
以及get_sb_nodev()函數(shù)(對于可以安裝多次的特殊文件系統(tǒng),例如tmpfs;參見下面)。
get_sb_bdev()執(zhí)行的最重要的操作如下:
1.  調(diào)用open_bdev_excl()打開設(shè)備文件名為dev_name的塊設(shè)備(參見第十三章的“字符設(shè)備驅(qū)動(dòng)程序“一節(jié))。
2.  調(diào)用sget()搜索文件系統(tǒng)的超級塊對象鏈表(type->fs_supers,參見前面的“文件系統(tǒng)類型注冊”一節(jié))。
如果找到一個(gè)與塊設(shè)備相關(guān)的超級塊,則返回它的地址。
否則,分配并初始化一個(gè)新的超級塊對象,把它插入到文件系統(tǒng)鏈表和超級塊全局鏈表中,并返回其地址。
3.  如果不是新的超級塊(它不是上一步分配的,因?yàn)槲募到y(tǒng)已經(jīng)被安裝),則跳到第6步。
4.  把參數(shù)flags中的值拷貝到超級塊的s_flags字段,并將s_id、s_old_blocksize以及s_blocksize字段設(shè)置為塊設(shè)備的合適值。
5.  調(diào)用依賴文件系統(tǒng)的函數(shù)(該函數(shù)作為傳遞給get_sb_bdev()的最后一個(gè)參數(shù))訪問磁盤上的超級塊信息,并填充新超級塊對象的其他字段。
6.  返回新超級塊對象的地址。

安裝根文件系統(tǒng)

安裝根文件系統(tǒng)是系統(tǒng)初始化的關(guān)鍵部分。
這是一個(gè)相當(dāng)復(fù)雜的過程,因?yàn)長inux內(nèi)核允許根文件系統(tǒng)存放在很多不同的地方,
比如硬盤分區(qū)、軟盤、通過NFS共享的遠(yuǎn)程文件系統(tǒng),甚至保存在ramdisk中(RAM中的虛擬塊設(shè)備)。
為了使敘述變得簡單,讓我們假定根文件系統(tǒng)存放在硬盤分區(qū)(畢竟這是最常見的情況)。
當(dāng)系統(tǒng)啟動(dòng)時(shí),內(nèi)核就要在變量ROOT_DEV中尋找包含根文件系統(tǒng)的磁盤主設(shè)備號(參見附錄一)。
當(dāng)編譯內(nèi)核時(shí),或者向最初的啟動(dòng)裝入程序傳遞一個(gè)合適的“root”選項(xiàng)時(shí),
根文件系統(tǒng)可以被指定為/dev目錄下的一個(gè)設(shè)備文件。
類似地,
根文件系統(tǒng)的安裝標(biāo)志存放在root_mountflags變量中。
用戶可以指定這些標(biāo)志,或者通過對已編譯的內(nèi)核映像使用rdev外部程序,
或者向最初的啟動(dòng)裝入程序傳遞一個(gè)合適的rootflags選項(xiàng)來達(dá)到(參見附錄一)。安裝根文件系統(tǒng)分兩個(gè)階段,如下所示:
1.  內(nèi)核安裝特殊rootfs文件系統(tǒng),該文件系統(tǒng)僅提供一個(gè)作為初始安裝點(diǎn)的空目錄。
2.  內(nèi)核在空目錄上安裝實(shí)際根文件系統(tǒng)。
為什么內(nèi)核不怕麻煩,要在安裝實(shí)際根文件系統(tǒng)之前安裝rootfs文件系統(tǒng)呢?
我們知道,rootfs文件系統(tǒng)允許內(nèi)核容易地改變實(shí)際根文件系統(tǒng)。
事實(shí)上,在某些情況下,內(nèi)核逐個(gè)地安裝和卸載幾個(gè)根文件系統(tǒng)。
例如,
一個(gè)發(fā)布版的初始啟動(dòng)光盤可能把具有一組最小驅(qū)動(dòng)程序的內(nèi)核裝入RAM中,
內(nèi)核把存放在ramdisk中的一個(gè)最小的文件系統(tǒng)作為根安裝。
接下來,在這個(gè)初始根文件系統(tǒng)中的程序探測系統(tǒng)的硬件(例如,它們判斷硬盤是否是EIDE、SCSI等等),裝入所有必需的內(nèi)核模塊,
并從物理塊設(shè)備重新安裝根文件系統(tǒng)。

階段1:安裝rootfs文件系統(tǒng)

第一階段是由init_rootfs()和init_mount_tree()函數(shù)完成的,它們在系統(tǒng)初始化過程中執(zhí)行。
init_rootfs()函數(shù)注冊特殊文件系統(tǒng)類型rootfs;
struct file_system_type rootfs_fs_type ={.name ="rootfs“;·get_sb = rootfs_get_sb;.kill_sb= kill_litter_super;
};
register_filesystem(&rootfs_fs_type);
init_mount_tree()函數(shù)執(zhí)行如下操作:
1.  調(diào)用do_kern_mount()函數(shù),
把字符串“rootfs”作為文件系統(tǒng)類型參數(shù)傳遞給它,
并把該函數(shù)返回的新安裝文件系統(tǒng)描述符的地址保存在mnt局部變量中。
正如前一節(jié)所介紹的,do_kern_mount()最終調(diào)用rootfs文件系統(tǒng)的get_sb方法,也即rootfs_get_sb()函數(shù):
struct superblock *rootfs_get_sb(struct file_system_type *fs_type, int flags,const char *dev_name,void *data)
{return get_sb_nodev(fs_type,flagsIMS_NOUSER,data, ramfs_fill_super);
}
get_sb_nodev()函數(shù)執(zhí)行如下步驟:
a.調(diào)用sget()函數(shù)分配新的超級塊,傳遞set_anon_super()函數(shù)的地址作為參數(shù)(參見前面的“特殊文件系統(tǒng)“一節(jié))。
接下來,用合適的方式設(shè)置超級塊的s_dev字段:
主設(shè)備號為0, 次設(shè)備號不同于其他已安裝的特殊文件系統(tǒng)的次設(shè)備號。
b.將flags參數(shù)的值拷貝到超級塊的s_flags字段中。
c.調(diào)用ramfs_fill_super()函數(shù)分配索引節(jié)點(diǎn)對象和對應(yīng)的目錄項(xiàng)對象,
并填充超級塊字段值。
由于rootfs是一種特殊文件系統(tǒng),沒有磁盤超級塊,因此只需執(zhí)行兩個(gè)超級塊操作。
d.返回新超級塊的地址。
2.  為進(jìn)程0的命名空間分配一個(gè)namespace對象,并將它插入到由do_kern_mount()函數(shù)返回的已安裝文件系統(tǒng)描述符中:
namespace = kmalloc(sizeof(*namespace〉,GFP_KERNEL);
list_add(&mnt->mnt_list,&namespace->list);
namespace->root = mnt;
mnt->mnt_namespace = init_task.namespace = namespace;
3.  將系統(tǒng)中其他每個(gè)進(jìn)程的namespace字段設(shè)置為namespace對象的地址;
同時(shí)初始化引用計(jì)數(shù)器namespace->count(缺省情況下,所有的進(jìn)程共享同一個(gè)初始namespace)。
4.  將進(jìn)程0的根目錄和當(dāng)前工作目錄設(shè)置為根文件系統(tǒng)。

階段2:安裝實(shí)際根文件系統(tǒng)

根文件系統(tǒng)安裝操作的第二階段是由內(nèi)核在系統(tǒng)初始化即將結(jié)束時(shí)進(jìn)行的。
根據(jù)內(nèi)核被編譯時(shí)所選擇的選項(xiàng),和內(nèi)核裝入程序所傳遞的啟動(dòng)選項(xiàng),
可以有幾種方法安裝實(shí)際根文件系統(tǒng)。
為了簡單起見,我們只考慮磁盤文件系統(tǒng)的情況,
它的設(shè)備文件名已通過“root”啟動(dòng)參數(shù)傳遞給內(nèi)核。
同時(shí)我們假定除了rootfs文件系統(tǒng)外,沒有使用其他初始特殊文件系統(tǒng)。
prepare_namespace()函數(shù)執(zhí)行如下操作:
1.  把root_device_name變量置為從啟動(dòng)參數(shù)“root”中獲取的設(shè)備文件名。同樣,
把ROOT_DEV變量置為同一設(shè)備文件的主設(shè)備號和次設(shè)備號。
2.  調(diào)用mount_root()函數(shù),依次執(zhí)行如下操作:
a.調(diào)用sys_mknod()(mknod()系統(tǒng)調(diào)用的服務(wù)例程)在rootfs初始根文件系統(tǒng)中創(chuàng)建設(shè)備文件/dev/root,
其主、次設(shè)備號與存放在ROOT_DEV中的一樣。
b.分配一個(gè)緩沖區(qū)并用文件系統(tǒng)類型名鏈表填充它。
該鏈表要么通過啟動(dòng)參數(shù)“rootfstype”傳送給內(nèi)核,
要么通過掃描文件系統(tǒng)類型單向鏈表中的元素建立。
c.掃描上一步建立的文件系統(tǒng)類型名鏈表。
對每個(gè)名字,調(diào)用sys_mount()試圖在根設(shè)備上安裝給定的文件系統(tǒng)類型。
由于每個(gè)特定于文件系統(tǒng)的方法使用不同的魔數(shù),
因此,對get_sb()的調(diào)用大都會(huì)失敗,
但有一個(gè)例外,那就是用根設(shè)備上實(shí)際使用過的文件系統(tǒng)的函數(shù)來填充超級塊的那個(gè)調(diào)用,
該文件系統(tǒng)被安裝在rootfs文件系統(tǒng)的/root目錄上。
d.調(diào)用sys_chdir(“/root”)改變進(jìn)程的當(dāng)前目錄。
3.  移動(dòng)rootfs文件系統(tǒng)根目錄上的已安裝文件系統(tǒng)的安裝點(diǎn)。
sys_mount(".”,“/",NULL,MS_MOVE,NULL);
sys_chroot(".");
注意,rootfs特殊文件系統(tǒng)沒有被卸載:它只是隱藏在基于磁盤的根文件系統(tǒng)下了。

卸載文件系統(tǒng)

umount()系統(tǒng)調(diào)用用來卸載一個(gè)文件系統(tǒng)。
相應(yīng)的sys_umount()服務(wù)例程作用于兩個(gè)參數(shù):文件名(多是安裝點(diǎn)目錄或是塊設(shè)備文件名)和一組標(biāo)志。
該函數(shù)執(zhí)行下列操作:
1.  調(diào)用path_lookup()查找安裝點(diǎn)路徑名;該函數(shù)把返回的查找操作結(jié)果存放在nameidata類型的局部變量nd中(參見下一節(jié))。
2.  如果查找的最終目錄不是文件系統(tǒng)的安裝點(diǎn),
則設(shè)置retval返回碼為-EINVAL并跳到第6步。
這種檢查是通過驗(yàn)證nd->mnt->mnt_root(它包含由nd.dentry指向的目錄項(xiàng)對象地址)進(jìn)行的。
3.  如果要卸載的文件系統(tǒng)還沒有安裝在命名空間中,
則設(shè)置retval返回碼為-EINVAL并跳到第6步(回想一下,某些特殊文件系統(tǒng)沒有安裝點(diǎn))。
這種檢查是通過在nd->mnt上調(diào)用check_mnt()函數(shù)進(jìn)行的。
4.  如果用戶不具有卸載文件系統(tǒng)的特權(quán),
則設(shè)置retval返回碼為-EPERM并跳到第6步。
5.   調(diào)用do_umount(),傳遞給它的參數(shù)為nd.mnt(已安裝文件系統(tǒng)對象)和flags(一組標(biāo)志)。該函數(shù)執(zhí)行下列操作:
a.從已安裝文件系統(tǒng)對象的mnt_sb字段檢索超級塊對象sb的地址。
b.如果用戶要求強(qiáng)制卸載操作,則調(diào)用umount_begin超級塊操作中斷任何正在進(jìn)行的安裝操作。
c.如果要卸載的文件系統(tǒng)是根文件系統(tǒng),且用戶并不要求真正地把它卸載下來,
則調(diào)用do_remount_sb()重新安裝根文件系統(tǒng)為只讀并終止。
d.為進(jìn)行寫操作而獲取當(dāng)前進(jìn)程的namespace->sem讀/寫信號量和vfsmount_lock自旋鎖。
e.如果已安裝文件系統(tǒng)不包含任何子安裝文件系統(tǒng)的安裝點(diǎn),
或者用戶要求強(qiáng)制卸載文件系統(tǒng),則調(diào)用umount_tree()卸載文件系統(tǒng)(及其所有子文件系統(tǒng))。
f.釋放vfsmount_lock自旋鎖和當(dāng)前進(jìn)程的namespace->sem讀/寫信號量。
6.   減少相應(yīng)文件系統(tǒng)根目錄的目錄項(xiàng)對象和已安裝文件系統(tǒng)描述符的引用計(jì)數(shù)器值;這些計(jì)數(shù)器值由path_lookup()增加。
7.  返回retval的值。

路徑名查找

當(dāng)進(jìn)程必須識(shí)別一個(gè)文件時(shí),就把它的文件路徑名傳遞給某個(gè)VFS系統(tǒng)調(diào)用,
如open()、mkdir()、rename()或stat()。
本節(jié)我們要說明VFS如何實(shí)現(xiàn)路徑名查找,也就是說如何從文件路徑名導(dǎo)出相應(yīng)的索引節(jié)點(diǎn)。
執(zhí)行這一任務(wù)的標(biāo)準(zhǔn)過程就是分析路徑名并把它拆分成一個(gè)文件名序列。
除了最后一個(gè)文件名以外,所有的文件名都必定是目錄。
如果路徑名的第一個(gè)字符是“/”,那么這個(gè)路徑名是絕對路徑,
因此從current->fs->root(進(jìn)程的根目錄)所標(biāo)識(shí)的目錄開始搜索。
否則,路徑名是相對路徑,因此從current->fs->pwd(進(jìn)程的當(dāng)前目錄)所標(biāo)識(shí)的目錄開始搜索。在對初始目錄的索引節(jié)點(diǎn)進(jìn)行處理的過程中,
代碼要檢查與第一個(gè)名字匹配的目錄項(xiàng),以獲得相應(yīng)的索引節(jié)點(diǎn)。
然后,從磁盤讀出包含那個(gè)索引節(jié)點(diǎn)的目錄文件,并檢查與第二個(gè)名字匹配的目錄項(xiàng),
以獲得相應(yīng)的索引節(jié)點(diǎn)。
對于包含在路徑中的每個(gè)名字,這個(gè)過程反復(fù)執(zhí)行。目錄項(xiàng)高速緩存極大地加速了這一過程,因?yàn)樗炎罱畛J褂玫哪夸涰?xiàng)對象保留在內(nèi)存中。
正如我們以前看到的,每個(gè)這樣的對象使特定目錄中的一個(gè)文件名與它相應(yīng)的索引節(jié)點(diǎn)相聯(lián)系。
因此在很多情況下,路徑名的分析可以避免從磁盤讀取中間目錄。
但是,事情并不像看起來那么簡單,因?yàn)楸仨毧紤]如下的Unix和VFS文件系統(tǒng)的特點(diǎn):
1.對每個(gè)目錄的訪問權(quán)必須進(jìn)行檢查,以驗(yàn)證是否允許進(jìn)程讀取這一目錄的內(nèi)容。	
2.文件名可能是與任意一個(gè)路徑名對應(yīng)的符號鏈接;
在這種情況下,分析必須擴(kuò)展到那個(gè)路徑名的所有分量。
3.符號鏈接可能導(dǎo)致循環(huán)引用;內(nèi)核必須考慮這個(gè)可能性,并能在出現(xiàn)這種情況時(shí)將循環(huán)終止。
4.文件名可能是一個(gè)已安裝文件系統(tǒng)的安裝點(diǎn)。這種情況必須檢測到,這樣,查找操作必須延伸到新的文件系統(tǒng)。
5.路徑名查找應(yīng)該在發(fā)出系統(tǒng)調(diào)用的進(jìn)程的命名空間中完成。
由具有不同命名空間的兩個(gè)進(jìn)程使用的相同路徑名,可能指定了不同的文件。路徑名查找是由path_lookup()函數(shù)執(zhí)行的,它接收三個(gè)參數(shù):
name指向要解析的文件路徑名的指針。
flags標(biāo)志的值,表示將會(huì)怎樣訪問查找的文件。在后面的表12-16中列出了所允許的標(biāo)志。
ndnameidata數(shù)據(jù)結(jié)構(gòu)的地址,這個(gè)結(jié)構(gòu)存放了查找操作的結(jié)果,其字段如表12-15 所示。
當(dāng)path_lookup()返回時(shí),nd指向的nameidata結(jié)構(gòu)用與路徑名查找操作有關(guān)的數(shù)據(jù)來填充。

在這里插入圖片描述

dentry和mnt字段分別指向所解析的最后一個(gè)路徑分量的目錄項(xiàng)對象和已安裝文件系統(tǒng)對象。
這兩個(gè)字段“描述“由給定路徑名表示的文件。
由于path_lookup()函數(shù)返回的nameidata結(jié)構(gòu)中的目錄項(xiàng)對象和已安裝文件系統(tǒng)對象代表了查找操作的結(jié)果,
因此在path_lookup()的調(diào)用者完成使用查找結(jié)果之前,這兩個(gè)對象都不能被釋放。
因此,path_lookup()增加兩個(gè)對象引用計(jì)數(shù)器的值。
如果調(diào)用者想釋放這些對象,則調(diào)用path_release()函數(shù),傳遞給它的參數(shù)為nameidata結(jié)構(gòu)的地址。
flags字段存放查找操作中使用的某些標(biāo)志的值;
它們在表12-16中列出。這些標(biāo)志中的大部分可由調(diào)用者在path_lookup()的flags參數(shù)中進(jìn)行設(shè)置。

在這里插入圖片描述

path_lookup()函數(shù)執(zhí)行下列步驟:
1.  如下初始化nd參數(shù)的某些字段:
a.把last_type字段置為LAST_ROOT(如果路徑名是一個(gè)“/”或“/”序列,那么這是必需的;參見后面的“父路徑名查找“一節(jié))。
b.把flags字段置為參數(shù)flags的值。
c.把depth字段置為0。
2.  為進(jìn)行讀操作而獲取當(dāng)前進(jìn)程的current->fs->lock讀/寫信號量。
3.  如果路徑名的第一個(gè)字符是“/“,那么查找操作必須從當(dāng)前根目錄開始:
獲取相應(yīng)已安裝文件對象(current->fs->rootmnt)和目錄項(xiàng)對象(current->fs->root)的地址,
增加引用計(jì)數(shù)器的值,并把它們的地址分別存放在nd->mnt和nd->dentry中。
4.  否則,如果路徑名的第一個(gè)字符不是“/“,則查找操作必須從當(dāng)前工作目錄開始:
獲得相應(yīng)已安裝文件系統(tǒng)對象(current->fs->pwdmmt)和目錄項(xiàng)對象(current->fs->pwd)的地址,
增加引用計(jì)數(shù)器的值,并把它們的地址分別存放在nd->mnt和nd->dentry中。
5.  釋放當(dāng)前進(jìn)程的current->fs->lock讀/寫信號量。
6.  把當(dāng)前進(jìn)程描述符中的total_link_count字段置為0(參見后面的“符號鏈接的查找”一節(jié))。
7.   調(diào)用link_path_walk()函數(shù)處理正在進(jìn)行的查找操作:
return link_path_walk(name,nd);我們現(xiàn)在準(zhǔn)備描述路徑名查找操作的核心,也就是link_path_walk()函數(shù)。
它接收的參數(shù)為要解析的路徑名指針name和nameidata數(shù)據(jù)結(jié)構(gòu)的地址nd。
為了簡單起見,我們首先描述當(dāng)LOOKUP_PARENT未被設(shè)置且路徑名不包含符號鏈接時(shí),
link_path_walk()做些什么(標(biāo)準(zhǔn)路徑名查找)。
接下來,我們討論LOOKUP_PARENT 被設(shè)置的情況:
這種類型的查找在創(chuàng)建、刪除或更名一個(gè)目錄項(xiàng)時(shí)是需要的,
也就是在父目錄名查找過程中是需要的。
最后,我們闡明該函數(shù)如何解析符號鏈接。

標(biāo)準(zhǔn)路徑名查找

當(dāng)LOOKUP_PARENT標(biāo)志被清零時(shí),link_path_walk()執(zhí)行下列步驟:
1.  用nd->flags初始化lookup_flags局部變量。
2.  跳過路徑名第一個(gè)分量前的任何斜杠(/)。
3.  如果剩余的路徑名為空,則返回0。
在nameidata數(shù)據(jù)結(jié)構(gòu)中,dentry和mnt字段指向原路徑名最后一個(gè)所解析分量對應(yīng)的對象。
4.  如果nd描述符中的depth字段的值為正,則把lookup_flags局部變量置為LOOKUP_FOLLOW標(biāo)志(參見“符號鏈接的查找“一節(jié))。
5.  執(zhí)行一個(gè)循環(huán),把name參數(shù)中傳遞的路徑名分解為分量(中間的“/”被當(dāng)作文件名分隔符對待);
對于每個(gè)找到的分量,該函數(shù):
a.從nd->dentry->d_inode檢索最近一個(gè)所解析分量的索引節(jié)點(diǎn)對象的地址(在第一次循環(huán)中,索引節(jié)點(diǎn)指向開始路徑名查找的目錄)。
b.檢查存放到索引節(jié)點(diǎn)中的最近那個(gè)所解析分量的許可權(quán)是否允許執(zhí)行(在Unix中,只有目錄是可執(zhí)行的,它才可以被遍歷)。
如果索引節(jié)點(diǎn)有自定義的permission方法,則執(zhí)行它;
否則,執(zhí)行exec_permission_lite()函數(shù),該函數(shù)檢查存放在索引節(jié)點(diǎn)i_mode字段的訪問模式和運(yùn)行進(jìn)程的特權(quán)。
在兩種情況中,如果最近所解析分量不允許執(zhí)行,那么link_path_walk()跳出循環(huán)并返回一個(gè)錯(cuò)誤碼。
c.考慮要解析的下一個(gè)分量。從它的名字,
函數(shù)為目錄項(xiàng)高速緩存散列表計(jì)算一個(gè)32位的散列值。
d.如果“/”終止了要解析的分量名,則跳過“/”之后的任何尾部“/”。
e.  如果要解析的分量是原路徑名中的最后一個(gè)分量,則跳到第6步。
f.如果分量名是一個(gè)“.“(單個(gè)圓點(diǎn)),則繼續(xù)下一個(gè)分量(“.“指的是當(dāng)前目錄,因此,這個(gè)點(diǎn)在目錄內(nèi)沒有什么效果)。
g.如果分量名是“..“(兩個(gè)圓點(diǎn)),則嘗試回到父目錄:
(1)如果最近解析的目錄是進(jìn)程的根目錄(nd->dentry等于current->fs->root,
而nd->mnt等于current->fs->rootmnt),那么再向上追蹤是不允許的:
在最近解析的分量上調(diào)用follow_mount()(見下面),繼續(xù)下一個(gè)分量。
(2)如果最近解析的目錄是nd->mnt文件系統(tǒng)的根目錄(nd->dentry等于nd->mnt->mnt_root),
并且這個(gè)文件系統(tǒng)也沒有被安裝在其他文件系統(tǒng)之上(nd->mnt等于nd->mnt->mnt_parent),
那么nd->mnt文件系統(tǒng)通常(注7)就是命名空間的根文件系統(tǒng):
在這種情況下,再向上追蹤是不可能的,
因此在最近解析的分量上調(diào)用follow_mount()(參見下面),繼續(xù)下一個(gè)分量。
(3)如果最近解析的目錄是nd->mnt文件系統(tǒng)的根目錄,而這個(gè)文件系統(tǒng)被安裝在其他文件系統(tǒng)之上,
那么就需要文件系統(tǒng)交換。
因此,把nd->dentry置為nd->mnt->mnt_mountpoint,
且把nd->mnt置為nd->mnt->mnt_parent,然后重新開始第5g步(回想一下,幾個(gè)文件系統(tǒng)可以安裝在同一個(gè)安裝點(diǎn)上)。
(4)如果最近解析的目錄不是已安裝文件系統(tǒng)的根目錄,那么必須回到父目錄:
把nd->dentry置為nd->dentry->d_parent,在父目錄上調(diào)用follow_mount(),繼續(xù)下一個(gè)分量。
follow_mount()函數(shù)檢查nd->dentry是否是某文件系統(tǒng)的安裝點(diǎn)(nd->dentry->d_mounted的值大于0);
如果是,則調(diào)用lookup_mnt()搜索目錄項(xiàng)高速緩存中已安裝文件系統(tǒng)的根目錄,
并把nd->dentry和nd->mnt更新為相應(yīng)已安裝文件系統(tǒng)的對象地址;
然后重復(fù)整個(gè)操作(幾個(gè)文件系統(tǒng)可以安裝在同一個(gè)安裝點(diǎn)上)。
從本質(zhì)上說,由于進(jìn)程可能從某個(gè)文件系統(tǒng)的目錄開始路徑名的查找,
而該目錄被另一個(gè)安裝在其父目錄上的文件系統(tǒng)所隱藏,
那么當(dāng)需要回到父目錄時(shí),則調(diào)用follow_mount()函數(shù)。
h.分量名既不是“.”,也不是“..”,因此函數(shù)必須在目錄項(xiàng)高速緩存中查找它。
如果低級文件系統(tǒng)有一個(gè)自定義的d_hash目錄項(xiàng)方法,則調(diào)用它來修改已在第5c步計(jì)算出的散列值。
i.把nd->flags字段中LOOKUP_CONTINUE標(biāo)志對應(yīng)的位置位,這表示還有下一個(gè)分量要分析。
j.調(diào)用do_lookup(),
得到與給定的父目錄(nd->dentry)和文件名(要解析的路徑名分量)相關(guān)的目錄項(xiàng)對象。
該函數(shù)本質(zhì)上首先調(diào)用__d_lookup()在目錄項(xiàng)高速緩存中搜索分量的目錄項(xiàng)對象。
如果沒有找到這樣的目錄項(xiàng)對象,則調(diào)用real_lookup()。
而real_lookup()執(zhí)行索引節(jié)點(diǎn)的lookup方法從磁盤讀取目錄,
創(chuàng)建一個(gè)新的目錄項(xiàng)對象并把它插入到目錄項(xiàng)高速緩存中,
然后創(chuàng)建一個(gè)新的索引節(jié)點(diǎn)對象并把它插入到索引節(jié)點(diǎn)高速緩存中(注8)。
在這一步結(jié)束時(shí),
next局部變量中的dentry和mnt字段將分別指向這次循環(huán)要解析的分量名的目錄項(xiàng)對象和已安裝文件系統(tǒng)對象。
k.調(diào)用follow_mount()函數(shù)檢查剛解析的分量(next.dentry)是否指向某個(gè)文件系統(tǒng)安裝點(diǎn)的一個(gè)目錄(next.dentry->d_mounted值大于0)。
follow_mount()更新next.dentry和next.mnt的值,
以使它們指向由這個(gè)路徑名分量所表示的目錄上安裝的最上層文件系統(tǒng)的目錄項(xiàng)對象和已安裝文件系統(tǒng)對象(參見第5g步)。
l.檢查剛解析的分量是否指向一個(gè)符號鏈接(next.dentry->d_inode具有一個(gè)自定義的follow_link方法)。
我們將在后面的“符號鏈接的查找“一節(jié)中描述。
m.檢查剛解析的分量是否指向一個(gè)目錄(next.dentry->d_inode具有一個(gè)自定義的lookup方法)。
如果沒有,返回一個(gè)錯(cuò)誤碼-ENOTDIR,因?yàn)檫@個(gè)分量位于原路徑名的中間。
n.把nd->dentry和nd->mnt分別置為next.dentry和next.mnt,然后繼續(xù)路徑名的下一個(gè)分量。
6.  現(xiàn)在,除了最后一個(gè)分量,原路徑名的所有分量都被解析。清除nd->flags中的LOOKUP_CONTINUE標(biāo)志。
7.  如果路徑名尾部有一個(gè)“/”,
則把lookup_flags局部變量中LOOKUP_FOLLOW和LOOKUP_DIRECTORY標(biāo)志對應(yīng)的位置位,
以強(qiáng)制由后面的函數(shù)來解釋最后一個(gè)作為目錄名的分量。
8.  檢查lookup_flags變量中LOOKUP_PARENT標(biāo)志的值。下面假定這個(gè)標(biāo)志被置為0,并把相反的情況推遲到下一節(jié)介紹。
9.  如果最后一個(gè)分量名是“.”(單個(gè)圓點(diǎn)),則終止執(zhí)行并返回值0(無錯(cuò)誤)。
在nd指向的nameidata數(shù)據(jù)結(jié)構(gòu)中,
dentry和mnt字段指向路徑名中倒數(shù)第二個(gè)分量對應(yīng)的對象(任何分量“.”在路徑名中沒有效果)。
10.如果最后一個(gè)分量名是“..”(兩個(gè)圓點(diǎn)),則嘗試回到父目錄:
a.如果最后解析的目錄是進(jìn)程的根目錄(nd->dentry等于current->fs->root,
nd->mnt等于current->fs->rootmnt),則在倒數(shù)第二個(gè)分量上調(diào)用follow_mount(),
終止執(zhí)行并返回值0(無錯(cuò)誤)。
nd->dentry和nd->mnt指向路徑名的倒數(shù)第二個(gè)分量對應(yīng)的對象,也就是進(jìn)程的根目錄。
b.如果最后解析的目錄是nd->mnt文件系統(tǒng)的根目錄(nd->dentry等于nd->mnt->mnt_root),
并且該文件系統(tǒng)沒有被安裝在另一個(gè)文件系統(tǒng)之上(nd->mnt等于nd->mnt->mnt_parent),那么再向上搜索是不可能的,
因此在倒數(shù)第二個(gè)分量上調(diào)用follow_mount(),終止執(zhí)行并返回值0(無錯(cuò)誤)。
c.如果最后解析的目錄是nd->mnt文件系統(tǒng)的根目錄,并且該文件系統(tǒng)被安裝在其他文件系統(tǒng)之上,
那么把nd->dentry和nd->mnt分別置為nd->mnt->mnt_mountpoint和nd->mnt->mnt_parent,然后重新執(zhí)行第10步。
d.如果最后解析的目錄不是已安裝文件系統(tǒng)的根目錄,
則把nd->dentry置為nd->dentry->d_parent,在父目錄上調(diào)用follow_mount(),終止執(zhí)行并返回值0(無錯(cuò)誤)。
nd->dentry和nd->mnt指向前一個(gè)分量(即路徑名倒數(shù)第二個(gè)分量)對應(yīng)的對象。
11.路徑名的最后分量名既不是“.”也不是“..”,因此,必須在高速緩存中查找它。
如果低級文件系統(tǒng)有自定義的d_hash目錄項(xiàng)方法,則該函數(shù)調(diào)用它來修改在第5c步已經(jīng)計(jì)算出的散列值。
12.調(diào)用do_lookup(),得到與父目錄和文件名相關(guān)的目錄項(xiàng)對象(參見第5j步)。
在這一步結(jié)束時(shí),next局部變量存放的是指向最后分量名對應(yīng)的目錄項(xiàng)和已安裝文件系統(tǒng)描述符的指針。
13.  調(diào)用follow_mount()檢查最后一個(gè)分量名是否是某個(gè)文件系統(tǒng)的一個(gè)安裝點(diǎn),
如果是,則把next局部變量更新為最上層已安裝文件系統(tǒng)根目錄對應(yīng)的目錄項(xiàng)對象和已安裝文件系統(tǒng)對象的地址。
14.檢查在lookup_flags中是否設(shè)置了LOOKUP_FOLLOW標(biāo)志,
且索引節(jié)點(diǎn)對象next.dentry->d_inode是否有一個(gè)自定義的follow_link方法。如果是,
分量就是一個(gè)必須進(jìn)行解釋的符號鏈接,這將在后面的“符號鏈接的查找“一節(jié)描述。
15.要解析的分量不是一個(gè)符號鏈接或符號鏈接不該被解釋。
把nd->mnt和nd->dentry字段分別置為next.mnt和next.dentry的值。
最后的目錄項(xiàng)對象就是整個(gè)查找操作的結(jié)果。
16.檢查nd->dentry->d_inode是否為NULL。
這發(fā)生在沒有索引節(jié)點(diǎn)與目錄項(xiàng)對象關(guān)聯(lián)時(shí),通常是因?yàn)槁窂矫赶蛞粋€(gè)不存在的文件。在這種情況下,返回一個(gè)錯(cuò)誤碼-ENOENT。
17.路徑名的最后一個(gè)分量有一個(gè)關(guān)聯(lián)的索引節(jié)點(diǎn)。
如果在lookup_flags中設(shè)置了LOOKUP_DIRECTORY標(biāo)志,
則檢查索引節(jié)點(diǎn)是否有一個(gè)自定義的lookup方法,
也就是說它是一個(gè)目錄。如果沒有,則返回一個(gè)錯(cuò)誤碼-ENOTDIR。
18.返回值0(無錯(cuò)誤)。nd->dentry和nd->mnt指向路徑名的最后分量。

父路徑名查找

在很多情況下,查找操作的真正目的并不是路徑名的最后一個(gè)分量,而是最后一個(gè)分量的前一個(gè)分量。
例如,當(dāng)文件被創(chuàng)建時(shí),最后一個(gè)分量表示還不存在的文件的文件名,
而路徑名中的其余路徑指定新鏈接必須插入的目錄。
因此,查找操作應(yīng)當(dāng)取回最后分量的前一個(gè)分量的目錄項(xiàng)對象。
另舉一個(gè)例子,
把路徑名/foo/bar表示的文件bar拆分出來就包含從目錄foo中移去bar。
因此,內(nèi)核真正的興趣在于訪問文件目錄foo而不是bar。
當(dāng)查找操作必須解析的是包含路徑名最后一個(gè)分量的目錄而不是最后一個(gè)分量本身時(shí),使用LOOKUP_PARENT標(biāo)志。
當(dāng)LOOKUP_PARENT標(biāo)志被設(shè)置時(shí),
link_path_walk()函數(shù)也在nameidata數(shù)據(jù)結(jié)構(gòu)中建立last和last_type字段。
last字段存放路徑名中的最后一個(gè)分量名。last_type 字段標(biāo)識(shí)最后一個(gè)分量的類型;
可以把它置為如表12-17所示的值之一。

在這里插入圖片描述

當(dāng)整個(gè)路徑名的查找操作開始時(shí),
LAST_ROOT標(biāo)志是由path_lookup()設(shè)置的缺省值(參見“路徑名查找“一節(jié)開始部分的描述)。
如果路徑名正好是“/”,則內(nèi)核不改變last_type字段的初始值。
last_type字段的其他值在LOOKUP_PARENT標(biāo)志置位時(shí)由link_path_walk()設(shè)置;
在這種情況下,函數(shù)執(zhí)行前一節(jié)描述的步驟,直到第8步。
不過,從第8步往后,路徑名中最后一個(gè)分量的查找操作是不同的:
1.  把nd->last置為最后一個(gè)分量名。
2.  把nd->last_type初始化為LAST_NORM。
3.  如果最后一個(gè)分量名為“.”(一個(gè)圓點(diǎn)),則把nd->last_type置為LAST_DOT。
4.  如果最后一個(gè)分量名為“..”(兩個(gè)圓點(diǎn)),則把nd->last_type置為LAST_DOTDOT。5.   通過返回值0(無錯(cuò)誤)終止。
你可以看到,最后一個(gè)分量根本就沒有被解釋。
因此,當(dāng)函數(shù)終止時(shí),nameidata數(shù)據(jù)結(jié)構(gòu)的dentry和mnt字段指向最后一個(gè)分量所在目錄對應(yīng)的對象。
http://m.risenshineclean.com/news/58892.html

相關(guān)文章:

  • 做網(wǎng)站怎么復(fù)制視頻鏈接網(wǎng)站自建
  • 網(wǎng)站建設(shè)公司大概多少錢長沙網(wǎng)站seo哪家公司好
  • 丹陽市制作網(wǎng)站南昌網(wǎng)站建設(shè)
  • 政府網(wǎng)站建設(shè)的創(chuàng)新機(jī)制企業(yè)網(wǎng)絡(luò)營銷
  • 無錫做網(wǎng)站品牌公司萬網(wǎng)注冊域名查詢官方網(wǎng)站
  • 做網(wǎng)站設(shè)計(jì)制作的公司口碑營銷案例2022
  • 做c語言的題目的網(wǎng)站移動(dòng)廣告平臺(tái)
  • 域名有了主機(jī)有了如何做網(wǎng)站已備案域名30元
  • 哪里有專做水果的網(wǎng)站怎么建立網(wǎng)站?
  • 如何建立一個(gè)手機(jī)網(wǎng)站nba新聞最新消息滾動(dòng)
  • 深圳有做網(wǎng)站的公司嗎seo 關(guān)鍵詞優(yōu)化
  • 網(wǎng)站備案照片人民網(wǎng) 疫情
  • 長春建站優(yōu)化加徽信xiala5百度網(wǎng)址怎么輸入?
  • 從音樂網(wǎng)站下載歌曲做鈴音要收費(fèi)嗎最新軍事消息
  • 菏澤公司做網(wǎng)站深圳seo優(yōu)化培訓(xùn)
  • 網(wǎng)站算陣地建設(shè)谷歌瀏覽器網(wǎng)頁版入口在哪里
  • 以做網(wǎng)站為畢設(shè)挖掘關(guān)鍵詞的工具
  • 化妝培訓(xùn)網(wǎng)站 源碼做優(yōu)化的網(wǎng)站
  • 在線網(wǎng)站設(shè)計(jì)網(wǎng)絡(luò)推廣公司有哪些
  • 做IT的會(huì)做網(wǎng)站嗎權(quán)重查詢愛站網(wǎng)
  • linux打包網(wǎng)站做備份網(wǎng)絡(luò)營銷產(chǎn)品策略的內(nèi)容
  • 深圳做網(wǎng)站建設(shè)比較好的公司濟(jì)南網(wǎng)站seo優(yōu)化
  • wordpress 自建網(wǎng)站seo百度快速排名
  • 九江做網(wǎng)站廈門網(wǎng)絡(luò)推廣外包多少錢
  • 邢臺(tái)網(wǎng)站建設(shè)包括哪些專業(yè)北京seo公司
  • 中企動(dòng)力做的家具行業(yè)網(wǎng)站企業(yè)網(wǎng)絡(luò)營銷策劃書范文
  • 12免費(fèi)建站網(wǎng)站百度關(guān)鍵詞搜索熱度查詢
  • 石城網(wǎng)站建設(shè)東莞有限公司seo
  • 長春火車站到吉大二院網(wǎng)絡(luò)營銷措施有哪些
  • 網(wǎng)站建設(shè)頁面頁腳怎么設(shè)置b站推廣2024mmm已更新