概述
在多數
unix
系統中,當多個程序/線程同時編輯一個檔案時,該檔案的最後狀态取決于最後一個寫該檔案的程序。
對于有些應用程式,如資料庫,各個程序需要保證它正在單獨地寫一個檔案。這時就要用到檔案鎖。
檔案鎖(也叫記錄鎖)的作用是,當一個程序讀寫檔案的某部分時,其他程序就無法修改同一檔案區域。
能夠實作檔案鎖的函數主要有2個:
flock
和
fcntl
。
早期的伯克利版本隻支援flock,該函數隻能對整個檔案加鎖,不能對檔案的一部分加鎖。
lockf
是在
fcntl
基礎上構造的函數,它提供了一個簡化的接口。它們允許對檔案中任意位元組區域加鎖,短至一個位元組,長至整個檔案。
fcntl函數
#include <fcntl.h>
int fcntl(int fd, int cmd, .../*struct flock *flockptr*/);
#傳回值:若成功,傳回值依賴于cmd,失敗傳回-1
cmd
是
F_GETLK, F_SETLK, F_SETLKW
中的一個。第三個參數是指向
flock
結構的指針,
flock
結構如下:
struct flock {
short l_type;/* one of F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence;/* SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start;/* offset in bytes, relative to l_whence */
off_t l_end;/* length, in bytes, 0 means lock to EOF */
off_t l_pid;/* returned with F_GETLK */
};
其中,
- 鎖類型:共享讀鎖
,獨占性寫鎖F_RDLCK
,解鎖F_WRLCK
F_UNLCK
- 加鎖或解鎖區域的起始位元組偏移量(
)l_start, l_whence
- 區域位元組長度(
)L_len
- 程序的id持有的鎖能阻塞目前程序,僅由
傳回F_GETLK
- 鎖可以在檔案尾處開始或者越過尾端開始,但是不能在檔案起始位置之前開始
- 若
, 表示鎖的範圍可以擴大到最大可能偏移量,這意味着不管向檔案中追加多少資料,它們都可以處于鎖的範圍内,而且起始位置可以任意l_len=0
- 設定
和l_start
指向檔案的起始位置,并且指定l_whence
,以實作對整個檔案加鎖(一般l_len=0
)l_start=0, l_whence=SEEK_SET
鎖的使用
使用鎖的基本規則:
- 任意多個程序在一個給定的位元組上可以有一把共享的讀鎖(
),但是在一個給定的位元組上隻能有一個程序有一把獨占性寫鎖(F_RDLCK
)F_WRLCK
- 如果在一個給定位元組上已經有一把或多把讀鎖,則不能在該位元組上再加寫鎖,如果在一個位元組上已經有一把獨占性寫鎖,則不能再對它加任何讀鎖
- 對于單個程序而言,如果程序對某個檔案區域已經有了一把鎖,然後又試圖在相同區域再加一把鎖,則新鎖會替換舊鎖
- 加讀鎖時,該描述符必須是讀打開,加寫鎖時,該描述符必須是寫打開
fcntl三種cmd的使用:
-
:判斷由F_GETLK
所描述的鎖是否會被另一把鎖所排斥(阻塞),如果存在一把鎖阻止建立由flockptr
所描述的鎖,由該現有鎖的資訊将重寫flockptr
指向的資訊。如果不存在這種情況,則除了将flockptr
設定為l_type
之處,F_UNLCK
所指向結構中的其他資訊保持不變flockptr
-
:設定由F_SETLK
所描述的鎖,如果程式試圖獲得一把鎖,而系統阻止程式獲得該鎖,則flockptr
會立即傳回錯誤,fcntl
設定為errno
。當EACCES或EAGAIN
時,此指令用來清除指定的鎖l_type=F_UNLCK
-
的阻塞版本(F_SETLKW:F_SETLK
)。如果程式嘗試獲得的鎖無法被授予,調用程序會進入休眠直到程序獲得鎖或者信号中斷wait
注意:用F_GETLK 測試能否建立一把鎖,然後用F_SETLK嘗試建立鎖之間并非原子操作,也就是說兩次調用之間有可能另一程序插入并建立了相同的鎖。如果不希望在等待鎖變為可用時産生阻塞,就必須處理由F_SETLK傳回的可能出錯值
下面是測試一把鎖的例子:
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
l ock.l_len = len;
if (fcntl(fd, F_GETLK, &lock) < ) {
printf("fcntl error: %s\n", strerror(errno));
return ;
}
if (lock.l_type == F_UNLCK) {
return ;
}
return lock.l_pid;
}
鎖的繼承與釋放
鎖的繼承和釋放有以下三條原則:
- 鎖與程序和檔案兩者相關聯。即當一個程序終止時,它所建立的所有鎖均釋放,對于描述符而言,無論它何時關閉,程序通過它引用的檔案上的任何一把鎖也都會釋放
- 由
産生的子程序不繼承父程序所設定的鎖fork
- 執行
後,新程式可以繼承原程式的鎖。注意,如果對一個檔案描述符設定了執行時關閉标志,那麼當作為exec的一部分關閉該檔案描述符時,将釋放相應檔案的所有鎖exec
避免死鎖
如果兩個程序互相等待對方持有并且不釋放的資源時,這兩個程序就會進入死鎖狀态。
如果一個程序已經控制了檔案中的一個加鎖區域,然後它又試圖對另一個程序控制的區域加鎖,那麼它就會進入睡眠,并有可能發生死鎖。
檢測到死鎖時,核心必須選擇一個程序接收錯誤傳回。
總結
在多程序或多線程環境中,當多個應用需要讀寫同一個檔案時,需要考慮對檔案加鎖,以保證對檔案修改的一緻性。
在使用檔案鎖時,應明确應用模式,防止死鎖。
更多關于檔案鎖的使用細節,請參考《UNIX環境進階程式設計》。