已掛載檔案系統上的快取

概述

CacheFiles 是一個快取後端,旨在將本地型別(如 Ext3)的已掛載檔案系統上的目錄用作快取。

CacheFiles 使用使用者空間守護程序來執行一些快取管理 - 例如回收過時的節點和剔除。這被稱為 cachefilesd,位於 /sbin 中。

快取的檔案系統和資料完整性僅與提供後備服務的檔案系統的檔案系統和資料完整性一樣好。請注意,CacheFiles 不嘗試記錄任何內容,因為各種檔案系統的日誌介面本質上非常具體。

CacheFiles 建立一個 misc 字元裝置 - “/dev/cachefiles” - 用於與守護程序通訊。一次只能有一個東西開啟它,並且當它開啟時,快取至少部分存在。守護程序開啟它並向下傳送命令以控制快取。

CacheFiles 目前僅限於單個快取。

CacheFiles 嘗試在檔案系統上保持至少一定百分比的可用空間,透過剔除其包含的物件來縮小快取以在必要時騰出空間 - 請參閱“快取剔除”部分。這意味著它可以放置在與即時資料集相同的介質上,並且會擴充套件以利用剩餘空間,並在資料集需要更多空間時自動收縮。

要求

使用 CacheFiles 及其守護程序需要在系統和快取檔案系統中提供以下功能

  • dnotify。

  • 擴充套件屬性(xattrs)。

  • openat() 及其朋友。

  • 檔案系統中檔案上的 bmap() 支援(FIBMAP ioctl)。

  • 使用 bmap() 來檢測檔案末尾的部分頁面。

強烈建議在使用 Ext3 檔案系統作為快取時啟用“dir_index”選項。

配置

快取由 /etc/cachefilesd.conf 中的指令碼配置。這些命令設定了準備使用的快取。以下指令碼命令可用

brun <N>%, bcull <N>%, bstop <N>%, frun <N>%, fcull <N>%, fstop <N>%

配置剔除限制。可選。請參閱有關剔除的部分。預設值分別為 7%(run)、5%(cull)和 1%(stop)。

以“b”開頭的命令是檔案空間(塊)限制,以“f”開頭的命令是檔案計數限制。

dir <path>

指定包含快取根目錄的目錄。強制性。

tag <name>

指定要由 FS-Cache 用於區分多個快取的標籤。可選。預設為“CacheFiles”。

debug <mask>

指定一個數字位掩碼來控制核心模組中的除錯。可選。預設為零(全部關閉)。可以將以下值 OR’d 到掩碼中以收集各種資訊

1

開啟函式入口跟蹤(_enter() 宏)

2

開啟函式退出跟蹤(_leave() 宏)

4

開啟內部除錯點跟蹤(_debug())

也可以透過 sysfs 設定此掩碼,例如

echo 5 > /sys/module/cachefiles/parameters/debug

啟動快取

透過執行守護程序來啟動快取。守護程序開啟快取裝置,配置快取並告訴它開始快取。此時,快取繫結到 fscache,快取變為活動狀態。

守護程序按如下方式執行

/sbin/cachefilesd [-d]* [-s] [-n] [-f <configfile>]

標誌是

-d

增加除錯級別。可以多次指定此選項,並且它會自身累積。

-s

將訊息傳送到 stderr 而不是 syslog。

-n

不要守護程序化並進入後臺。

-f <configfile>

使用備用配置檔案,而不是預設檔案。

要避免的事項

不要在快取中掛載其他東西,因為這會導致問題。核心模組包含其自己的非常精簡的路徑遍歷工具,該工具忽略掛載點,但守護程序無法避免它們。

當快取處於活動狀態時,不要在快取中建立、重新命名或取消連結檔案和目錄,因為這可能會導致狀態變得不確定。

重新命名快取中的檔案可能會使物件看起來像其他物件(檔名是查詢鍵的一部分)。

不要更改或刪除快取附加到快取檔案的擴充套件屬性,因為這會導致快取狀態管理混亂。

不要在快取中建立檔案或目錄,以免快取混淆或提供不正確的資料。

