前沿拓展:
u**scan.sys
試試把你的電腦重做系統(tǒng)吧,做系統(tǒng)的光盤(pán)一定不要和你現(xiàn)在使用的系統(tǒng)是一張盤(pán),換張新盤(pán)做下系統(tǒng)!這樣應(yīng)該可以解決問(wèn)
在 Linux 文件系統(tǒng)就采用了位圖的方式來(lái)管理空閑空間,不僅用于數(shù)據(jù)空閑塊的管理,還用于 inode 空閑塊的管理,因?yàn)?inode 也是存儲(chǔ)在磁盤(pán)的,自然也要有對(duì)其管理。
文件系統(tǒng)的結(jié)構(gòu)
前面提到 Linux 是用位圖的方式管理空閑空間,用戶在創(chuàng)建一個(gè)新文件時(shí),Linux 內(nèi)核會(huì)通過(guò) inode 的位圖找到空閑可用的 inode,并進(jìn)行分配。要存儲(chǔ)數(shù)據(jù)時(shí),會(huì)通過(guò)塊的位圖找到空閑的塊,并分配,但仔細(xì)計(jì)算一下還是有問(wèn)題的。
數(shù)據(jù)塊的位圖是放在磁盤(pán)塊里的,假設(shè)是放在一個(gè)塊里,一個(gè)塊 4K,每位表示一個(gè)數(shù)據(jù)塊,共可以表示 4 * 1024 * 8 = 2^15 個(gè)空閑塊,由于 1 個(gè)數(shù)據(jù)塊是 4K 大小,那么最大可以表示的空間為 2^15 * 4 * 1024 = 2^27 個(gè) byte,也就是 128M。
也就是說(shuō)按照上面的結(jié)構(gòu),如果采用「一個(gè)塊的位圖 + 一系列的塊」,外加「一個(gè)塊的 inode 的位圖 + 一系列的 inode 的結(jié)構(gòu)」能表示的最大空間也就 128M,這太少了,現(xiàn)在很多文件都比這個(gè)大。
在 Linux 文件系統(tǒng),把這個(gè)結(jié)構(gòu)稱為一個(gè)塊組,那么有 N 多的塊組,就能夠表示 N 大的文件。
下圖給出了 Linux Ext2 整個(gè)文件系統(tǒng)的結(jié)構(gòu)和塊組的內(nèi)容,文件系統(tǒng)都由大量塊組組成,在硬盤(pán)上相繼排布:
最前面的第一個(gè)塊是引導(dǎo)塊,在系統(tǒng)啟動(dòng)時(shí)用于啟用引導(dǎo),接著后面就是一個(gè)一個(gè)連續(xù)的塊組了,塊組的內(nèi)容如下:
超級(jí)塊,包含的是文件系統(tǒng)的重要信息,比如 inode 總個(gè)數(shù)、塊總個(gè)數(shù)、每個(gè)塊組的 inode 個(gè)數(shù)、每個(gè)塊組的塊個(gè)數(shù)等等。
塊組描述符,包含文件系統(tǒng)中各個(gè)塊組的狀態(tài),比如塊組中空閑塊和 inode 的數(shù)目等,每個(gè)塊組都包含了文件系統(tǒng)中「所有塊組的組描述符信息」。
數(shù)據(jù)位圖和 inode 位圖, 用于表示對(duì)應(yīng)的數(shù)據(jù)塊或 inode 是空閑的,還是被使用中。
inode 列表,包含了塊組中所有的 inode,inode 用于保存文件系統(tǒng)中與各個(gè)文件和目錄相關(guān)的所有元數(shù)據(jù)。
數(shù)據(jù)塊,包含文件的有用數(shù)據(jù)。
你可以會(huì)發(fā)現(xiàn)每個(gè)塊組里有很多重復(fù)的信息,比如超級(jí)塊和塊組描述符表,這兩個(gè)都是全局信息,而且非常的重要,這么做是有兩個(gè)原因:
如果系統(tǒng)崩潰破壞了超級(jí)塊或塊組描述符,有關(guān)文件系統(tǒng)結(jié)構(gòu)和內(nèi)容的所有信息都會(huì)丟失。如果有冗余的副本,該信息是可能恢復(fù)的。
通過(guò)使文件和管理數(shù)據(jù)盡可能接近,減少了磁頭尋道和旋轉(zhuǎn),這可以提高文件系統(tǒng)的性能。
不過(guò),Ext2 的后續(xù)版本采用了稀疏技術(shù)。該做法是,超級(jí)塊和塊組描述符表不再存儲(chǔ)到文件系統(tǒng)的每個(gè)塊組中,而是只寫(xiě)入到塊組 0、塊組 1 和其他 ID 可以表示為 3、 5、7 的冪的塊組中。
目錄的存儲(chǔ)
在前面,我們知道了一個(gè)普通文件是如何存儲(chǔ)的,但還有一個(gè)特殊的文件,經(jīng)常用到的目錄,它是如何保存的呢?
基于 Linux 一切皆文件的設(shè)計(jì)思想,目錄其實(shí)也是個(gè)文件,你甚至可以通過(guò) vim 打開(kāi)它,它也有 inode,inode 里面也是指向一些塊。
和普通文件不同的是,普通文件的塊里面保存的是文件數(shù)據(jù),而目錄文件的塊里面保存的是目錄里面一項(xiàng)一項(xiàng)的文件信息。
在目錄文件的塊中,最簡(jiǎn)單的保存格式就是列表,就是一項(xiàng)一項(xiàng)地將目錄下的文件信息(如文件名、文件 inode、文件類型等)列在表里。
列表中每一項(xiàng)就代表該目錄下的文件的文件名和對(duì)應(yīng)的 inode,通過(guò)這個(gè) inode,就可以找到真正的文件。
目錄格式哈希表
通常,第一項(xiàng)是「.」,表示當(dāng)前目錄,第二項(xiàng)是「..」,表示上一級(jí)目錄,接下來(lái)就是一項(xiàng)一項(xiàng)的文件名和 inode。
如果一個(gè)目錄有超級(jí)多的文件,我們要想在這個(gè)目錄下找文件,按照列表一項(xiàng)一項(xiàng)的找,效率就不高了。
于是,保存目錄的格式改成哈希表,對(duì)文件名進(jìn)行哈希計(jì)算,把哈希值保存起來(lái),如果我們要查找一個(gè)目錄下面的文件名,可以通過(guò)名稱取哈希。如果哈希能夠匹配上,就說(shuō)明這個(gè)文件的信息在相應(yīng)的塊里面。
Linux 系統(tǒng)的 ext 文件系統(tǒng)就是采用了哈希表,來(lái)保存目錄的內(nèi)容,這種方法的優(yōu)點(diǎn)是查找非常迅速,插入和刪除也較簡(jiǎn)單,不過(guò)需要一些預(yù)備措施來(lái)避免哈希沖突。
目錄查詢是通過(guò)在磁盤(pán)上反復(fù)搜索完成,需要不斷地進(jìn)行 I/O **作,開(kāi)銷較大。所以,為了減少 I/O **作,把當(dāng)前使用的文件目錄緩存在內(nèi)存,以后要使用該文件時(shí)只要在內(nèi)存中**作,從而降低了磁盤(pán)**作次數(shù),提高了文件系統(tǒng)的訪問(wèn)速度。
軟鏈接和硬鏈接
有時(shí)候我們希望給某個(gè)文件取個(gè)別名,那么在 Linux 中可以通過(guò)硬鏈接(Hard Link)和軟鏈接(Symbolic Link)的方式來(lái)實(shí)現(xiàn),它們都是比較特殊的文件,但是實(shí)現(xiàn)方式也是不相同的。
硬鏈接是多個(gè)目錄項(xiàng)中的「索引節(jié)點(diǎn)」指向一個(gè)文件,也就是指向同一個(gè) inode,但是 inode 是不可能跨越文件系統(tǒng)的,每個(gè)文件系統(tǒng)都有各自的 inode 數(shù)據(jù)結(jié)構(gòu)和列表,所以硬鏈接是不可用于跨文件系統(tǒng)的。由于多個(gè)目錄項(xiàng)都是指向一個(gè) inode,那么只有刪除文件的所有硬鏈接以及源文件時(shí),系統(tǒng)才會(huì)徹底刪除該文件。
硬鏈接
軟鏈接相當(dāng)于重新創(chuàng)建一個(gè)文件,這個(gè)文件有**的 inode,但是這個(gè)文件的內(nèi)容是另外一個(gè)文件的路徑,所以訪問(wèn)軟鏈接的時(shí)候,實(shí)際上相當(dāng)于訪問(wèn)到了另外一個(gè)文件,所以軟鏈接是可以跨文件系統(tǒng)的,甚至目標(biāo)文件被刪除了,鏈接文件還是在的,只不過(guò)指向的文件找不到了而已。
軟鏈接
文件 I/O
文件的讀寫(xiě)方式各有千秋,對(duì)于文件的 I/O 分類也非常多,常見(jiàn)的有
緩沖與非緩沖 I/O
直接與非直接 I/O
阻塞與非阻塞 I/O VS 同步與異步 I/O
接下來(lái),分別對(duì)這些分類討論討論。
緩沖與非緩沖 I/O
文件**作的標(biāo)準(zhǔn)庫(kù)是可以實(shí)現(xiàn)數(shù)據(jù)的緩存,那么根據(jù)「是否利用標(biāo)準(zhǔn)庫(kù)緩沖」,可以把文件 I/O 分為緩沖 I/O 和非緩沖 I/O:
緩沖 I/O,利用的是標(biāo)準(zhǔn)庫(kù)的緩存實(shí)現(xiàn)文件的加速訪問(wèn),而標(biāo)準(zhǔn)庫(kù)再通過(guò)系統(tǒng)調(diào)用訪問(wèn)文件。
非緩沖 I/O,直接通過(guò)系統(tǒng)調(diào)用訪問(wèn)文件,不經(jīng)過(guò)標(biāo)準(zhǔn)庫(kù)緩存。
這里所說(shuō)的「緩沖」特指標(biāo)準(zhǔn)庫(kù)內(nèi)部實(shí)現(xiàn)的緩沖。
比方說(shuō),很多程序遇到換行時(shí)才真正輸出,而換行前的內(nèi)容,其實(shí)就是被標(biāo)準(zhǔn)庫(kù)暫時(shí)緩存了起來(lái),這樣做的目的是,減少系統(tǒng)調(diào)用的次數(shù),畢竟系統(tǒng)調(diào)用是有 CPU 上下文切換的開(kāi)銷的。
直接與非直接 I/O
我們都知道磁盤(pán) I/O 是非常慢的,所以 Linux 內(nèi)核為了減少磁盤(pán) I/O 次數(shù),在系統(tǒng)調(diào)用后,會(huì)把用戶數(shù)據(jù)拷貝到內(nèi)核中緩存起來(lái),這個(gè)內(nèi)核緩存空間也就是「頁(yè)緩存」,只有當(dāng)緩存滿足某些條件的時(shí)候,才發(fā)起磁盤(pán) I/O 的請(qǐng)求。
那么,根據(jù)是「否利用**作系統(tǒng)的緩存」,可以把文件 I/O 分為直接 I/O 與非直接 I/O:
直接 I/O,不會(huì)發(fā)生內(nèi)核緩存和用戶程序之間數(shù)據(jù)**,而是直接經(jīng)過(guò)文件系統(tǒng)訪問(wèn)磁盤(pán)。
非直接 I/O,讀**作時(shí),數(shù)據(jù)從內(nèi)核緩存中拷貝給用戶程序,寫(xiě)**作時(shí),數(shù)據(jù)從用戶程序拷貝給內(nèi)核緩存,再由內(nèi)核決定什么時(shí)候?qū)懭霐?shù)據(jù)到磁盤(pán)。
如果你在使用文件**作類的系統(tǒng)調(diào)用函數(shù)時(shí),指定了 O_DIRECT 標(biāo)志,則表示使用直接 I/O。如果沒(méi)有設(shè)置過(guò),默認(rèn)使用的是非直接 I/O。
如果用了非直接 I/O 進(jìn)行寫(xiě)數(shù)據(jù)**作,內(nèi)核什么情況下才會(huì)把緩存數(shù)據(jù)寫(xiě)入到磁盤(pán)?
以下幾種場(chǎng)景會(huì)觸發(fā)內(nèi)核緩存的數(shù)據(jù)寫(xiě)入磁盤(pán):
在調(diào)用 write 的最后,當(dāng)發(fā)現(xiàn)內(nèi)核緩存的數(shù)據(jù)太多的時(shí)候,內(nèi)核會(huì)把數(shù)據(jù)寫(xiě)到磁盤(pán)上;
用戶主動(dòng)調(diào)用 sync,內(nèi)核緩存會(huì)刷到磁盤(pán)上;
當(dāng)內(nèi)存十分緊張,無(wú)法再分配頁(yè)面時(shí),也會(huì)把內(nèi)核緩存的數(shù)據(jù)刷到磁盤(pán)上;
內(nèi)核緩存的數(shù)據(jù)的緩存時(shí)間超過(guò)某個(gè)時(shí)間時(shí),也會(huì)把數(shù)據(jù)刷到磁盤(pán)上;
阻塞與非阻塞 I/O VS 同步與異步 I/O
為什么把阻塞 / 非阻塞與同步與異步放一起說(shuō)的呢?因?yàn)樗鼈兇_實(shí)非常相似,也非常容易混淆,不過(guò)它們之間的關(guān)系還是有點(diǎn)微妙的。
先來(lái)看看阻塞 I/O,當(dāng)用戶程序執(zhí)行 read ,線程會(huì)被阻塞,一直等到內(nèi)核數(shù)據(jù)準(zhǔn)備好,并把數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到應(yīng)用程序的緩沖區(qū)中,當(dāng)拷貝過(guò)程完成,read 才會(huì)返回。
注意,阻塞等待的是「內(nèi)核數(shù)據(jù)準(zhǔn)備好」和「數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài)」這兩個(gè)過(guò)程。過(guò)程如下圖:
阻塞 I/O
知道了阻塞 I/O ,來(lái)看看非阻塞 I/O,非阻塞的 read 請(qǐng)求在數(shù)據(jù)未準(zhǔn)備好的情況下立即返回,可以繼續(xù)往下執(zhí)行,此時(shí)應(yīng)用程序不斷輪詢內(nèi)核,直到數(shù)據(jù)準(zhǔn)備好,內(nèi)核將數(shù)據(jù)拷貝到應(yīng)用程序緩沖區(qū),read 調(diào)用才可以獲取到結(jié)果。過(guò)程如下圖:
非阻塞 I/O
注意,這里最后一次 read 調(diào)用,獲取數(shù)據(jù)的過(guò)程,是一個(gè)同步的過(guò)程,是需要等待的過(guò)程。這里的同步指的是內(nèi)核態(tài)的數(shù)據(jù)拷貝到用戶程序的緩存區(qū)這個(gè)過(guò)程。
舉個(gè)例子,訪問(wèn)管道或 socket 時(shí),如果設(shè)置了 O_NONBLOCK 標(biāo)志,那么就表示使用的是非阻塞 I/O 的方式訪問(wèn),而不做任何設(shè)置的話,默認(rèn)是阻塞 I/O。
應(yīng)用程序每次輪詢內(nèi)核的 I/O 是否準(zhǔn)備好,感覺(jué)有點(diǎn)傻乎乎,因?yàn)檩喸兊倪^(guò)程中,應(yīng)用程序啥也做不了,只是在循環(huán)。
為了解決這種傻乎乎輪詢方式,于是 I/O 多路復(fù)用技術(shù)就出來(lái)了,如 select、poll,它是通過(guò) I/O **分發(fā),當(dāng)內(nèi)核數(shù)據(jù)準(zhǔn)備好時(shí),再以**通知應(yīng)用程序進(jìn)行**作。
這個(gè)做法大大改善了應(yīng)用進(jìn)程對(duì) CPU 的利用率,在沒(méi)有被通知的情況下,應(yīng)用進(jìn)程可以使用 CPU 做其他的事情。
下圖是使用 select I/O 多路復(fù)用過(guò)程。注意,read 獲取數(shù)據(jù)的過(guò)程(數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài)的過(guò)程),也是一個(gè)同步的過(guò)程,需要等待:
I/O 多路復(fù)用
實(shí)際上,無(wú)論是阻塞 I/O、非阻塞 I/O,還是基于非阻塞 I/O 的多路復(fù)用都是同步調(diào)用。因?yàn)樗鼈冊(cè)?read 調(diào)用時(shí),內(nèi)核將數(shù)據(jù)從內(nèi)核空間拷貝到應(yīng)用程序空間,過(guò)程都是需要等待的,也就是說(shuō)這個(gè)過(guò)程是同步的,如果內(nèi)核實(shí)現(xiàn)的拷貝效率不高,read 調(diào)用就會(huì)在這個(gè)同步過(guò)程中等待比較長(zhǎng)的時(shí)間。
而真正的異步 I/O 是「內(nèi)核數(shù)據(jù)準(zhǔn)備好」和「數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài)」這兩個(gè)過(guò)程都不用等待。
當(dāng)我們發(fā)起 aio_read 之后,就立即返回,內(nèi)核自動(dòng)將數(shù)據(jù)從內(nèi)核空間拷貝到應(yīng)用程序空間,這個(gè)拷貝過(guò)程同樣是異步的,內(nèi)核自動(dòng)完成的,和前面的同步**作不一樣,應(yīng)用程序并不需要主動(dòng)發(fā)起拷貝動(dòng)作。過(guò)程如下圖:
異步 I/O
下面這張圖,小編綜合來(lái)說(shuō)了以上幾種 I/O 模型:
在前面我們知道了,I/O 是分為兩個(gè)過(guò)程的:
數(shù)據(jù)準(zhǔn)備的過(guò)程;
數(shù)據(jù)從內(nèi)核空間拷貝到用戶進(jìn)程緩沖區(qū)的過(guò)程;
阻塞 I/O 會(huì)阻塞在「過(guò)程 1 」和「過(guò)程 2」,而非阻塞 I/O 和基于非阻塞 I/O 的多路復(fù)用只會(huì)阻塞在「過(guò)程 2」,所以這三個(gè)都可以認(rèn)為是同步 I/O。
異步 I/O 則不同,「過(guò)程 1 」和「過(guò)程 2 」都不會(huì)阻塞。
用故事去理解這幾種 I/O 模型
舉個(gè)你去飯?zhí)贸燥埖睦?,你好比用戶程序,飯?zhí)煤帽?*作系統(tǒng)。
阻塞 I/O 好比,你去飯?zhí)贸燥?,但是飯?zhí)玫牟诉€沒(méi)做好,第二你就一直在那里等啊等,等了好長(zhǎng)一段時(shí)間終于等到飯?zhí)冒⒁贪巡硕肆顺鰜?lái)(數(shù)據(jù)準(zhǔn)備的過(guò)程),但是你還得繼續(xù)等阿姨把菜(內(nèi)核空間)打到你的飯盒里(用戶空間),經(jīng)歷完這兩個(gè)過(guò)程,你才可以離開(kāi)。
非阻塞 I/O 好比,你去了飯?zhí)?,?wèn)阿姨菜做好了沒(méi)有,阿姨告訴你沒(méi),你就離開(kāi)了,過(guò)幾十分鐘,你又來(lái)飯?zhí)脝?wèn)阿姨,阿姨說(shuō)做好了,于是阿姨幫你把菜打到你的飯盒里,這個(gè)過(guò)程你是得等待的。
基于非阻塞的 I/O 多路復(fù)用好比,你去飯?zhí)贸燥垼l(fā)現(xiàn)有一排窗口,飯?zhí)冒⒁谈嬖V你這些窗口都還沒(méi)做好菜,等做好了再通知你,于是等啊等(select 調(diào)用中),過(guò)了一會(huì)阿姨通知你菜做好了,但是不知道哪個(gè)窗口的菜做好了,你自己看吧。于是你只能一個(gè)一個(gè)窗口去確認(rèn),后面發(fā)現(xiàn) 5 號(hào)窗口菜做好了,于是你讓 5 號(hào)窗口的阿姨幫你打菜到飯盒里,這個(gè)打菜的過(guò)程你是要等待的,雖然時(shí)間不長(zhǎng)。打完菜后,你自然就可以離開(kāi)了。
異步 I/O 好比,你讓飯?zhí)冒⒁虒⒉俗龊貌巡舜虻斤埡欣锖?,把飯盒送到你面前,整個(gè)過(guò)程你都不需要任何等待。
拓展知識(shí):
原創(chuàng)文章,作者:九賢生活小編,如若轉(zhuǎn)載,請(qǐng)注明出處:http://xiesong.cn/70004.html