2017年3月3日 星期五

Linux 檔案鎖(File Locking)

檔案鎖(File Locking)


兩種不同的檔案鎖API
  • flock(): 對整個檔案上鎖
  • fcntl(): 對檔案區間上鎖
基本上兩種的通用流程都像是這樣
  1. 檔案上鎖
  2. 執行IO
  3. 檔案解鎖


因為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
  1. 鎖的序號
  2. FLOCK表flock()鎖, POSIX表fcntl()
  3. 鎖的模式ADVISORY或MANDATORY
  4. 鎖的類型, READ或WRITE
  5. 持有鎖的PID
  6. 檔案系統主要裝置編號: 檔案系統次要裝置編號: inode編號
  7. 鎖的起始byte, flock都是0
  8. 鎖的結束byte, EOF表示到檔案結尾
  • cat /proc/locks若在前頭看到->表示該鎖正被blocking
    • 1: -> POSIX ADVISORY WRITE 03:07:133880 0 EOF


利用file lock來確保daemon只有一個instance.

  • 在一個標準目錄下建立一個檔案並在檔案中放入一把write lock. 通常以.pid結尾
  • 綁定某個port(通常用於網路伺服器)

沒有留言:

張貼留言