文件鎖是保持文件同步的一種手段,當多個用戶同時操作同一個文件時,文件鎖可以保證數據不發生衝突。NFSv2和NFSv3依靠NLM協議實現文件鎖,NFSv4本身實現了文件鎖,不需要NLM協同工作了。NFS中的文件鎖既可以加在客戶端,也可以加在服務器端。如果客戶端掛載NFS文件系統時使用了選項nolock,表示在客戶端加鎖。這種情況下可以保證同一個客戶端的多個進程訪問同一個文件的過程不發生衝突,但是不同客戶端訪問同一個文件時還可能發生衝突,因爲文件鎖加在了客戶端,其他客戶端不知道這個文件鎖的存在。如果客戶端掛載NFS文件系統時使用了選項lock,表示在服務器端加鎖,這樣所有的客戶端都可以檢查服務器端是否存在文件鎖,因此所有客戶端訪問同一個文件時都不會發生衝突。客戶端加鎖時和其他文件系統的加鎖過程沒有什麼區別,因此我們只講解服務器端加鎖的情況。
1.NFS中文件鎖操作
NFSV4中,服務器端鎖處理函數的入口點是nfs4_proc_lock(),這個函數處理了NFSv4中所有關於文件鎖的操作,函數代碼如下:
參數filp:這是一個文件對象指針,表示對哪個文件進行鎖處理
參數cmd:這是文件鎖操作的方法,取值爲:F_GETLK、F_SETLK、F_SETLKW
參數request:這裏包含了文件鎖信息
static int
nfs4_proc_lock(struct file *filp, int cmd, struct file_lock *request)
{
struct nfs_open_context *ctx;
struct nfs4_state *state;
unsigned long timeout = NFS4_LOCK_MINTIMEOUT;
int status;
/* verify open state */
// ctx是打開文件時設置的數據結構,其中包含了nfs4_state指針.
ctx = nfs_file_open_context(filp); // return filp->private_data
state = ctx->state; // 取出nfs4_state結構
// 檢查文件鎖的起始位置和結束位置是否非法
if (request->fl_start < 0 || request->fl_end < 0)
return -EINVAL;
// 文件鎖包含三種操作:F_GETLK、F_SETLK、F_SETLKW
if (IS_GETLK(cmd)) { // 查詢文件鎖的信息
if (state != NULL)
return nfs4_proc_getlk(state, F_GETLK, request);
return 0;
}
// 檢查操作類型是否合法
if (!(IS_SETLK(cmd) || IS_SETLKW(cmd)))
return -EINVAL;
if (request->fl_type == F_UNLCK) { // 這是解鎖操作
if (state != NULL)
return nfs4_proc_unlck(state, cmd, request); // 刪除文件鎖
return 0;
}
if (state == NULL)
return -ENOLCK;
/*
* Don't rely on the VFS having checked the file open mode,
* since it won't do this for flock() locks.
*/
// 檢查是否有權限創建文件鎖
switch (request->fl_type) {
case F_RDLCK: // 創建一個讀鎖
if (!(filp->f_mode & FMODE_READ)) // 只有讀打開的情況下才能創建讀鎖
return -EBADF;
break;
case F_WRLCK: // 創建一個寫鎖
if (!(filp->f_mode & FMODE_WRITE)) // 只有寫打開的情況下才能創建寫鎖
return -EBADF;
}
do {
status = nfs4_proc_setlk(state, cmd, request); // 創建一個文件鎖
if ((status != -EAGAIN) || IS_SETLK(cmd))
break;
timeout = nfs4_set_lock_task_retry(timeout);
status = -ERESTARTSYS;
if (signalled())
break;
} while(status < 0);
return status;
}
Linux定義了三種文件鎖操作方法:
F_GETLK:查詢指定文件鎖的信息,參數request中包含了待查找文件鎖的信息
F_SETLK:設置文件鎖,包括加鎖和解鎖兩種操作,參數request中包含了鎖信息。加鎖過程中如果和其他的文件鎖發生了衝突,則直接出錯退出。
F_SETLKW:設置文件鎖,包括加鎖和解鎖兩種操作,參數request中包含了鎖信息。加鎖過程中如果和其他的文件鎖發生了衝突,則一直等待直到加鎖成功爲止。
2.文件鎖相關的請求
NFSv4中有四個與文件鎖相關的請求,分別是:
LOCK:給文件加鎖,nfs4_proc_setlk()會發起這個請求
LOCKT:查詢文件鎖的信息,nfs4_proc_getlk()會發起這個請求。
LOCKU:解鎖,nfs4_proc_unlck()會發起這個請求。
RELEASE_LOCKOWNER:釋放文件鎖所有者,nfs4_proc_unlck()會發起這個請求。
2.1LOCK請求報文和應答報文結構
LOCK操作也分爲幾種情況,下面先說說最常見的情況:創建一個新的文件鎖。這種情況下LOCK請求報文應該包含下列數據:
struct LOCK4args {
/* CURRENT_FH: file */
nfs_lock_type4 locktype;
bool reclaim;
offset4 offset;
length4 length;
locker4 locker;
};
locktype表示文件鎖類型,取值如下:
READ_LT:創建一個讀鎖,如果與其他的文件鎖發生了衝突,則馬上退出。
WRITE_LT:創建一個寫鎖,如果與其他的文件鎖發生了衝突,則馬上退出。
READW_LT:創建一個讀鎖,如果與其他的文件鎖發生了衝突,則等待,直到成功加鎖。
WRITEW_LK:創建一個寫鎖,如果與其他的文件鎖發生了衝突,則等待,直到成功加鎖。
reclaim:這是鎖狀態回覆標誌位。如果服務器宕機重啓了,這種情況下客戶端需要恢復以前申請的文件鎖,這時LOCK請求報文中的reclaim設置爲1,其他情況下這個值爲0。
offset:這是文件鎖鎖定的數據段在文件中的起始偏移量。
length:這是文件鎖鎖定的數據端的長度。
locker:這是標識本次LOCK操作的數據結構,如果是第一次加鎖,這個數據結構如下:
struct open_to_lock_owner4 {
seqid4 open_seqid;
stateid4 open_stateid;
seqid4 lock_seqid;
lock_owner4 lock_owner;
};
open_seqid:加鎖前需要先打開文件,因此需要先執行OPEN操作,這是OPEN操作中的seqid順序號。
open_stateid:這是OPEN操作中返回的stateid。
lock_seqid:這是爲LOCK操作準備的順序號
lock_owner:相當於是LOCK操作的名稱,包含下列信息:
struct lock_owner4 {
clientid4 clientid;
opaque owner<NFS4_OPAQUE_LIMIT>;
};
clientid:這是客戶端的clientid。
owner:這是一個字符串,客戶端隨便定義,但是需要保證能夠唯一確定文件鎖的所有者。
如果加鎖成功,LOCK應答報文中包含下列數據:
struct LOCK4resok {
stateid4 lock_stateid;
};
應答報文很簡單,只包含了爲這個文件鎖生成的stateid。
如果與文件中其他的文件鎖發生了衝突,加鎖失敗了,LOCK應答報文包含下列數據:
struct LOCK4denied {
offset4 offset;
length4 length;
nfs_lock_type4 locktype;
lock_owner4 owner;
};
這個數據結構中包含了與我們衝突的文件鎖的信息。
offset:衝突的文件鎖在文件中的偏移量。
length:衝突的文件鎖的長度。
locktye:衝突的文件鎖的長度。
owner:衝突的文件鎖的名稱。
2.2LOCKT請求報文和應答報文結構
LOCKT請求報文包含下列數據:
struct LOCKT4args {
/* CURRENT_FH: file */
nfs_lock_type4 locktype;
offset4 offset;
length4 length;
lock_owner4 owner;
};
locktype:文件鎖類型,取值如下:READ_LT、WRITE_LK、READW_LT、WRITEW_LK。
offset:這是文件鎖鎖定的數據段在文件中的起始偏移量。
length:這是文件鎖鎖定的數據端的長度。
owner:相當於是LOCKT操作的名稱。
如鎖沒有文件鎖跟我們測試的文件鎖發生衝突,LOCKT應答報文只返回錯誤碼:NFS_OK。
如果文件中有文件鎖跟我們測試的文件鎖發生了衝突,則返回發生衝突的文件鎖的信息,應答報文如下:
struct LOCK4denied {
offset4 offset;
length4 length;
nfs_lock_type4 locktype;
lock_owner4 owner;
};
2.3LOCKU請求報文和應答報文結構
LOCKU請求報文包含了用戶請求刪除的文件鎖的信息,數據如下:
struct LOCKU4args {
/* CURRENT_FH: file */
nfs_lock_type4 locktype;
seqid4 seqid;
stateid4 stateid;
offset4 offset;
length4 length;
};
如果服務器正確刪除了這個文件鎖,則返回stateid。否則,什麼都不返回。2.4RELEASE_LOCKOWNER請求報文和應答報文結構
RELEASE_LOCKOWNER請求報文包含下列數據:
struct RELEASE_LOCKOWNER4args {
lock_owner4 lock_owner;
};
RELEASE_LOCKOWNER應答報文只包含服務器返回的錯誤碼。