Windows上Linux子系統的一個關鍵目標是允許用戶像在Linux系統上一樣使用他們的文件,而又對Windows系統中的文件有充分的互操作性。不像一個虛擬機,你必須使用網絡共享或其他解決方案來共享主機和其他操作系統的文件,WSL直接訪問所有你的Windows磁盤,允許很容易的進行交互操作。
Windows文件系統與Linux文件系統有很大的不同,這篇文章將會介紹WSL如何在這兩個世界間架起橋梁。
通過虛擬文件系統(VFS)對抽象文件系統操作,它即提供了接口供用戶程序與文件系統交互(通過系統調用如打開、讀取、修改文件權限,獲取文件信息,等等),又提供了一個文件系統必須實現的接口。這使得多個文件系統可以共存,並提供相同的操作和語義,VFS提供所有這些文件系統的統一的命名空間視圖給用戶。
在這個命名空間裡,文件系統掛載在不同的目錄。例如,在一個典型的Linux系統硬盤可能安裝在根目錄,/,/ dev,/ proc, / sys, / mnt, / cdrom所有掛載的不同文件系統可能在不同的設備上。舉例來說,Linux上使用的文件系統包括ext4,rfs,FAT以及其他。
VFS通過使用大量的數據結實現了文件系統操作的各種系統調用,比如索引節點,目錄項和文件,以及相關文件系統必須實現的回調函數。
索引節點(Inode)
inode是VFS中使用核心數據結構。它代表一個文件系統對象如一個常規文件、目錄、符號鏈接,等等。一個inode包括文件類型,大小,權限,最後修改時間和其他屬性等信息。對於許多常見的Linux磁盤文件系統,如ext4,磁盤上用來表示文件元數據的數據結構直接對應於Linux內核使用的inode結構。
雖然一個索引節點代表一個文件,但它不代表一個文件名。一個文件可能有多個名稱或硬鏈接,但只有一個inode。
文件系統提供了一個查找函數來回調VFS,用來檢索特定文件的inode,它是基於父inode和孩子的名字。文件系統必須實現其他一些inode操作,如修改權限,獲取文件信息,打開文件等等。
VFS使用目錄項緩存來表示文件系統名稱空間。目錄項只存在於內存中,並包含一個指向該文件的inode。舉個例子,如果你有一個像/home/user/foo這樣的路徑,那麼就有一個目錄項對應於home,user和foo,每個都有一個指針指向一個inode。目錄項是快速查找時的緩存,但如果一個條目在緩存中沒有,就用inode查找操作來從文件系統中檢索索引節點,然後就可以創建一個新的目錄項。
當一個inode被打開,就創建了一個文件的的文件對象,它會記錄該文件的很多信息,如跟蹤文件偏移量、文件是只讀,只寫,還是兩者兼而有之。文件系統必須提供的文件操作,如讀read、寫write、同步sync等。
應用程序通過文 件描述符引用到文件對象。它們在一個進程中都是唯一的 值,指向進程所打開的文件。 文件描述符可以指向其他提供文件接口的對象,在Linux中,提供文件接口的對象包括tty、套接字和管道。 多個文件描述符可以指向相同的文件對象,例如可使用dup系統調用獲得同一個文件對象的文件描述符。
除了常規的文件和目錄,Linux支持許多額外的文件類型。 包括設備文件、fifo、套接字和符號鏈接。
其中有些文件會影響路徑解析。 符號鏈接是一種特殊文件,指向一個不同的文件或目錄,並通過VFS對符號鏈接指向的文件進行無縫地處理。 如果你打開路徑/foo/bar/baz,並且bar是/zed的一個符號鏈接,那麼你實際打開的就是/zed/baz。
類似地,可以使用一個目錄作為另一個文件系統的掛載點。 在這種情況下,當一個路徑包含這個目錄,那麼掛載點下面所有inode的操作實際都會在新的文件系統上。
Linux使用許多並不從磁盤讀取文件的文件系統。TmpFs作為臨時內存文件系統使用,其內容將不會持久化。ProcFs和SysFs提供進程的內核信息、設備和驅動程序訪問。這些文件系統沒有相關的磁盤、網絡或其他設備,而是由內核虛擬化出來的。
Windows將所有系統資源都泛化成對象。這些不僅包括文件,而且還包括線程,共享內存段,計時器,這裡僅舉幾例。所有打開文件的請求最終都通過NT內核對象管理器,它通過I/O管理器將請求路由到正確的文件系統驅動程序。在Windows上文件系統驅動程序實現的接口更通用,並只需要更少的需求。例如,沒有類似的公共的inode結構,也沒有目錄項;相反,文件系統驅動程序如ntfs.sys負責解析路徑和打開文件對象。
Windows文件系統 雖然也可以掛載到其它文件系統的目錄中,但 通常是掛載到諸如C:和D:這樣的驅動器號上。這些驅動器號實際上是一種Win32的結構,對象管理器是不能直接對其進行處理的。對象管理器使用的命名空間和Linux文件系統的命名空間有點類似,它的根目錄是\, 文件系統卷標 用路徑為\設備名\硬盤卷標1這樣的設備對象來表示。
當我們使用C:\foo\bar這樣的路徑打開某個文件時,Win32的CreateFile函數將其轉換成一個NT路徑,形式為“\Dos設備名\C:\foo\bar”,這裡的“\Dos設備名\C:”通常是一個符號鏈接,例如,鏈接到“\設備名\硬盤卷標4”。因此,該文件真正的完整路徑應當是“\設備名\硬盤卷標4\foo\bar”。由對象管理器確定路徑中的每個組成部分,直到遇到了設備對象,這和Linux中的VFS很類似。這時候,對象管理器將請求提交給I\O管理器,I\O管理器創建一個包含剩余路徑的I\O請求包(IRP),將該請求包發送至文件系統驅動器以確定設備名。
當打開某個文件時,對象管理器會為該文件創建一個文件對象。對象管理器提供指向文件對象的句柄,而不是文件描述符。實際上,句柄可以指向任意的對象管理器對象,而不只是文件。
當你調用某個系統調用時,比如NtReadFile(通常使用Win32的ReadFile函數),I/O管理器會再次創建一個發送給文件系統驅動器的IRP(輸入\輸入請求包),以使文件對象可以執行此請求。
因為在NT中沒有索引節點或者類似的東西,所以Windows中針對文件的絕大部分操作都需要一個文件對象。
Windows系統僅能支持兩種文件類型:普通文件和目錄。文件和目錄都可以成為重新解析點,重解析點是具有固定的頭部和任意數據塊的特殊文件。頭部包括一個用來標識重解析點類型的標簽,並且必須通過文件系統過濾器進行處理,頭部也可能包括內置的重解析點類型,也就是I/O管理器本身。
重解析點用來實現符號鏈接和掛載點。在這些情況下,標簽表示重解析點是一個符號鏈接或掛載點,和重解析點相關聯的數據中包括了鏈接目標或掛載點的卷標名。重解析點也可以具有其它的用途,比如作為Windows8系統中OneDrive使用的占位符文件。
不像Linux,Windows文件系統默認情況下是不區分大小寫的。實際上,Windows和NTFS是支持大小寫敏感的,只是默認不啟用而已。
Windows上的Linux子系統(WSL)必須將各種Linux文件系統操作轉換成NT內核操作。 WSL必須提供一塊控件來存放Linux系統文件,來支撐所有的功能,包括Linux權限,符號鏈接和其他特殊文件如FIFO;它還必須提供訪問你系統中Windows卷的能力;它還必須提供一些特殊的文件系統比如ProcFs。
為了做到這些,WSL實現了一個VFS組件,它是仿照Linux上的VFS。總架構如下所示。
當一個應用程序調用一個系統調用,它是被系統調用層處理,系統調用層定義了各種內核功能項如open、read、 chmod和stat等等。對於這些文件相關的系統調用,系統調用層幾乎不做什麼處理;它基本上就是把請求傳遞給VFS。
對於使用了路徑的操作(比如open或者stat),VFS使用目錄項緩存來解析路徑。如果一個目錄項不在緩存中,它調用文件系統插件中的某一個插件來生成這個目錄項的inode。這些插件提供inode操作,比如查找,更改權限以及其他,很類似於Linux內核中對inode的操作。當文件被打開時, VFS使用文件系統的inode打開操作來生成一個文件對象,並返回這個文件對象的文件描述符。對於文件描述符的系統調用(比如read, write或者sync)直接調用文件系統定義的相應操作。這個系統有意的使用近似Linux行為,所以WSL可以支持相同的語義。
VFS定義了一些文件系統插件:VolFs和DrvFs用來表示磁盤上的文件,其余的包括內存中的文件系統TmpFs以及偽文件系統,比如ProcFs,SysFs和CgroupFs。
VolFs和DrvFs是用來將Linux文件系統滿足Windows文件系統。他們展示了WSL是如何與你磁盤上的文件交互的,並且提供兩種不同用途:VolFs被設計成提供所有Linux文件系統特性,DrvFs被設計為提供與Windows交互。
讓我們更詳細的來看看這些文件系統。
WSL使用的主要的文件系統是VolFs。可以用它來存儲Linux系統文件,以及Linux用戶的home目錄。因此,VolFs支持Linux虛擬文件系統(VFS)提供的絕大部分特性,其中包括Linux權限、符號鏈接、先入先出(FIFOs)、套接字(sockets)和設備文件。
VolFs用來掛載虛擬文件系統(VFS)的根目錄,使用%LocalAppData%\lxss\rootfs作為備用存儲器。另外,還有其它的幾個VolFs掛載點,尤其是/root和/home,它們分別使用%LocalAppData%\lxss\root和%LocalAppData%\lxss\home掛載。分別進行掛載的原因是當卸載WSL時,默認情況下,home目錄是不刪除的,所以存儲過的所有個人文件都會保留。
需要注意的是所有的這些掛載點都要使用Windows用戶文件夾中的目錄進行存儲。每個Windows用戶都擁有自己的WSL環境,因此也可以擁有Linux中的root權限,可以在不影響其它Windows用戶的情況下安裝程序。
因為 Windows 沒有相關的 索引節點概念,VolFs 必須在索引節點上對Windows 文件對象保持一個句柄。VFS 請求一個新的索引節點使用了lookup 回調,VolFs 使用來自父索引節點和子節點名稱來處理相關的打開和獲取句柄。這些句柄的打開不需要任何的讀/寫文件,且只能用於元數據的請求。
當文件被打開,VolFs 創建一個 Linux 文件對象指向索引節點。它可以重復打開節點索引的文件句柄,並可以通過請求在文件對象上讀/寫訪問和存儲新的句柄。然後使用該句柄來滿足文件操作,比如讀和寫。
如上所述,Linux和Windows的文件系統有好幾個方面的不同。VolFs必須對Windows不能直接支持的幾個Linux特性提供支持。
Windows本身可以處理大小寫敏感。正如前面提到的,Windows和NTFS實際上支持大小寫敏感操作,所以,無論全局注冊鍵如何控制,VolFs只需請求對象管理器將路徑看做是大小寫敏感的。
Linux的文件名還支持將全部字符視為合法字符。NT中卻有更多的限制,有些字符根本不允許作為文件名,還有些字符可能具有特殊含義(例如“:”表示改變數據流)。為了支持所有Linux文件名,VolFs將文件名中的非法字符進行轉義。
Linux中的斷開鏈接(unlinking)和重命名(renaming)有一些不同的語義。具體的說,某個文件即使有打開著的文件描述符指向它,也可以斷開鏈接。同樣,某個文件即使打開著,也可以對其進行重命名的操作。在Windows中,如果要請求刪除一個文件,那它最終的狀態必須是關閉著的,此時文件名在文件系統中可見。為了支持Linux中的斷開鏈接語義,請求刪除前,VolFs會將斷開鏈接的文件重命名至一個隱藏的臨時文件夾中。
Linux的索引節點具有一些Windows中不存在的屬性,包括擁有者和組、文件模式等。這些屬性存儲在與磁盤文件相關聯的NTFS擴展屬性中。擴展屬性中存儲的信息如下:
模式(Mode):包括文件類型(普通文件、符號鏈接、FIFOs等)和該文件的權限位。
擁有者(Owner):擁有該文件的Linux用戶ID和組ID。
設備ID(Device ID):設備文件的主設備號和次設備號。注意,WSL目前還不允許用戶在VolFs中創建設備文件。
文件時間(File times):Linux中的文件訪問時間、修改時間和改變時間所采用的格式和粒度與Windows中不同,所以,這些時間也存儲在EAs中。
另外,如果某個文件具有一些文件能力特性,那它會被存儲在變異數據流中。注意,WSL目前還不允許用戶更改文件的文件能力特性。
其余的索引節點屬性,例如索引節點號和文件大小, 都源自於NTFS中保存的信息。
上面提到了,VolFs文件以普通文件的方式存儲在Windows系統的目錄中,因此,不支持和Windows系統之間的互操作能力。如果從Windows系統中向這些目錄添加一個新文件,因為缺少必要的EAs,所以VolFs不知道如何處理該文件,只能將其忽略掉。在WSL中,許多編輯器在保存一個已存在的文件時也會刪除EAs,使得該文件再次成為不可用。
另外,因為VFS緩存目錄項,當運行著WSL時,在Windows系統中對那些目錄所做的任何改變都不會有准確的反應。
為了便於和Windows系統之間的互操作,WSL使用了DrvFs文件系統。WSL將具有可支持文件系統的固定驅動器自動掛載到/mnt目錄中,例如/mnt/c、/mnt/d等。目前,僅能支持NTFS和ReFs卷標。
DrvFs的操作方式和VolFs類似。當創建索引節點和文件對象時,打開指向Windows文件的句柄。但是,和VolFs相比,DrvFs遵循Windows規則(有幾個例外,下面會說明)。DrvFs使用Windows權限,只允許使用合法的NTFS文件名,不支持特殊的文件類型,比如FIFOs和sockets。
Linux通常采用簡單的權限模型,允許某個文件的所有者、所屬組或其它的任何人擁有對該文件的讀、寫或執行權限。Windows卻采用訪問控制列表(ACLs),為每個單個文件和目錄指定復雜的訪問規則(Linux也的確能使用ACLs,只不過WSL目前還不支持)。
在DrvFs中,當打開一個文件時,使用基於用戶令牌的Windows權限,令牌是由用戶執行bash.exe產生的。所以,在WSL中要訪問C:\Windows目錄下的文件,在bash環境中使用“sudo”還是不行的,雖然你擁有了root權限,但並沒有改變你的Windows用戶令牌,還必須啟動bash.exe以便進一步獲得合適的權限。
為了給用戶關於他針對某個文件所擁有的權限的提示,DrvFs檢查用戶有效的文件權限,並將其轉換成“讀/寫/可執行”位,例如執行“ls -l”命令可以看到。但也並不都是一對一的映射關系。例如,Windows中對於在目錄中創建文件和子目錄需要不同的權限。如果用戶擁有任意一種權限,DrvFs都視為對目錄擁有寫訪問權限,但實際上有些操作可能還是會因拒絕訪問而失敗。
因為對文件的有效訪問權限會因bash.exe是否啟動是否提升了權限而不同,所以,DrvFs中顯示的文件權限也會隨著bash實例的權限提升與否而改變。
當計算對某個文件的有效訪問權限時,在DrvFs中要考慮到只讀屬性。在Windows中對某個文件設置了只讀屬性表現在WSL中就是沒有寫的權限。使用Chmod可以設置文件的只讀屬性(通過移除所有的寫權限,比如,使用命令“chmod a-w 某個文件”)或者清除只讀屬性(通過添加任意寫權限,比如,使用命令"chmod u+w 某個文件")。這種方法和Linux中的CIFS文件系統很類似,CIFS用來訪問Windows SMB共享。
因為支持Windows的NTFS,DrvFs支持區分大小寫文件。這意味著可以創建兩個只文件名大小寫不同的DrvFs。請注意,許多的Windows應用程序可能無法處理這種情況,並且可能無法打開一個或兩個文件。
大小寫敏感被禁用在卷根上,但其它地方可以用。因此,為了使用大小寫敏感的文件。不要嘗試創建/mnt/c,而是創建一個目錄,在這個目錄裡可以創建文件。
雖然NT支持符號鏈接,我們也不能依賴這種支持,因為WSL創建的符號鏈接有可能指向在Windows中沒有任何意義的路徑,比如/proc。另外,在NT中創建符號鏈接是需要管理員權限的。因此,必須找到其它的解決方案。
和VolFs不同,在DrvFs中,我們不能依賴EAs來標示某個文件是符號鏈接。取而代之的是,WSL采用一種全新的重解析點來表示符號鏈接。因此,這些鏈接只在WSL中有效,而不能被其它的Windows組件所識別,比如文件浏覽器(File Explorer)或者cmd.exe。需要注意的是,既然ReFs對重解析點缺少支持,那麼它也不支持WSL中的符號鏈接。然而,NTFS已經對WSL中的符號鏈接有了全面支持。
和VolFs不同,DrvFs不會存儲任何的額外信息。相反,所有的索引節點屬性都是通過查詢NT中使用的文件屬性、有效權限以及其它的信息派生而來的。DrvFs還禁用了目錄項緩存,以確保即使Windows進程對目錄的內容進行了修改的情況下,它也總能提供正確的、最新的信息。因此,當使用DrvFs時,對Windows進程如何處理文件沒有任何限制。
DrvFs還對文件使用了Windows刪除語義,所以,如果某個文件存在任何打開著的文件描述符(或者Windows進程在處理著),那我們就不能取消對其的鏈接。
正如在Linux中,這些特殊的文件系統不會顯示磁盤上的文件,而是代表了內核中有關進程、線程和設備的信息。這些文件在運行時動態產生。有些情況下,這些文件的信息完全保存在lxcore.sys驅動中。在其它情況下,例如某個進程對CPU的使用,WSL需要查詢NT內核以獲取相關信息。然而,和Windows文件系統之間是沒有任何交互的。
結論
通過使用VolFs,WSL為其內部的Linux文件系統模擬出完整的Linux特性,從而提供了對Windows文件的訪問,並且通過使用DrvFs,提供了對Windows設備及文件的完全訪問。在寫作本文時,DrvFs已經實現了Linux文件系統的一些功能,比如大小寫敏感和符號鏈接,同時還支持和Windows之間的互操作。
將來,我們會繼續增強對Linux文件系統特性的支持,對於VolFs和DrvFs都是如此。在VolFs掛載的同時要進行必要的互操作時會遇到各種限制,我們的目標就是減少這種情況發生的次數。從GitHub和User Voice社區上,我們獲得了大量反饋,這些都會促使我們改進,幫助我們以最重要的場景為目標。