千鋒扣丁學堂Linux培訓之虛擬文件系統(tǒng)詳解
2019-05-22 14:13:17
5354瀏覽
今天千鋒扣丁學堂Linux培訓老師給大家分享一篇關于Linux中虛擬文件系統(tǒng)的詳細介紹,首先虛擬文件系統(tǒng)是一種神奇的抽象,它使得“一切皆文件”哲學在Linux中成為了可能。
什么是文件系統(tǒng)?根據(jù)早期的Linux貢獻者和作家RobertLove所說,“文件系統(tǒng)是一個遵循特定結構的數(shù)據(jù)的分層存儲?!辈贿^,這種描述也同樣適用于VFAT(虛擬文件分配表VirtualFileAllocationTable)、Git和Cassandra(一種NoSQL數(shù)據(jù)庫)。那么如何區(qū)別文件系統(tǒng)呢?
文件系統(tǒng)基礎概念
Linux內核要求文件系統(tǒng)必須是實體,它還必須在持久對象上實現(xiàn)open()、read()和write()方法,并且這些實體需要有與之關聯(lián)的名字。從面向對象編程的角度來看,內核將通用文件系統(tǒng)視為一個抽象接口,這三大函數(shù)是“虛擬”的,沒有默認定義。因此,內核的默認文件系統(tǒng)實現(xiàn)被稱為虛擬文件系統(tǒng)(VFS)。
VFS是著名的類Unix系統(tǒng)中“一切皆文件”概念的基礎。讓我們看一下它有多奇怪,上面的小小演示體現(xiàn)了字符設備/dev/console實際的工作。該圖顯示了一個在虛擬電傳打字控制臺(tty)上的交互式Bash會話。將一個字符串發(fā)送到虛擬控制臺設備會使其顯示在虛擬屏幕上。而VFS甚至還有其它更奇怪的屬性。例如,它可以在其中尋址。
我們熟悉的文件系統(tǒng)如ext4、NFS和/proc都在名為file_operations的C語言數(shù)據(jù)結構中提供了三大函數(shù)的定義。此外,個別的文件系統(tǒng)會以熟悉的面向對象的方式擴展和覆蓋了VFS功能。正如RobertLove指出的那樣,VFS的抽象使Linux用戶可以輕松地將文件復制到(或復制自)外部操作系統(tǒng)或抽象實體(如管道),而無需擔心其內部數(shù)據(jù)格式。在用戶空間這一側,通過系統(tǒng)調用,進程可以使用文件系統(tǒng)方法之一read()從文件復制到內核的數(shù)據(jù)結構中,然后使用另一種文件系統(tǒng)的方法write()輸出數(shù)據(jù)。
屬于VFS基本類型的函數(shù)定義本身可以在內核源代碼的fs/*.c文件中找到,而fs/的子目錄中包含了特定的文件系統(tǒng)。內核還包含了類似文件系統(tǒng)的實體,例如cgroup、/dev和tmpfs,在引導過程的早期需要它們,因此定義在內核的init/子目錄中。請注意,cgroup、/dev和tmpfs不會調用file_operations的三大函數(shù),而是直接讀取和寫入內存。
下圖大致說明了用戶空間如何訪問通常掛載在Linux系統(tǒng)上的各種類型文件系統(tǒng)。像管道、dmesg和POSIX時鐘這樣的結構在此圖中未顯示,它們也實現(xiàn)了structfile_operations,而且其訪問也要通過VFS層。
VFS是個“墊片層”,位于系統(tǒng)調用和特定file_operations的實現(xiàn)(如ext4和procfs)之間。然后,file_operations函數(shù)可以與特定于設備的驅動程序或內存訪問器進行通信。tmpfs、devtmpfs和cgroup不使用file_operations而是直接訪問內存。
VFS的存在促進了代碼重用,因為與文件系統(tǒng)相關的基本方法不需要由每種文件系統(tǒng)類型重新實現(xiàn)。代碼重用是一種被廣泛接受的軟件工程最佳實踐!唉,但是如果重用的代碼引入了嚴重的錯誤,那么繼承常用方法的所有實現(xiàn)都會受到影響。
/tmp:一個小提示
找出系統(tǒng)中存在的VFS的簡單方法是鍵入mount|grep-vsd|grep-v:/,在大多數(shù)計算機上,它將列出所有未駐留在磁盤上,同時也不是NFS的已掛載文件系統(tǒng)。其中一個列出的VFS掛載肯定是/tmp,對吧?
為什么把/tmp留在存儲設備上是不可取的?因為/tmp中的文件是臨時的(!),并且存儲設備比內存慢,所以創(chuàng)建了tmpfs這種文件系統(tǒng)。此外,比起內存,物理設備頻繁寫入更容易磨損。最后,/tmp中的文件可能包含敏感信息,因此在每次重新啟動時讓它們消失是一項功能。
不幸的是,默認情況下,某些Linux發(fā)行版的安裝腳本仍會在存儲設備上創(chuàng)建/tmp。如果你的系統(tǒng)出現(xiàn)這種情況,請不要絕望。按照一直優(yōu)秀的ArchWiki上的簡單說明來解決問題就行,記住分配給tmpfs的內存就不能用于其他目的了。換句話說,包含了大文件的龐大的tmpfs可能會讓系統(tǒng)耗盡內存并崩潰。
另一個提示:編輯/etc/fstab文件時,請務必以換行符結束,否則系統(tǒng)將無法啟動。(猜猜我怎么知道。)
/proc和/sys
除了/tmp之外,大多數(shù)Linux用戶最熟悉的VFS是/proc和/sys。(/dev依賴于共享內存,而沒有file_operations結構)。為什么有兩種呢?讓我們來看看更多細節(jié)。
procfs為用戶空間提供了內核及其控制的進程的瞬時狀態(tài)的快照。在/proc中,內核發(fā)布有關其提供的設施的信息,如中斷、虛擬內存和調度程序。此外,/proc/sys是存放可以通過sysctl命令配置的設置的地方,可供用戶空間訪問。單個進程的狀態(tài)和統(tǒng)計信息在/proc/<PID>目錄中報告。
/proc文件的行為說明了VFS可以與磁盤上的文件系統(tǒng)不同。一方面,/proc/meminfo包含了可由命令free展現(xiàn)出來的信息。另一方面,它還是空的!怎么會這樣?這種情況讓人聯(lián)想起康奈爾大學物理學家N.DavidMermin在1985年寫的一篇名為《沒有人看見月亮的情況嗎?現(xiàn)實和量子理論》。事實是當進程從/proc請求數(shù)據(jù)時內核再收集有關內存的統(tǒng)計信息,而且當沒有人查看它時,/proc中的文件實際上沒有任何內容。正如Mermin所說,“這是一個基本的量子學說,一般來說,測量不會揭示被測屬性的預先存在的價值?!保P于月球的問題的答案留作練習。)
procfs的空文件是有道理的,因為那里可用的信息是動態(tài)的。sysfs的情況則不同。讓我們比較一下/proc與/sys中不為空的文件數(shù)量。
procfs只有一個不為空的文件,即導出的內核配置,這是一個例外,因為每次啟動只需要生成一次。另一方面,/sys有許多更大一些的文件,其中大多數(shù)由一頁內存組成。通常,sysfs文件只包含一個數(shù)字或字符串,與通過讀取/proc/meminfo等文件生成的信息表格形成鮮明對比。
sysfs的目的是將內核稱為“kobject”的可讀寫屬性公開給用戶空間。kobject的唯一目的是引用計數(shù):當刪除對kobject的最后一個引用時,系統(tǒng)將回收與之關聯(lián)的資源。然而,/sys構成了內核著名的“到用戶空間的穩(wěn)定ABI”,它的大部分內容在任何情況下都沒有人能“破壞”。但這并不意味著sysfs中的文件是靜態(tài),這與易失性對象的引用計數(shù)相反。
內核的穩(wěn)定ABI限制了/sys中可能出現(xiàn)的內容,而不是任何給定時刻實際存在的內容。列出sysfs中文件的權限可以了解如何設置或讀取設備、模塊、文件系統(tǒng)等的可配置、可調參數(shù)。邏輯上強調procfs也是內核穩(wěn)定ABI的一部分的結論,盡管內核的文檔沒有明確說明。
sysfs中的文件確切地描述了實體的每個屬性,并且可以是可讀的、可寫的,或兩者兼而有之。文件中的“0”表示SSD不可移動的存儲設備。
sysfs中的文件確切地描述了實體的每個屬性,并且可以是可讀的、可寫的,或兩者兼而有之。文件中的“0”表示SSD不可移動的存儲設備。
用eBPF和bcc工具一窺VFS內部
了解內核如何管理sysfs文件的最簡單方法是觀察它的運行情況,在ARM64或x86_64上觀看的最簡單方法是使用eBPF。eBPF(擴展的伯克利數(shù)據(jù)包過濾器extendedBerkeleyPacketFilter)由在內核中運行的虛擬機組成,特權用戶可以從命令行進行查詢。內核源代碼告訴讀者內核可以做什么;而在一個啟動的系統(tǒng)上運行eBPF工具會顯示內核實際上做了什么。
令人高興的是,通過bcc工具入門使用eBPF非常容易,這些工具在主要Linux發(fā)行版的軟件包中都有,并且已經由BrendanGregg給出了充分的文檔說明。bcc工具是帶有小段嵌入式C語言片段的Python腳本,這意味著任何對這兩種語言熟悉的人都可以輕松修改它們。據(jù)當前統(tǒng)計,bcc/tools中有80個Python腳本,使得系統(tǒng)管理員或開發(fā)人員很有可能能夠找到與她/他的需求相關的已有腳本。
要了解VFS在正在運行中的系統(tǒng)上的工作情況,請嘗試使用簡單的vfscount或vfsstat腳本,這可以看到每秒都會發(fā)生數(shù)十次對vfs_open()及其相關的調用。
作為一個不太重要的例子,讓我們看一下在運行的系統(tǒng)上插入USB記憶棒時sysfs中會發(fā)生什么。
在上面的第一個簡單示例中,只要sysfs_create_files()命令運行,trace.pybcc工具腳本就會打印出一條消息。我們看到sysfs_create_files()由一個kworker線程啟動,以響應USB棒的插入事件,但是它創(chuàng)建了什么文件?第二個例子說明了eBPF的強大能力。這里,trace.py正在打印內核回溯(-K選項)以及sysfs_create_files()創(chuàng)建的文件的名稱。單引號內的代碼段是一些C源代碼,包括一個易于識別的格式字符串,所提供的Python腳本引入LLVM即時編譯器(JIT)來在內核虛擬機內編譯和執(zhí)行它。必須在第二個命令中重現(xiàn)完整的sysfs_create_files()函數(shù)簽名,以便格式字符串可以引用其中一個參數(shù)。在此C片段中出錯會導致可識別的C編譯器錯誤。例如,如果省略-I參數(shù),則結果為“無法編譯BPF文本”。熟悉C或Python的開發(fā)人員會發(fā)現(xiàn)bcc工具易于擴展和修改。
插入USB記憶棒后,內核回溯顯示PID7711是一個kworker線程,它在sysfs中創(chuàng)建了一個名為events的文件。使用sysfs_remove_files()進行相應的調用表明,刪除USB記憶棒會導致刪除該events文件,這與引用計數(shù)的想法保持一致。在USB棒插入期間(未顯示)在eBPF中觀察sysfs_create_link()表明創(chuàng)建了不少于48個符號鏈接。
無論如何,events文件的目的是什么?使用cscope查找函數(shù)__device_add_disk()顯示它調用disk_add_events(),并且可以將“mediachange”或“ejectrequest”寫入到該文件。這里,內核的塊層通知用戶空間該“磁盤”的出現(xiàn)和消失??紤]一下這種檢查USB棒的插入的工作原理的方法與試圖僅從源頭中找出該過程的速度有多快。
只讀根文件系統(tǒng)使得嵌入式設備成為可能
確實,沒有人通過拔出電源插頭來關閉服務器或桌面系統(tǒng)。為什么?因為物理存儲設備上掛載的文件系統(tǒng)可能有掛起的(未完成的)寫入,并且記錄其狀態(tài)的數(shù)據(jù)結構可能與寫入存儲器的內容不同步。當發(fā)生這種情況時,系統(tǒng)所有者將不得不在下次啟動時等待fsck文件系統(tǒng)恢復工具運行完成,在最壞的情況下,實際上會丟失數(shù)據(jù)。
然而,狂熱愛好者會聽說許多物聯(lián)網和嵌入式設備,如路由器、恒溫器和汽車現(xiàn)在都運行著Linux。許多這些設備幾乎完全沒有用戶界面,并且沒有辦法干凈地讓它們“解除啟動”。想一想啟動電池耗盡的汽車,其中運行Linux的主機設備的電源會不斷加電斷電。當引擎最終開始運行時,系統(tǒng)如何在沒有長時間fsck的情況下啟動呢?答案是嵌入式設備依賴于只讀根文件系統(tǒng)(簡稱ro-rootfs)。
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可能經常是不方便甚至是痛苦的,編程語言中的常量變量經常就是這樣,但帶來的好處很容易償還這種額外的開銷。
對于嵌入式開發(fā)人員,創(chuàng)建只讀根文件系統(tǒng)確實需要做一些額外的工作,而這正是VFS的用武之地。Linux需要/var中的文件可寫,此外,嵌入式系統(tǒng)運行的許多流行應用程序會嘗試在$HOME中創(chuàng)建配置的點文件。放在家目錄中的配置文件的一種解決方案通常是預生成它們并將它們構建到rootfs中。對于/var,一種方法是將其掛載在單獨的可寫分區(qū)上,而/本身以只讀方式掛載。使用綁定或疊加掛載是另一種流行的替代方案。
綁定和疊加掛載以及在容器中的使用
運行manmount是了解綁定掛載bindmount和疊加掛載overlaymount的最好辦法,這種方法使得嵌入式開發(fā)人員和系統(tǒng)管理員能夠在一個路徑位置創(chuàng)建文件系統(tǒng),然后以另外一個路徑將其提供給應用程序。對于嵌入式系統(tǒng),這代表著可以將文件存儲在/var中的不可寫閃存設備上,但是在啟動時將tmpfs中的路徑疊加掛載或綁定掛載到/var路徑上,這樣應用程序就可以在那里隨意寫它們的內容了。下次加電時,/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ā)生什么:
讓我們看看發(fā)生了什么:
這里,systemd-nspawn將主機的procfs和sysfs中的選定文件按其rootfs中的路徑提供給容器。除了設置綁定掛載時的MS_BIND標志之外,mount系統(tǒng)調用的一些其它標志用于確定主機命名空間和容器中的更改之間的關系。例如,綁定掛載可以將/proc和/sys中的更改傳播到容器,也可以隱藏它們,具體取決于調用。
理解Linux內部結構看似是一項不可能完成的任務,因為除了Linux用戶空間應用程序和glibc這樣的C庫中的系統(tǒng)調用接口,內核本身也包含大量代碼。取得進展的一種方法是閱讀一個內核子系統(tǒng)的源代碼,重點是理解面向用戶空間的系統(tǒng)調用和頭文件以及主要的內核內部接口,這里以file_operations表為例。file_operations使得“一切都是文件”得以可以實際工作,因此掌握它們收獲特別大。頂級fs/目錄中的內核C源文件構成了虛擬文件系統(tǒng)的實現(xiàn),虛擬文件??系統(tǒng)是支持流行的文件系統(tǒng)和存儲設備的廣泛且相對簡單的互操作性的墊片層。通過Linux命名空間進行綁定掛載和覆蓋掛載是VFS魔術,它使容器和只讀根文件系統(tǒng)成為可能。結合對源代碼的研究,eBPF內核工具及其bcc接口使得探測內核比以往任何時候都更簡單。
以上就是關于千鋒扣丁學堂Linux培訓之虛擬文件系統(tǒng)的全部內容,
最后想要了解更多關于Linux方面內容的小伙伴,請關注扣丁學堂Linux培訓官網、微信等平臺,扣丁學堂IT職業(yè)在線學習教育平臺為您提供權威的Linux開發(fā)視頻,Linux培訓后的前景無限,行業(yè)薪資和未來的發(fā)展會越來越好的,扣丁學堂老師精心推出的Linux視頻教程定能讓你快速掌握Linux從入門到精通開發(fā)實戰(zhàn)技能??鄱W堂Linux技術交流群:422345477。
【關注微信公眾號獲取更多學習資料】 【掃碼進入Python全棧開發(fā)免費公開課】
查看更多關于“Linux培訓資訊”的相關文章>>
標簽:
Linux培訓
Linux視頻教程
紅帽Linux視頻
Linux學習視頻
Linux入門視頻