在 Linux 上,openat
系統呼叫可用於建立檔案並測試它們是否存在。就 C/C++ 記憶體模型而言,建立檔案並驗證其存在會建立同步關係。我需要知道的是這些同步是否都是順序一致的。 (我當然希望如此,但我實際上還沒有在任何地方看到過這個記錄。)
例如,給定進程 p1 和 p2,以及路徑 A 和 B:
如果 p1 這樣做:建立(A),然後建立(B)
p2 執行以下操作:嘗試開啟(B),然後嘗試開啟(A)
且沒有其他進程幹擾A或B,是否有可能p2成功開啟B但找不到A?
如果有什麼不同,我們可以假設所有操作都在一個檔案系統中。
答案1
透過所有底層磁碟和多核心 CPU 最佳化,不一定能夠確定兩個進程之間操作序列的嚴格順序。這就是如果存在時間相關行為的可能性則使用信號量的原因。
答案2
僅適用於同一目錄中的檔案。
讀取存取權限。鎖定規則:呼叫者鎖定我們正在存取的目錄。鎖被共享。
物件創建。鎖定規則:與上面相同,但鎖定是排他的。
物體移除。鎖定規則:呼叫者鎖定父級,找到受害者,鎖定受害者並呼叫方法。鎖是獨佔的。
rename()
那是不是跨目錄。鎖定規則:呼叫者鎖定父級並尋找來源和目標。在交換的情況下(使用RENAME_EXCHANGE
標誌參數)鎖定兩者。無論如何,如果目標已經存在,請鎖定它。如果來源是非目錄,則鎖定它。如果我們需要鎖定兩者,請按 inode 指標順序鎖定它們。然後調用該方法。所有鎖都是獨佔的。注意:我們可能會鎖定共享的來源(以及交換情況下的目標)。連結創建。鎖定規則:
鎖定父級
檢查來源不是目錄
鎖源
呼叫該方法。所有鎖都是獨佔的。
跨目錄重命名。整個群中最棘手的。鎖定規則:
鎖定檔案系統
將父母鎖定在「祖先第一」的順序。
找到來源和目標。
如果舊父級等於或是目標的後代,則失敗 -
ENOTEMPTY
如果新父級等於或是來源的後代,則失敗 -
ELOOP
如果是交換,請鎖定來源和目標。
如果目標存在,則鎖定它。如果來源是非目錄,則鎖定它。如果我們需要鎖定兩者,請按照 inode 指標順序進行操作。
呼叫該方法。全部
->i_rwsem
採取獨家。同樣,我們可能會鎖定共享的來源(以及交換情況下的目標)。上述規則顯然保證了所有將要透過方法讀取、修改或刪除的目錄都將被呼叫者鎖定。
鎖定強制線性化,因此操作在單一目錄上完全有序。但是,讀取存取 (1)、物件建立 (2) 和物件刪除 (3) 不會採用比目錄鎖更廣泛的鎖,因此無法保證不同目錄中目錄操作的順序;不同的觀察者可能會看到目錄的線性歷史以不同的方式交織。