不要更改快取中的 chmod 檔案。該模組建立具有最少許可權的內容,以防止隨機使用者直接訪問它們。

快取剔除

快取可能偶爾需要剔除以騰出空間。這涉及從快取中丟棄比其他任何內容使用較少的物件。剔除基於資料物件的訪問時間。如果空目錄未使用,則將其剔除。

快取剔除基於底層檔案系統中可用的塊的百分比和檔案的百分比來完成。有六個“限制”

brun, frun

如果快取中的可用空間量和可用檔案數都高於這兩個限制,則關閉剔除。

bcull, fcull

如果快取中的可用空間量或可用檔案數低於這些限制中的任何一個,則開始剔除。

bstop, fstop

如果快取中的可用空間量或可用檔案數低於這些限制中的任何一個,則在剔除將事物再次提高到這些限制之上之前,不允許進一步分配磁碟空間或檔案。

必須這樣配置這些

0 <= bstop < bcull < brun < 100
0 <= fstop < fcull < frun < 100

請注意,這些是可用空間和可用檔案的百分比,並且_不_顯示為 100 減去“df”程式顯示的百分比。

使用者空間守護程序掃描快取以構建可剔除物件的表。然後以最近最少使用順序剔除這些。一旦在表中騰出空間,就會啟動對快取的新掃描。如果它們的 atimes 已更改,或者核心模組說它仍在 使用它們,則會跳過物件。

快取結構

CacheFiles 模組將在給定的目錄中建立兩個目錄

  • cache/

  • graveyard/

活動的快取物件都駐留在第一個目錄中。CacheFiles 核心模組將任何無法簡單地取消連結的已停用或剔除的物件移動到墓地,守護程序將從墓地實際刪除它們。

守護程序使用 dnotify 監視墓地目錄,並將刪除其中出現的任何內容。

該模組將索引物件表示為檔名為“I...”或“J...”的目錄。請注意,“cache/”目錄本身是一個特殊的索引。

如果資料物件沒有子物件,則將其表示為檔案,如果資料物件有子物件,則將其表示為目錄。它們的檔名都以“D...”或“E...”開頭。如果表示為目錄,則資料物件將在目錄中有一個名為“data”的檔案,該檔案實際儲存資料。

特殊物件類似於資料物件,只是它們的檔名以“S...”或“T...”開頭。

如果一個物件有子物件,那麼它將表示為一個目錄。在代表性目錄中立即是一個以子物件鍵的雜湊值命名的目錄集合,並預先新增一個“@”。如果可能,此目錄中將放置子物件的表示

 /INDEX    /INDEX     /INDEX                            /DATA FILES
/=========/==========/=================================/================
cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400
cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400/@75/Es0g000w...DB1ry
cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400/@75/Es0g000w...N22ry
cache/@4a/I03nfs/@30/Ji000000000000000--fHg8hi8400/@75/Es0g000w...FP1ry

如果金鑰太長以至於超過了新增修飾後的 NAME_MAX,則會將其切割成碎片,其中前幾個碎片將用於建立目錄巢狀,最後一個碎片將成為最後一個目錄中的物件。中間目錄的名稱將預先新增“+”

J1223/@23/+xy...z/+kl...m/Epqr

請注意,金鑰是原始資料,不僅可能超過 NAME_MAX 的大小,還可能包含諸如“/”和 NUL 字元之類的東西,因此它們可能不適合直接轉換為檔名。

為了處理這個問題,CacheFiles 將直接使用適當的可列印檔名,並對不直接適合的檔名進行“base-64”編碼。物件檔名的兩個版本表示編碼

物件型別

可列印

已編碼

索引

“I...”

“J...”

資料

“D...”

“E...”

特殊

“S...”

“T...”

中間目錄始終為“@”或“+”(視情況而定)。

快取中的每個物件都有一個擴充套件屬性標籤,其中儲存了物件型別 ID(區分特殊物件所必需的)和來自 netfs 的輔助資料。後者用於檢測快取中的過時物件並更新或停用它們。

