linux 檔案鎖flock,lockf,fcntl
1、flock,lockf,fcntl之間區別
先上結論:flock是檔案鎖,鎖的粒度是整個檔案,就是說如果一個程序對一個檔案加了LOCK_EX型別的鎖,別的程序是不能對這個檔案加鎖的。
lockf是對fcntl的封裝,這兩個東西在核心上的實現是一樣的。它們的粒度是位元組,不同的程序可以對相同的檔案不同位元組加LOCK_EX型別的鎖。
2、linux檔案系統
在詳解鎖的實現機制前,我們先來看一下linux檔案系統的實現。
相信大家都看過這樣一副圖。與程序相關的是檔案描述符表,檔案表和i-node都是系統級別的。當我們在程序中開啟一個檔案時,檔案描述符裡就會產生一個檔案描述符表項與之對應,同樣的系統內也會有檔案控制代碼和相應的i-node,我們需要注意的是多個檔案表項(同一個程序或不同程序)可以指向同一個檔案控制代碼。
2、 flock鎖的實現機制
flock
在實現上關聯到的是檔案描述符(上圖中檔案描述符部分),這就意味著如果我們在程序中複製了一個檔案描述符,那麼使用flock
對這個描述符加的鎖也會在新複製出的描述符中繼續引用。我們可以寫如下程式碼測試:
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/file.h> #include<wait.h> #define PATH "/tmp/lock" int main() { int fd; pid_t pid; fd = open(PATH, O_RDWR | O_CREAT | O_TRUNC, 0644); flock(fd, LOCK_EX); printf("%d: locked!\n", getpid()); pid = fork(); if (pid == 0) { // fd = open(PATH, O_RDWR|O_CREAT|O_TRUNC, 0644); flock(fd, LOCK_EX); printf("%d: locked!\n", getpid()); exit(0); } wait(NULL); unlink(PATH); exit(0); }
輸出結果:
Sangfor:aCMP/acmp-fefcfe3a1674 ~/test o ./a.out 118333: locked! 118334: locked!
由結果可見,父程序已經持有互斥鎖的情況下,子程序應該對檔案加鎖失敗才符合我們的預期。但是子程序確加鎖成功。原因就在於flock的實現是關聯檔案描述符。
子程序由父程序建立,子程序的檔案描述符表和父程序的一模一樣,在fork()子程序後,子程序本身就持有該檔案的互斥鎖。同樣的道理,對檔案描述符dup(), dup2()都會有這樣的問題。怎麼解決這個問題呢?
1、重新open這個檔案,使用新的檔案描述符
fd = open(PATH, O_RDWR|O_CREAT|O_TRUNC, 0644);
我們將上述註釋掉的程式碼開啟,重新編譯執行,輸出結果如下:
Sangfor:aCMP/acmp-fefcfe3a1674 ~/test o ./a.out 172199: locked!
這次子程序沒有能上鎖,重新open這個檔案會建立一個新的檔案描述符與父程序程序而來的描述符是不相關的,所以就符合我們預期的效果。
另外要注意:除非檔案描述符被標記了close-on-exec
標記,flock
鎖和lockf
鎖都可以穿越exec
,在當前程序變成另一個執行映象之後仍然保留。
3、lockf的實現機制
lockf的實現是關聯到核心i-node的(上圖核心部分),每次加鎖都會在i-node節點上掛一個flock的結構:
struct flock { short l_type;/*F_RDLCK, F_WRLCK, or F_UNLCK*/ off_t l_start;/*相對於l_whence的偏移值,位元組為單位*/ short l_whence;/*從哪裡開始:SEEK_SET, SEEK_CUR, or SEEK_END*/ off_t l_len;/*長度, 位元組為單位; 0 意味著縮到檔案結尾*/ pid_t l_pid;/*returned with F_GETLK*/ };
對LOCK_EX型別的鎖來說,核心中最多隻有一份這樣的資料,所以即使檔案描述符是從父程序程序過來或dup()產生的,對同一個節點加鎖都會失敗。我們寫如下程式碼測試一下:
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/file.h> #include <wait.h> #define PATH "/tmp/lock" int main() { int fd; pid_t pid; fd = open(PATH, O_RDWR | O_CREAT | O_TRUNC, 0644); lockf(fd, F_LOCK, 0); printf("%d: locked!\n", getpid()); pid = fork(); if (pid == 0) { lockf(fd, F_LOCK, 0); printf("%d: locked!\n", getpid()); exit(0); } wait(NULL); unlink(PATH); exit(0); }
執行結果:
Sangfor:aCMP/acmp-fefcfe3a1674 ~/test o ./a1.out 47087: locked!
這次我們沒有對檔案重新open,子程序就一直等在那裡。完全符合我們的預期。這也印證了lockf的實現是核心中i-node相關的。
此外有一篇文章介紹建議性鎖和強制性鎖,這篇文章是以flock為例說明的,flock和lockf的加鎖規則是一致的。需要了解加鎖規則請參看: