2021-02-06 分類: 網(wǎng)站建設
什么是文件系統(tǒng)?根據(jù)早期的 Linux 貢獻者和作家 Robert Love 所說,“文件系統(tǒng)是一個遵循特定結(jié)構(gòu)的數(shù)據(jù)的分層存儲?!?不過,這種描述也同樣適用于 VFAT(虛擬文件分配表Virtual File Allocation Table)、Git 和Cassandra(一種 NoSQL 數(shù)據(jù)庫)。那么如何區(qū)別文件系統(tǒng)呢?
如果我們能夠 open()、read() 和 write(),它就是一個文件,如這個主控臺會話所示。
VFS 是著名的類 Unix 系統(tǒng)中 “一切皆文件” 概念的基礎。讓我們看一下它有多奇怪,上面的小小演示體現(xiàn)了字符設備 /dev/console 實際的工作。該圖顯示了一個在虛擬電傳打字控制臺(tty)上的交互式 Bash 會話。將一個字符串發(fā)送到虛擬控制臺設備會使其顯示在虛擬屏幕上。而 VFS 甚至還有其它更奇怪的屬性。例如,它可以在其中尋址。
我們熟悉的文件系統(tǒng)如 ext4、NFS 和 /proc 都在名為 file_operations 的 C 語言數(shù)據(jù)結(jié)構(gòu)中提供了三大函數(shù)的定義。此外,個別的文件系統(tǒng)會以熟悉的面向?qū)ο蟮姆绞綌U展和覆蓋了 VFS 功能。正如 Robert Love 指出的那樣,VFS 的抽象使 Linux 用戶可以輕松地將文件復制到(或復制自)外部操作系統(tǒng)或抽象實體(如管道),而無需擔心其內(nèi)部數(shù)據(jù)格式。在用戶空間這一側(cè),通過系統(tǒng)調(diào)用,進程可以使用文件系統(tǒng)方法之一 read() 從文件復制到內(nèi)核的數(shù)據(jù)結(jié)構(gòu)中,然后使用另一種文件系統(tǒng)的方法 write() 輸出數(shù)據(jù)。
屬于 VFS 基本類型的函數(shù)定義本身可以在內(nèi)核源代碼的 fs/*.c 文件 中找到,而 fs/ 的子目錄中包含了特定的文件系統(tǒng)。內(nèi)核還包含了類似文件系統(tǒng)的實體,例如 cgroup、/dev 和 tmpfs,在引導過程的早期需要它們,因此定義在內(nèi)核的 init/ 子目錄中。請注意,cgroup、/dev 和 tmpfs 不會調(diào)用 file_operations 的三大函數(shù),而是直接讀取和寫入內(nèi)存。
下圖大致說明了用戶空間如何訪問通常掛載在 Linux 系統(tǒng)上的各種類型文件系統(tǒng)。像管道、dmesg 和 POSIX 時鐘這樣的結(jié)構(gòu)在此圖中未顯示,它們也實現(xiàn)了 struct file_operations,而且其訪問也要通過 VFS 層。
/proc/meminfo 是一個空文件,但仍包含有價值的信息。
/proc 文件的行為說明了 VFS 可以與磁盤上的文件系統(tǒng)不同。一方面,/proc/meminfo包含了可由命令 free 展現(xiàn)出來的信息。另一方面,它還是空的!怎么會這樣?這種情況讓人聯(lián)想起康奈爾大學物理學家 N. David Mermin 在 1985 年寫的一篇名為《沒有人看見月亮的情況嗎?現(xiàn)實和量子理論》。事實是當進程從 /proc 請求數(shù)據(jù)時內(nèi)核再收集有關內(nèi)存的統(tǒng)計信息,而且當沒有人查看它時,/proc 中的文件實際上沒有任何內(nèi)容。正如 Mermin 所說,“這是一個基本的量子學說,一般來說,測量不會揭示被測屬性的預先存在的價值?!?關于月球的問題的答案留作練習。)
當沒有進程訪問它們時,/proc 中的文件為空。(來源)
procfs 的空文件是有道理的,因為那里可用的信息是動態(tài)的。sysfs 的情況則不同。讓我們比較一下 /proc 與 /sys 中不為空的文件數(shù)量。
用 eBPF 觀察插入 USB 記憶棒時 /sys 中會發(fā)生什么,簡單的和復雜的例子。
在上面的第一個簡單示例中,只要 sysfs_create_files() 命令運行,trace.py bcc 工具腳本就會打印出一條消息。我們看到 sysfs_create_files() 由一個 kworker 線程啟動,以響應 USB 棒的插入事件,但是它創(chuàng)建了什么文件?第二個例子說明了 eBPF 的強大能力。這里,trace.py 正在打印內(nèi)核回溯(-K 選項)以及 sysfs_create_files() 創(chuàng)建的文件的名稱。單引號內(nèi)的代碼段是一些 C 源代碼,包括一個易于識別的格式字符串,所提供的 Python 腳本引入 LLVM 即時編譯器(JIT) 來在內(nèi)核虛擬機內(nèi)編譯和執(zhí)行它。必須在第二個命令中重現(xiàn)完整的 sysfs_create_files() 函數(shù)簽名,以便格式字符串可以引用其中一個參數(shù)。在此 C 片段中出錯會導致可識別的 C 編譯器錯誤。例如,如果省略 -I 參數(shù),則結(jié)果為“無法編譯 BPF 文本”。熟悉 C 或 Python 的開發(fā)人員會發(fā)現(xiàn) bcc 工具易于擴展和修改。
插入 USB 記憶棒后,內(nèi)核回溯顯示 PID 7711 是一個 kworker 線程,它在 sysfs 中創(chuàng)建了一個名為 events 的文件。使用 sysfs_remove_files() 進行相應的調(diào)用表明,刪除 USB 記憶棒會導致刪除該 events 文件,這與引用計數(shù)的想法保持一致。在 USB 棒插入期間(未顯示)在 eBPF 中觀察 sysfs_create_link() 表明創(chuàng)建了不少于 48 個符號鏈接。
無論如何,events 文件的目的是什么?使用 cscope 查找函數(shù) __device_add_disk()顯示它調(diào)用 disk_add_events(),并且可以將 “mediachange” 或 “ejectrequest” 寫入到該文件。這里,內(nèi)核的塊層通知用戶空間該 “磁盤” 的出現(xiàn)和消失。考慮一下這種檢查 USB 棒的插入的工作原理的方法與試圖僅從源頭中找出該過程的速度有多快。
只讀根文件系統(tǒng)使得嵌入式設備成為可能
確實,沒有人通過拔出電源插頭來關閉服務器或桌面系統(tǒng)。為什么?因為物理存儲設備上掛載的文件系統(tǒng)可能有掛起的(未完成的)寫入,并且記錄其狀態(tài)的數(shù)據(jù)結(jié)構(gòu)可能與寫入存儲器的內(nèi)容不同步。當發(fā)生這種情況時,系統(tǒng)所有者將不得不在下次啟動時等待 fsck 文件系統(tǒng)恢復工具 運行完成,在最壞的情況下,實際上會丟失數(shù)據(jù)。
然而,狂熱愛好者會聽說許多物聯(lián)網(wǎng)和嵌入式設備,如路由器、恒溫器和汽車現(xiàn)在都運行著 Linux。許多這些設備幾乎完全沒有用戶界面,并且沒有辦法干凈地讓它們“解除啟動”。想一想啟動電池耗盡的汽車,其中運行 Linux 的主機設備 的電源會不斷加電斷電。當引擎最終開始運行時,系統(tǒng)如何在沒有長時間 fsck 的情況下啟動呢?答案是嵌入式設備依賴于只讀根文件系統(tǒng)(簡稱 ro-rootfs)。
ro-rootfs 是嵌入式系統(tǒng)不經(jīng)常需要 fsck 的原因。
ro-rootfs 提供了許多優(yōu)點,雖然這些優(yōu)點不如耐用性那么顯然。一個是,如果 Linux 進程不可以寫入,那么惡意軟件也無法寫入 /usr 或 /lib。另一個是,基本上不可變的文件系統(tǒng)對于遠程設備的現(xiàn)場支持至關重要,因為支持人員擁有理論上與現(xiàn)場相同的本地系統(tǒng)。也許最重要(但也是最微妙)的優(yōu)勢是 ro-rootfs 迫使開發(fā)人員在項目的設計階段就決定好哪些系統(tǒng)對象是不可變的。處理 ro-rootfs 可能經(jīng)常是不方便甚至是痛苦的,編程語言中的常量變量經(jīng)常就是這樣,但帶來的好處很容易償還這種額外的開銷。
對于嵌入式開發(fā)人員,創(chuàng)建只讀根文件系統(tǒng)確實需要做一些額外的工作,而這正是 VFS 的用武之地。Linux 需要 /var 中的文件可寫,此外,嵌入式系統(tǒng)運行的許多流行應用程序會嘗試在 $HOME 中創(chuàng)建配置的點文件。放在家目錄中的配置文件的一種解決方案通常是預生成它們并將它們構(gòu)建到 rootfs 中。對于 /var,一種方法是將其掛載在單獨的可寫分區(qū)上,而 / 本身以只讀方式掛載。使用綁定或疊加掛載是另一種流行的替代方案。
綁定和疊加掛載以及在容器中的使用運行 man mount 是了解綁定掛載bind mount和疊加掛載overlay mount的最好辦法,這種方法使得嵌入式開發(fā)人員和系統(tǒng)管理員能夠在一個路徑位置創(chuàng)建文件系統(tǒng),然后以另外一個路徑將其提供給應用程序。對于嵌入式系統(tǒng),這代表著可以將文件存儲在 /var 中的不可寫閃存設備上,但是在啟動時將 tmpfs 中的路徑疊加掛載或綁定掛載到 /var 路徑上,這樣應用程序就可以在那里隨意寫它們的內(nèi)容了。下次加電時,/var 中的變化將會消失。疊加掛載為 tmpfs 和底層文件系統(tǒng)提供了聯(lián)合,允許對 ro-rootfs 中的現(xiàn)有文件進行直接修改,而綁定掛載可以使新的空 tmpfs 目錄在 ro-rootfs 路徑中顯示為可寫。雖然疊加文件系統(tǒng)是一種適當?shù)奈募到y(tǒng)類型,而綁定掛載由 VFS 命名空間工具實現(xiàn)的。
根據(jù)疊加掛載和綁定掛載的描述,沒有人會對 Linux 容器 中大量使用它們感到驚訝。讓我們通過運行 bcc 的 mountsnoop 工具監(jiān)視當使用 systemd-nspawn 啟動容器時會發(fā)生什么:
在 mountsnoop.py 運行的同時,system-nspawn 調(diào)用啟動容器。
讓我們看看發(fā)生了什么:
在容器 “啟動” 期間運行 mountsnoop 可以看到容器運行時很大程度上依賴于綁定掛載。(僅顯示冗長輸出的開頭)
這里,systemd-nspawn 將主機的 procfs 和 sysfs 中的選定文件按其 rootfs 中的路徑提供給容器。除了設置綁定掛載時的 MS_BIND 標志之外,mount 系統(tǒng)調(diào)用的一些其它標志用于確定主機命名空間和容器中的更改之間的關系。例如,綁定掛載可以將 /proc 和 /sys 中的更改傳播到容器,也可以隱藏它們,具體取決于調(diào)用。
總結(jié)
理解 Linux 內(nèi)部結(jié)構(gòu)看似是一項不可能完成的任務,因為除了 Linux 用戶空間應用程序和 glibc 這樣的 C 庫中的系統(tǒng)調(diào)用接口,內(nèi)核本身也包含大量代碼。取得進展的一種方法是閱讀一個內(nèi)核子系統(tǒng)的源代碼,重點是理解面向用戶空間的系統(tǒng)調(diào)用和頭文件以及主要的內(nèi)核內(nèi)部接口,這里以 file_operations 表為例。file_operations 使得“一切都是文件”得以可以實際工作,因此掌握它們收獲特別大。頂級 fs/ 目錄中的內(nèi)核 C 源文件構(gòu)成了虛擬文件系統(tǒng)的實現(xiàn),虛擬文件系統(tǒng)是支持流行的文件系統(tǒng)和存儲設備的廣泛且相對簡單的互操作性的墊片層。通過 Linux 命名空間進行綁定掛載和覆蓋掛載是 VFS 魔術,它使容器和只讀根文件系統(tǒng)成為可能。結(jié)合對源代碼的研究,eBPF 內(nèi)核工具及其 bcc 接口使得探測內(nèi)核比以往任何時候都更簡單。
網(wǎng)頁名稱:Linux 中的虛擬文件系統(tǒng)詳解
URL標題:http://www.rwnh.cn/news/99434.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設計、關鍵詞優(yōu)化、定制開發(fā)、微信公眾號、網(wǎng)站營銷、ChatGPT
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容