請注意,CacheFiles 將從快取中擦除它無法識別的任何檔案或任何型別不正確的檔案(例如 FIFO 檔案或裝置檔案)。

安全模型和 SELinux

CacheFiles 的實現是為了正確處理 Linux 核心的 LSM 安全功能和 SELinux 設施。

CacheFiles 面臨的問題之一是,它通常代表程序執行,並在該程序的上下文中執行,其中包括不適合訪問快取的安全上下文 - 要麼是因為快取中的檔案無法被該程序訪問,要麼是因為如果程序在快取中建立檔案,則該檔案可能無法被其他程序訪問。

CacheFiles 的工作方式是臨時更改程序充當的安全上下文(fsuid、fsgid 和參與者安全標籤) - 而不會在程序成為其他程序執行的操作的目標時更改程序的安全上下文(因此訊號傳遞等仍然可以正常工作)。

當要求 CacheFiles 繫結到其快取時,它會

  1. 查詢附加到根快取目錄的安全標籤,並將其用作將建立檔案的安全標籤。預設情況下,這是

    cachefiles_var_t
    
  2. 查詢發出繫結請求的程序(假定為 cachefilesd 守護程序)的安全標籤,預設情況下,該標籤將是

    cachefilesd_t
    

    並要求 LSM 提供一個安全 ID,以便根據守護程序的標籤充當。預設情況下,這將是

    cachefiles_kernel_t
    

    SELinux 根據策略中的這種形式的規則將守護程序的安全 ID 轉換為模組的安全 ID

    type_transition <daemon's-ID> kernel_t : process <module's-ID>;
    

    例如

    type_transition cachefilesd_t kernel_t : process cachefiles_kernel_t;
    

模組的安全 ID 使其有權在快取中建立、移動和刪除檔案和目錄,查詢和訪問快取中的目錄和檔案,設定和訪問快取物件上的擴充套件屬性,以及讀取和寫入快取中的檔案。

守護程序的安全 ID 僅賦予其非常有限的許可權:它可以掃描目錄、統計檔案以及擦除檔案和目錄。它可能無法讀取或寫入快取中的檔案,因此它被排除訪問其中快取的資料;它也不允許在快取中建立新檔案。

以下位置提供了策略原始檔

和更高版本。在該 tarball 中,請參閱檔案

cachefilesd.te
cachefilesd.fc
cachefilesd.if

它們由 RPM 直接構建和安裝。

如果正在使用非基於 RPM 的系統,請將上述檔案複製到它們自己的目錄並執行

make -f /usr/share/selinux/devel/Makefile
semodule -i cachefilesd.pp

在構建之前,您需要安裝 checkpolicy 和 selinux-policy-devel。

預設情況下,快取位於 /var/fscache 中,但如果希望將其放置在其他位置,則必須更改上述策略檔案,或者必須安裝輔助策略以標記快取的備用位置。

有關如何新增輔助策略以在 SELinux 處於強制模式時允許將快取放置在其他位置的說明,請參閱

/usr/share/doc/cachefilesd-*/move-cache.txt

安裝 cachefilesd rpm 時;或者,可以在原始檔中找到該文件。

關於安全的說明

CacheFiles 利用 task_struct 中的拆分安全性。它分配自己的 task_security 結構,並在代表另一個程序執行時,將 current->cred 重定向到指向它,在該程序的上下文中。

這樣做的原因是它呼叫 vfs_mkdir() 等,而不是繞過安全性並直接呼叫 inode ops。因此,VFS 和 LSM 可能會拒絕 CacheFiles 訪問快取資料,因為在某些情況下,快取程式碼在對 netfs 發出原始 syscall 的任何程序的安全上下文中執行。

此外,如果 CacheFiles 建立檔案或目錄,則將使用該物件建立的安全引數(UID、GID、安全標籤)將派生自發出系統呼叫的程序,從而可能阻止其他程序訪問快取 - 包括 CacheFiles 的快取管理守護程序 (cachefilesd)。

