從內核實現的角度來看,每當創建一把文件鎖的時候,系統就會實例化一個struct file_lock對象,這個file_lock對象會記錄鎖的相關信息:如鎖的類型(共享鎖,獨佔鎖)、擁有這把鎖的進程號、鎖的標識(租賃鎖,阻塞鎖,POSIX鎖,FLOCK鎖),等等。最後把這個file_lock對象插入到被鎖文件的inode.i_flock鏈表中,就完成了對該文件的加鎖功能。要是其它進程想要對同一個文件加鎖,那麼它在將file_lock對象插入到inode.i_flock之前,會遍歷該鏈表,如果沒有發現衝突的鎖,就將其插入到鏈表尾,表示加鎖成功,否則失敗。
至於爲什麼要將inode與file_lock以鏈表的形式關聯起來,主要是考慮到用戶有時可以對同一個文件加多個文件鎖。例如:我們可以對同一個文件加多個共享鎖;或者我們可以同時對文件加POSIX鎖和FLOCK鎖,這兩種鎖分別對應flock()和fcntl()兩種系統調用函數;再或者可以通過多次調用fcntl()對同一個文件中的多個內容塊加上POSIX記錄鎖。
下面講下POSIX鎖和FLOCK鎖的一些區別:
2. POSIX鎖可以重複加鎖,即同一個進程,可以對同一個文件多次加同樣一把鎖。例如:第一次我對A文件的一個0~10的內容塊加了一把獨佔鎖,那麼第二次同一個進程中我一樣可以對這個A文件的0~10的內容塊再加一把獨佔鎖,這個有點像是遞歸加鎖,但是我解鎖時只需要解一次。FLOCK鎖則不同,如果你第一次對A文件加了一把獨佔鎖,那麼在同一個進程中你就不能對A文件再加一把鎖了。這個區別其實只不過是在加鎖的時候,遍歷inode.i_flock鏈表時,發現存在PID相同的鎖時,系統對於POSIX鎖和FLOCK鎖的具體處理手段不一樣罷了。
3. 通過第2點,我們可以想象一下,POSIX鎖和FLOCK鎖在多線程環境下的不同。我們知道從Linux內核的視角來看,它是不區分所謂的進程和線程的,都不過是CPU調度隊列中的一個個task_struct實例而已,所以不會對線程的場景進行專門的處理,也正以爲如此,平時我們用的NPTL線程庫也都是在用戶態環境中模擬出來的,Linux內核並不直接支持。回到剛剛的話題,因爲內核它在加鎖的時候是看PID的,所以在內核看來多線程的加鎖只不過是同一個進程(因爲每個線程的PID都是一樣的)在對同一個文件加多把鎖。這樣,多線程環境下的加鎖行爲就表現爲:同一個進程中的多個線程可以對同一個文件加多次POSIX獨佔或共享鎖,但是不可以對同一個文件加多次FLOCK獨佔鎖(不過共享鎖是可以加多次的)。
4. 在一個項目中使用了GPFS共享文件系統,我們在開發過程中發現,對於FLOCK鎖只支持本地,而POSIX鎖則可以支持跨主機加鎖。例如:我們有兩臺獨立的機器A和B,在A機器上有某個進程對文件f加POSIX獨佔鎖,然後在B機器上當有某個進程想對f加POSIX獨佔鎖時,就會失敗。可是當我們使用FLOCK鎖時,就發現兩臺機器對同一個文件加FLOCK鎖是互不影響的,即A和B機器都可以對f加獨佔鎖。針對這種情況,IBM工程師在郵件中給出的解釋如下:
對於一些更加底層的細節問題,如:加鎖過程是隻在VFS層操作還是涉及到具體的物理文件系統、GPFS上的POSIX文件鎖如何做到跨機器有效,等等問題,可能就要參考源代碼了。