多粒度時間戳¶
引言¶
歷來,核心總是使用粗粒度時間值來標記 inode。該值每 jiffy 更新一次,因此在該 jiffy 內發生的任何更改都將具有相同的時間戳。
當核心標記 inode(由於讀或寫操作)時,它首先獲取當前時間,然後將其與現有時間戳進行比較,以檢視是否有任何變化。如果沒有變化,則可以避免更新 inode 的元資料。
因此,從效能角度來看,粗粒度時間戳是好的,因為它們減少了元資料更新的需求,但從確定是否有任何變化的立場來看,它們是糟糕的,因為在一個 jiffy 內可能會發生很多事情。
它們在 NFSv3 中尤其麻煩,因為不變的時間戳使得難以判斷是否需要使快取失效。NFSv4 提供了一個專用的變更屬性,應該總是顯示可見的變更,但並非所有檔案系統都正確實現了這一點,導致 NFS 伺服器在許多情況下用 ctime 替代它。
多粒度時間戳旨在透過在檔案最近被查詢過時間戳,並且當前粗粒度時間未引起變化時,選擇性地使用細粒度時間戳來彌補這一點。
Inode 時間戳¶
目前 inode 中有 3 個時間戳,它們根據不同的活動更新為當前的牆上時鐘時間
- ctime
inode 變更時間。每當 inode 的元資料發生變化時,它會標記為當前時間。請注意,此值無法從使用者空間設定。
- mtime
inode 修改時間。每當檔案內容發生變化時,它會標記為當前時間。
- atime
inode 訪問時間。每當 inode 的內容被讀取時,它會標記為當前時間。這被普遍認為是一個嚴重的錯誤。通常透過 noatime 或 relatime 等選項來避免。
更新 mtime 總是意味著 ctime 的變化,但由於讀取請求而更新 atime 則不會。
多粒度時間戳僅針對 ctime 和 mtime 進行跟蹤。atime 不受影響,並且總是使用粗粒度值(受制於下限)。
Inode 時間戳排序¶
除了提供有關單個檔案變化的資訊外,檔案時間戳在“make”等應用程式中也起著重要作用。這些程式透過測量時間戳來確定原始檔是否比快取物件新。
像 make 這樣的使用者空間應用程式只能根據操作邊界來確定排序。對於系統呼叫,這些是系統呼叫的入口和出口點。對於 io_uring 或 nfsd 操作,這些是請求提交和響應。在併發操作的情況下,使用者空間無法確定事件發生的順序。
例如,如果一個執行緒依次修改一個檔案,然後再修改另一個檔案,那麼第二個檔案必須顯示等於或晚於第一個檔案的 mtime。如果兩個執行緒執行不重疊的類似操作,情況也是如此。
然而,如果兩個執行緒有時間上重疊的競態系統呼叫,那麼就沒有這樣的保證,第二個檔案可能顯示為在第一個檔案之前、之後或同時被修改,無論哪個先提交。
請注意,上述假設系統不會發生即時時鐘向後跳變。如果這種情況發生在不合時宜的時間,那麼時間戳可能會出現倒退,即使在一個正常執行的系統上也是如此。
多粒度時間戳實現¶
多粒度時間戳旨在確保對單個檔案的更改始終可識別,同時不違反修改多個不同檔案時的排序保證。這會影響 mtime 和 ctime,但 atime 將始終使用粗粒度時間戳。
它在 i_ctime_nsec 欄位中使用一個未使用的位來指示 mtime 或 ctime 是否已被查詢過。如果其中一個或兩者都被查詢過,那麼核心會特別注意確保下一次時間戳更新將顯示可見的變化。這確保了諸如 NFS 等用例的緊密快取一致性,同時不犧牲當檔案未被監視時減少元資料更新的好處。
Ctime 下限值¶
僅僅根據 mtime 或 ctime 是否已被查詢來簡單地使用細粒度或粗粒度時間戳是不夠的。一個檔案可能獲得一個細粒度時間戳,而第二個檔案稍後修改時可能獲得一個粗粒度時間戳,這個時間戳卻顯得比第一個檔案更早,這將打破核心的時間戳排序保證。
為了緩解這個問題,維護一個全域性下限值,以確保這種情況不會發生。在上述示例中,這兩個檔案在這種情況下可能顯示為同時被修改,但它們絕不會顯示相反的順序。為了避免即時時鐘跳變問題,下限被管理為一個單調的 ktime_t 值,並且這些值在需要時會轉換為即時時鐘值。
實現注意事項¶
多粒度時間戳旨在供從本地時鐘獲取 ctime 值的本地檔案系統使用。這與僅僅從伺服器映象時間戳值的網路檔案系統等形成對比。
對於大多數檔案系統,只需在 fstype->fs_flags 中設定 FS_MGTIME 標誌即可選擇啟用,前提是 ctime 僅透過 inode_set_ctime_current() 設定。如果檔案系統有一個沒有呼叫 generic_fillattr 的 ->getattr 例程,那麼它應該呼叫 fill_mg_cmtime() 來填充這些值。對於 setattr,它應該使用 setattr_copy() 來更新時間戳,或者以其他方式模仿其行為。