Linux 中的文件鎖
Linux 支持的文件鎖技術主要包括勸告鎖(advisory lock)和強制鎖(mandatory lock)這兩種。此外,Linux 中還引入了兩種強制鎖的變種形式:共享模式強制鎖(share-mode mandatory lock)和租借鎖(lease)。
在 Linux 中,不論進程是在使用勸告鎖還是強制鎖,它都可以同時使用共享鎖和排他鎖(又稱爲讀鎖和寫鎖)。多個共享鎖之間不會相互干擾,多個進程在同一時刻可以對同一個文件加共享鎖。但是,如果一個進程對該文件加了排他鎖,那麼其他進程則無權再對該文件加共享鎖或者排他鎖,直到該排他鎖被釋放。所以,對於同一個文件來說,它可以同時擁有很多讀者,但是在某一特定時刻,它只能擁有一個寫者,它們之間的兼容關係如表 1 所示。
表 1. 鎖間的兼容關係
是否滿足請求 | ||
---|---|---|
當前加上的鎖 | 共享鎖 | 排他鎖 |
無 | 是 | 是 |
共享鎖 | 是 | 否 |
排他鎖 | 否 | 否 |
勸告鎖
勸告鎖是一種協同工作的鎖。對於這一種鎖來說,內核只提供加鎖以及檢測文件是否已經加鎖的手段,但是內核並不參與鎖的控制和協調。也就是說,如果有進程不遵守“遊戲規則”,不檢查目標文件是否已經由別的進程加了鎖就往其中寫入數據,那麼內核是不會加以阻攔的。因此,勸告鎖並不能阻止進程對文件的訪問,而只能依靠各個進程在訪問文件之前檢查該文件是否已經被其他進程加鎖來實現併發控制。進程需要事先對鎖的狀態做一個約定,並根據鎖的當前狀態和相互關係來確定其他進程是否能對文件執行指定的操作。從這點上來說,勸告鎖的工作方式與使用信號量保護臨界區的方式非常類似。
勸告鎖可以對文件的任意一個部分進行加鎖,也可以對整個文件進行加鎖,甚至可以對文件將來增大的部分也進行加鎖。由於進程可以選擇對文件的某個部分進行加鎖,所以一個進程可以獲得關於某個文件不同部分的多個鎖。
強制鎖
與勸告鎖不同,強制鎖是一種內核強制採用的文件鎖,它是從 System V Release 3 開始引入的。每當有系統調用 open()、read() 以及write() 發生的時候,內核都要檢查並確保這些系統調用不會違反在所訪問文件上加的強制鎖約束。也就是說,如果有進程不遵守遊戲規則,硬要往加了鎖的文件中寫入內容,內核就會加以阻攔:
如果一個文件已經被加上了讀鎖或者共享鎖,那麼其他進程再對這個文件進行寫操作就會被內核阻止;
如果一個文件已經被加上了寫鎖或者排他鎖,那麼其他進程再對這個文件進行讀取或者寫操作就會被內核阻止。
如果其他進程試圖訪問一個已經加有強制鎖的文件,進程行爲取決於所執行的操作模式和文件鎖的類型,歸納如表 2 所示:
表 2. 進行對已加強制鎖的文件進行操作時的行爲
當前鎖類型 | 阻塞讀 | 阻塞寫 | 非阻塞讀 | 非阻塞寫 |
---|---|---|---|---|
讀鎖 | 正常讀取數據 | 阻塞 | 正常讀取數據 | EAGAIN |
寫鎖 | 阻塞 | 阻塞 | EAGAIN | EAGAIN |
需要注意的是,如果要訪問的文件的鎖類型與要執行的操作存在衝突,那麼採用阻塞讀/寫操作的進程會被阻塞,而採用非阻塞讀/寫操作的進程則不會阻塞,而是立即返回 EAGAIN。
另外,unlink() 系統調用並不會受到強制鎖的影響,原因在於一個文件可能存在多個硬鏈接,此時刪除文件時並不會修改文件本身的內容,而是隻會改變其父目錄中 dentry 的內容。
然而,在有些應用中並不適合使用強制鎖,所以索引節點結構中的 i_flags 字段中定義了一個標誌位MS_MANDLOCK用於有選擇地允許或者不允許對一個文件使用強制鎖。在 super_block 結構中,也可以將 s_flags 這個標誌爲設置爲1或者0,用以表示整個設備上的文件是否允許使用強制鎖。
要想對一個文件採用強制鎖,必須按照以下步驟執行:
使用 -o mand 選項來掛載文件系統。這樣在執行 mount() 系統調用時,會傳入 MS_MANDLOCK 標記,從而將 super_block 結構中的 s_flags 設置爲 1,用來表示在這個文件系統上可以採用強制鎖。
Linux文件鎖的示例
爲了理解文件鎖是如何工作的,我們建立程序文件file_lock.c:
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char **argv) {
if (argc > 1) {
int fd = open(argv[1], O_WRONLY);
if(fd == -1) {
printf("Unable to open the file\n");
exit(1);
}
static struct flock lock;
lock.l_type = F_WRLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
lock.l_pid = getpid();
int ret = fcntl(fd, F_SETLKW, &lock);
printf("Return value of fcntl:%d\n",ret);
if(ret==0) {
while (1) {
scanf("%c", NULL);
}
}
}
}
用gcc編譯此程序:
# cc -o file_lock file_lock.c
使用mount命令帶“mand”參數來重新掛載根文件系統,如下所示。這將在文件系統級別使能強制鎖功能。注意:你必須切換到root用戶才能執行下面的命令。
# mount -oremount,mand /
在可執行的(file_lock所在的)目錄中創建兩個名爲“advisory.txt”和“mandatory.txt”的文件。對於“mandatory.txt”使能Set-Group-ID,同時不使能Group-Execute-Bit,如下所示:
# touch advisory.txt
# touch mandatory.txt
# chmod g+s,g-x mandatory.txt
測試協同鎖:執行示例程序,以“advisory.txt”作爲參數。
# ./file_lock advisory.txt
此程序將等待用戶的輸入。從另一個終端或控制檯,嘗試輸入以下命令行:
# ls >>advisory.txt
在上面的例子中,ls命令會將其輸出寫入到advisory.txt文件中。即使我們獲得了一個寫入鎖,仍然會有一些進程(非合作)能夠往文件裏寫入數據。這就是所謂的“協同”鎖。
測試強制鎖:再次執行示例程序,以“mandatory.txt”作爲參數。
# ./file_lock mandatory.txt
從另一個終端或控制檯,嘗試輸入以下命令行:
# ls >>mandatory.txt
在上面的例子中,ls命令在將其輸出寫入到mandatory.txt文件之前,會等待文件鎖被刪除。雖然它仍然是一個非合作進程,但強制鎖起了作用。
共享模式鎖
Linux 中還引入了兩種特殊的文件鎖:共享模式強制鎖和租借鎖。這兩種文件鎖可以被看成是強制鎖的兩種變種形式。共享模式強制鎖可以用於某些私有網絡文件系統,如果某個文件被加上了共享模式強制鎖,那麼其他進程打開該文件的時候不能與該文件的共享模式強制鎖所設置的訪問模式相沖突。但是由於可移植性不好,因此並不建議使用這種鎖。
租借鎖
採用強制鎖之後,如果一個進程對某個文件擁有寫鎖,只要它不釋放這個鎖,就會導致訪問該文件的其他進程全部被阻塞或不斷失敗重試;即使該進程只擁有讀鎖,也會造成後續更新該文件的進程的阻塞。爲了解決這個問題,Linux 中採用了一種新型的租借鎖。
當進程嘗試打開一個被租借鎖保護的文件時,該進程會被阻塞,同時,在一定時間內擁有該文件租借鎖的進程會收到一個信號。收到信號之後,擁有該文件租借鎖的進程會首先更新文件,從而保證了文件內容的一致性,接着,該進程釋放這個租借鎖。如果擁有租借鎖的進程在一定的時間間隔內沒有完成工作,內核就會自動刪除這個租借鎖或者將該鎖進行降級,從而允許被阻塞的進程繼續工作。
系統默認的這段間隔時間是 45 秒鐘,定義如下:
137 int lease_break_time = 45;
這個參數可以通過修改 /proc/sys/fs/lease-break-time 進行調節(當然,/proc/sys/fs/leases-enable 必須爲 1 才行)。
轉自:http://www.ibm.com/developerworks/cn/linux/l-cn-filelock/