需要的是臨時覆蓋發出系統呼叫的程序的安全性。但是,我們不能只是對安全資料進行就地更改,因為這會影響程序作為物件,而不僅僅是作為主體。這意味著它可能會丟失訊號或 ptrace 事件,例如,並影響程序在 /proc 中的外觀。

因此,CacheFiles 利用客觀安全性 (task->real_cred) 和主觀安全性 (task->cred) 之間的安全邏輯拆分。客觀安全性儲存程序的內在安全屬性,並且永遠不會被覆蓋。這是在 /proc 中顯示的內容,也是當程序成為其他程序執行的操作的目標時使用的內容(例如 SIGKILL)。

主觀安全性儲存程序的活動安全屬性,並且可能會被覆蓋。這在外部不可見,並且在程序對另一個物件執行操作時使用,例如 SIGKILLing 另一個程序或開啟檔案。

存在 LSM 掛鉤,允許 SELinux(或 Smack 或任何其他程式)拒絕 CacheFiles 以特定安全標籤的上下文執行的請求,或者使用另一個安全標籤建立檔案和目錄。

統計資訊

如果 FS-Cache 使用以下選項編譯啟用

CONFIG_CACHEFILES_HISTOGRAM=y

那麼它將收集某些統計資訊並透過 proc 檔案顯示它們。

/proc/fs/cachefiles/histogram

cat /proc/fs/cachefiles/histogram
JIFS  SECS  LOOKUPS   MKDIRS    CREATES
===== ===== ========= ========= =========

這顯示了各種任務執行所需時間在 0 jiffies 和 HZ-1 jiffies 之間的每個時間段內的次數的細分。列如下

時間測量

查詢

在後備 fs 上執行查詢所需的時間

MKDIRS

在後備 fs 上執行 mkdir 所需的時間

CREATES

在後備 fs 上執行建立所需的時間

每行顯示花費特定時間範圍內的事件數。每個步驟的大小為 1 jiffy。JIFS 列指示涵蓋的特定 jiffy 範圍,SECS 欄位指示等效的秒數。

除錯

如果啟用了 CONFIG_CACHEFILES_DEBUG,可以透過調整以下位置中的值來啟用 CacheFiles 設施的執行時除錯

/sys/module/cachefiles/parameters/debug

這是要啟用的除錯流的位掩碼

BIT

VALUE

STREAM

POINT

0

1

常規

函式入口跟蹤

1

2

函式退出跟蹤

2

4

常規

應將適當的值集 OR’d 在一起,並將結果寫入控制檔案。例如

echo $((1|4|8)) >/sys/module/cachefiles/parameters/debug

將開啟所有函式入口除錯。

按需讀取

當在其原始模式下工作時,CacheFiles 充當遠端聯網 fs 的本地快取 - 而在按需讀取模式下,CacheFiles 可以提高需要按需讀取語義的場景,例如容器映像分發。

在發生快取未命中時,可以看到這兩種模式之間的本質區別:在原始模式下,netfs 將從遠端伺服器獲取資料,然後將其寫入快取檔案;在按需讀取模式下,獲取資料並將其寫入快取的操作將委託給使用者守護程序。

應啟用 CONFIG_CACHEFILES_ONDEMAND 以支援按需讀取模式。

協議通訊

按需讀取模式使用簡單的協議在核心和使用者守護程序之間進行通訊。該協議可以建模為

kernel --[request]--> user daemon --[reply]--> kernel

CacheFiles 將在需要時向用戶守護程序傳送請求。使用者守護程序應輪詢 devnode(“/dev/cachefiles”)以檢查是否有待處理的請求需要處理。當有待處理的請求時,將返回 POLLIN 事件。

然後,使用者守護程序讀取 devnode 以獲取要處理的請求。應該注意的是,每次讀取只能獲得一個請求。完成處理請求後,使用者守護程序應將回複寫入 devnode。

每個請求都以訊息頭開始,其形式為

struct cachefiles_msg {
        __u32 msg_id;
        __u32 opcode;
        __u32 len;
        __u32 object_id;
        __u8  data[];
};

其中

  • msg_id 是在所有待處理請求中標識此請求的唯一 ID。

  • opcode 指示此請求的型別。

  • object_id 是標識所操作快取檔案的唯一 ID。

  • data 指示此請求的有效負載。

  • len 指示此請求的整個長度,包括標頭和隨後的特定於型別的有效負載。

啟用按需模式

“bind”命令提供了一個可選引數

bind [ondemand]

當“bind”命令沒有給出任何引數時,它預設為原始模式。當給出“ondemand”引數時,即“bind ondemand”,將啟用按需讀取模式。

OPEN 請求

當 netfs 首次開啟快取檔案時,將向用戶守護程序傳送一個帶有 CACHEFILES_OP_OPEN 操作碼的請求,又名 OPEN 請求。有效負載格式的形式為

struct cachefiles_open {
        __u32 volume_key_size;
        __u32 cookie_key_size;
        __u32 fd;
        __u32 flags;
        __u8  data[];
};

其中

  • data 包含 volume_key,其後直接跟著 cookie_key。卷金鑰是一個以 NUL 結尾的字串;cookie 金鑰是二進位制資料。

  • volume_key_size 指示卷金鑰的大小(以位元組為單位)。

  • cookie_key_size 指示 cookie 金鑰的大小(以位元組為單位)。

  • fd 指示對快取檔案的匿名 fd,透過該 fd,使用者守護程序可以對快取檔案執行 write/llseek 檔案操作。

使用者守護程序可以使用給定的 (volume_key, cookie_key) 對來區分請求的快取檔案。使用給定的匿名 fd,使用者守護程序可以獲取資料並在後臺將其寫入快取檔案,即使核心尚未觸發快取未命中。

請注意,每個快取檔案都有一個唯一的 object_id,但它可能有多個匿名 fd。使用者守護程序可以透過 dup() 從 @fd 欄位指示的初始匿名 fd 複製匿名 fd。因此,每個 object_id 都可以對映到多個匿名 fd,而 usr 守護程序本身需要維護對映。

在實現使用者守護程序時,請注意 RLIMIT_NOFILE、/proc/sys/fs/nr_open/proc/sys/fs/file-max。通常,這些不需要很大,因為它們與每個單獨檔案系統的開啟裝置 blob 的數量有關,而不是開啟檔案的數量。

使用者守護程序應透過在 devnode 上發出“copen”(完成開啟)命令來回復 OPEN 請求

copen <msg_id>,<cache_size>

其中

  • msg_id 必須與 OPEN 請求的 msg_id 欄位匹配。

  • 當 >= 0 時,cache_size 指示快取檔案的大小;當 < 0 時,cache_size 指示使用者守護程序遇到的任何錯誤程式碼。

CLOSE 請求

當 cookie 撤回時,將向用戶守護程序傳送一個 CLOSE 請求(操作碼 CACHEFILES_OP_CLOSE)。這告訴使用者守護程序關閉與給定 object_id 關聯的所有匿名 fd。CLOSE 請求沒有額外的有效負載,不應回覆。

READ 請求

在按需讀取模式下遇到快取未命中時,CacheFiles 將向用戶守護程序傳送一個 READ 請求(操作碼 CACHEFILES_OP_READ)。這告訴使用者守護程序獲取請求的檔案範圍的內容。有效負載的形式為

struct cachefiles_read {
        __u64 off;
        __u64 len;
};

其中

  • off 指示請求的檔案範圍的起始偏移量。

  • len 指示請求的檔案範圍的長度。

當它收到 READ 請求時,使用者守護程序應獲取請求的資料並將其寫入由 object_id 標識的快取檔案。

完成處理 READ 請求後,使用者守護程序應透過對與 READ 請求中給出的 object_id 關聯的其中一個匿名 fd 使用 CACHEFILES_IOC_READ_COMPLETE ioctl 來回復。ioctl 的形式為

ioctl(fd, CACHEFILES_IOC_READ_COMPLETE, msg_id);

其中

  • fd 是與給定的 object_id 關聯的匿名 fd 之一。

  • msg_id 必須與 READ 請求的 msg_id 欄位匹配。