檔案鎖(File Locking)
兩種不同的檔案鎖API
- flock(): 對整個檔案上鎖
- fcntl(): 對檔案區間上鎖
基本上兩種的通用流程都像是這樣
- 檔案上鎖
- 執行IO
- 檔案解鎖
因為stdio函式庫有自己的buffer, 所以搭配file locking要小心使用
- 使用read()和write()取代stdio函式庫的function來執行檔案IO
- 在lock前先flush stdio stream, 在unlock之前也先flush stdio stream
- 使用setbuf()來關閉stdio buffer
Flock:
flock()
int flock(int fd, int operation):
- 會將整個檔案上鎖
- operation參數有LOCK_SH, LOCK_EX和LOCK_UN, 可以利用OR(|)將LOCK_NB變成nonblock. flock()就不會被blocking, 會回傳-1.
Operation:
Value
|
Description
|
LOCK_SH
|
對fd reference的檔案設置一把共用鎖(shared lock)
|
LOCK_EX
|
對fd reference的檔案設置一把共用鎖(exclusive lock)
|
LOCK_UN
|
將fd reference的檔案解鎖
|
LOCK_NB
|
產生一個non-blocking的請求
|
- 同時持有一個shared lock的process數量並沒有限制. 但同時只有一個process有exclusive lock.(Exclusive lock會拒絕其他shared lock和exclusive lock)
- 無論process對於檔案的存取方式為何(R/W, ReadOnly, WriteOnll), 都可以使用flock()對檔案加上shared lock或exclusive lock
- shared lock跟exclusive lock可以互相轉換. 但轉換的過程不是atomic的. 轉換的步驟是先移除舊的鎖然後建立新的鎖. 中間若有另一個新鎖上去, 轉換過程就會發生失敗並且失去原本的鎖。
- 再將shared lock轉成exclusive lock的過程中. 若有另一個process持有相同的shared lock, 那他就會被blocking, 除非他有設置LOCK_NB
flock()上鎖的相容性
Process A
|
Process B
| |
LOCK_SH
|
LOCK_EX
| |
LOCK_SH
|
Yes
|
No
|
LOCK_EX
|
No
|
No
|
flock的lock是跟fd有關而不是跟檔案本身有關, 當關閉所有個fd則檔案就會被unlok.
利用dup()做出來的newfd就會把原本fd所放的lock給unlok,
flock(fd, LOCK_EX);
newfd=dup(fd);
flock(newfd, LOCK_UN);
由fork()出來的child fd也會有相同的情形
flock(fd, LOCK_EX);
if (fork() == 0)
flock(fd, LOCK_UN)
flock()的限制
- 只能上鎖整個檔案
- 只能使用advisory lock
- 很多NFS實作無法使用flock()的上鎖
Fcntl:
- fcntl()用來鎖住檔案中與應用程式定義的紀錄邊界的資料範圍, 稱之為record locking.
- linux可以將record locking應用在任何類型的file descripitor
使用fcntl()通用的形式
struct flock flockstr;
fcntl(fd, cmd, &flockstr);
flock data structure:
struct flock {
short l_type;
short l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
}
- l_type: 鎖的類別
- F_RDLCK: 設定一個read lock
- F_WRLCK: 設定一個write lock
- F_UNLCK: unlock一個lock
- l_whence: 要lock的起點
- SEEK_SET: 檔案起點
- SEEK_CUR: 目前檔案位置
- SEEK_END: 檔案結尾
- l_start: 類似offset的概念. 以l_whence為起點
- l_len:
- 帶上鎖的位元數. 起始位置由l_whence和l_start定義. 可以對檔案結尾之後的byte上鎖但無法對檔案起點之前的byte上鎖.
- 若l_len=0表示將從l_whence和l_start指定的位置之後到檔案結尾都要上鎖, 無論檔案增長到多大.
- 鎖整個檔案
- l_whence = SEEK_SET
- l_start = 0
- l_len = 0
cmd參數
- F_SETLK: 對flockstr指定的byte資料上鎖
- F_SETLKW: 與F_SETLK相同. 而且是以blocking的方式上鎖. 使用F_SETLKW上鎖kernel會判斷目前環境有沒有可能產生deadlock. 有的話會選擇其中一個prcess的fcntl()失敗並回傳EDEADLK的錯誤
- F_GETLK: 檢查現在該byte有沒有被lock. 並將資訊放到flockstr中
- No: l_type = F_UNLCK, 其餘欄位不變
- Yes: 回傳任一個lock回來, 但不確定是回傳那一個. 回傳的資訊包括
- l_type
- l_start
- l_len
- l_whence = 0
- l_pid: 持有這把鎖的process id
fcntl()的lock和unlock的細節:
- 對於某區間unlock一定會成功即使我們沒有對那個區間lock
- 一個process對於一個檔案的特定區間只能有一種鎖
- 一個process無法將自己鎖在一個檔案區間之外
- 在某個特定鎖中間再加上一個較小區間的鎖, 會造成有三把鎖的情形 如下圖
- 在某個特定鎖中間解鎖較小區間的鎖, 會造成有兩把鎖的情形 如下圖
上鎖的限制和效能
- Linux並沒有對於recording lock有設定上限
- 每個檔案都有一個 linked list, 此linked list記錄著該檔案的鎖. linked list中的鎖是有排序的. 先依照process id在依照起始的offset排序. 類似下圖
- 把一把新鎖加入的時候, kernel會檢查上面每一把鎖有沒有跟新鎖衝突. 從第一個lock到最後一個lock依序檢查. 新增和移除一個鎖的時間跟檔案上的鎖數量呈現一個線性關係.
fcntl()繼承和釋放
- fork()出來的child prcoess不會繼承record locking.
- 一個process內每個thread都共用同一組record locking.
- record locking同時與一個process和一個inode有關係.當一個prcess終止時, 全部的record locking將會被release
- 當一個process關閉一個fd時, 該process所持有的對應檔案的每把鎖都會釋放, example:
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
fd1 = open("testfile", O_RDWR);
fd2 = open("testfile", O_RDWR);
if (fcntl(fd1, cmd, &fl) == -1)
errExit("fcntl")
close(fd2)
鎖的starvation和Queue機制
- 排隊中的lock request獲得請求的順序是不確定的
- write跟read有一樣的優先權
強制上鎖(mandatory)
- fcntl()或flock()默認的鎖都是advisory, 表示process可以忽略使用fcntl()或flock()直接去對檔案做IO
- Linux支援強制式的fcntl()紀錄鎖, 表示process需要對每次檔案IO前都進行檢查
- 開啟mandatory lock的方式
- mount的時候加上 -o mand的參數
- ex. # mount -o mand /dev/sda10 /testfs
- 原理是利用開啟set-group-ID並關閉group-execute來達成
- 在shell可以利用下列指令
- # chmod g+s,g-x /testfs/file
- 若有任一process有一個檔案任何部分有mandatory lock(read lock or write lock)就無法在該檔案建立一個shared memory mapping. 反之亦然
- 使用mandatory lock會造成效能下降應減少用之
/proc/locks 檔案
可以看到目前系統存在的lock資訊
- 1: POSIX ADVISORY WRITE 03:07:133880 0 EOF
- 鎖的序號
- FLOCK表flock()鎖, POSIX表fcntl()鎖
- 鎖的模式ADVISORY或MANDATORY
- 鎖的類型, READ或WRITE
- 持有鎖的PID
- 檔案系統主要裝置編號: 檔案系統次要裝置編號: inode編號
- 鎖的起始byte, flock都是0
- 鎖的結束byte, EOF表示到檔案結尾
- cat /proc/locks若在前頭看到->表示該鎖正被blocking
- 1: -> POSIX ADVISORY WRITE 03:07:133880 0 EOF
利用file lock來確保daemon只有一個instance.
- 在一個標準目錄下建立一個檔案並在檔案中放入一把write lock. 通常以.pid結尾
- 綁定某個port(通常用於網路伺服器)
沒有留言:
張貼留言