1.客戶端處理程序
這篇文章中我們講講文件鎖的加鎖過程,首先說說與文件鎖相關的數據結構
struct nfs4_lock_state {
// nfs4_state結構中包含多個nfs4_lock_state結構,這些結構構成了一個鏈表,
// ls_locks指向鏈表中相鄰的元素.
struct list_head ls_locks; /* Other lock stateids */
// 這個nfs4_lock_state結構所屬的nfs4_state結構
struct nfs4_state * ls_state; /* Pointer to open state */
// nfs4_lock_state已經初始化完畢了.
#define NFS_LOCK_INITIALIZED 1
int ls_flags;
// 這個結構保證文件鎖操作按順序進行.
struct nfs_seqid_counter ls_seqid;
nfs4_stateid ls_stateid; // 這是服務器返回來的stateid.
atomic_t ls_count; // 這個數據結構的計數.
struct nfs4_lock_owner ls_owner; // 這是這個nfs4_lock_state的名稱.
};
這個數據結構跟LOCK操作相關,Linux中文件鎖跟進程有關,不同進程之間的文件鎖不能發生衝突,同一個進程可以對文件加多個鎖。nfs4_lock_state記錄了一個進程中加的所有文件鎖的信息。如果用戶在同一個進程中調用LOCK多文件加了多個鎖,那麼每次LOCK結果後都會更新這個數據結構中的值,最主要的是這個數據結構中記錄了一個stateid,服務器可以根據這個stateid檢查用戶對文件的訪問權限。
上一遍文章中講過,當客戶端創建文件鎖時會調用nfs4_proc_setlk(),這個函數最終調用了_nfs4_do_setlk(),這個函數也很簡單,就是創建了一個RPC任務,根據參數信息填充了RPC請求報文,然後向服務器發起了LOCK請求。所以就不講解這個函數的處理流程了,我們對比RFC的規定,Linux是如何填充LOCK請求報文的。
static void encode_lock(struct xdr_stream *xdr, const struct nfs_lock_args *args, struct compound_hdr *hdr)
{
__be32 *p;
encode_op_hdr(xdr, OP_LOCK, decode_lock_maxsz, hdr); // 這是LOCK請求編號, 12
p = reserve_space(xdr, 28);
// 這是文件鎖類型 4字節
*p++ = cpu_to_be32(nfs4_lock_type(args->fl, args->block));
// 是否是reclaim文件鎖,當服務器重啓後客戶端需要重新獲取文件鎖信息,稱爲reclaim. 4字節
*p++ = cpu_to_be32(args->reclaim);
// 這是文件鎖在文件中的偏移位置 8字節
p = xdr_encode_hyper(p, args->fl->fl_start);
// 這是文件鎖長度 8字節
p = xdr_encode_hyper(p, nfs4_lock_length(args->fl));
*p = cpu_to_be32(args->new_lock_owner); // 1 4字節
// 如果這是進程中第一個文件鎖
if (args->new_lock_owner){
// 這是OPEN操作的seqid編號
encode_nfs4_seqid(xdr, args->open_seqid); // 這是open --> lock --> unlock -->close 這個過程中的編號
// 這是OPEN操作中的stateid
encode_nfs4_stateid(xdr, args->open_stateid); // 文件的stateid.
// 這是LOCK操作中的seqid編號
encode_nfs4_seqid(xdr, args->lock_seqid);
// 這相當於是爲這個nfs4_lock_state起的名稱
encode_lockowner(xdr, &args->lock_owner); // owner
}
else { // 如果不是進程中第一個文件鎖
// 首先編碼上次文件鎖操作返回的stateid
encode_nfs4_stateid(xdr, args->lock_stateid);
// 這是LOCK操作中的seqid編號
encode_nfs4_seqid(xdr, args->lock_seqid);
}
}
RFC對LOCK請求報文中大部分字段都給出了明確的定義,唯一沒有給出明確定義的是名稱字段,Linux中名字字段構成如下:
static void encode_lockowner(struct xdr_stream *xdr, const struct nfs_lowner *lowner)
{
__be32 *p;
p = reserve_space(xdr, 32);
p = xdr_encode_hyper(p, lowner->clientid); // 8字節 clientid
*p++ = cpu_to_be32(20); // 4字節 20
p = xdr_encode_opaque_fixed(p, "lock id:", 8); // 8字節
*p++ = cpu_to_be32(lowner->s_dev); // 4字節 設備號
xdr_encode_hyper(p, lowner->id); // 8字節
}
名字字段由三部分構成:字符串"lock id:" + 文件系統所在設備號 + 一個唯一的編號id。這個唯一的編號是根據idr機制分配的,額可以保證每個nfs4_lock_state結構中的編號都不同。
2.服務器端的處理函數
首先介紹幾個數據結構
struct nfs4_lockowner {
struct nfs4_stateowner lo_owner; /* must be first element */
// 鏈接到lockowner_ino_hashtbl中
struct list_head lo_owner_ino_hash; /* hash by owner,file */
// 同一個nfs4_openowner->nfs4_ol_stateid結構中所有的nfs4_lockowner構成了一個鏈表
// lo_perstateid指向了鏈表中相鄰的元素.
struct list_head lo_perstateid; /* for lockowners only */
// 這是一個臨時鏈表指針,將nfs4_lockowner結構放到一個臨時鏈表中時使用這個字段
// RELEASE_LOCKOWNER請求中使用了這個字段.
struct list_head lo_list; /* for temporary uses */
};
服務器接收到LOCK請求報文後,執行nfsd4_decode_lock()解析報文內容,解析出的數據保存在數據結構struct nfsd4_lock中,這個數據結構的定義如下:
struct nfsd4_lock {
/* request */
// 文件鎖類型:READ_LT、WRITE_LK、READW_LT、WRITEW_LK.
u32 lk_type;
// 是否是reclaim,文件鎖reclaim過程發生在服務器宕機重啓後.
u32 lk_reclaim; /* boolean */
u64 lk_offset; // 文件鎖在文件中的偏移量
u64 lk_length; // 文件鎖長度
u32 lk_is_new; // 是否是第一個文件鎖.
union {
struct {
u32 open_seqid; // OPEN操作中的seqid
stateid_t open_stateid; // OPEN操作中的stateid
u32 lock_seqid; // LOCK操作中的seqid
clientid_t clientid; // 客戶端的clientid
struct xdr_netobj owner; // 相當於一個名稱
} new; // 第一個文件鎖
struct {
stateid_t lock_stateid; // 上次LOCK操作返回的stateid
u32 lock_seqid; // LOCK操作的seqid
} old; // 不是第一個文件鎖
} v;
/* response */
union {
struct {
// 鎖操作正常,返回stateid
stateid_t stateid;
} ok;
// 鎖發生衝突了,返回衝突鎖的信息.
struct nfsd4_lock_denied denied;
} u;
};
服務器端處理LOCK請求的函數是nfsd4_lock(),這個函數的代碼如下:
__be32
nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfsd4_lock *lock)
{
struct nfs4_openowner *open_sop = NULL;
struct nfs4_lockowner *lock_sop = NULL;
struct nfs4_ol_stateid *lock_stp;
struct file *filp = NULL;
struct file_lock file_lock;
struct file_lock conflock;
__be32 status = 0;
bool new_state = false;
int lkflg;
int err;
struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);
dprintk("NFSD: nfsd4_lock: start=%Ld length=%Ld\n",
(long long) lock->lk_offset,
(long long) lock->lk_length);
// 檢查文件鎖的長度,超出了文件長度.
if (check_lock_length(lock->lk_offset, lock->lk_length))
return nfserr_inval;
// 解析文件句柄,檢查用戶的訪問權限.
if ((status = fh_verify(rqstp, &cstate->current_fh, // 目前的文件句柄
S_IFREG, NFSD_MAY_LOCK))) { // 普通文件,加鎖權限.
dprintk("NFSD: nfsd4_lock: permission denied!\n");
return status; // 沒有權限.
}
nfs4_lock_state();
if (lock->lk_is_new) { // 這是進程中添加的第一個文件鎖
struct nfs4_ol_stateid *open_stp = NULL;
status = nfserr_stale_clientid;
// clientid已經過期了,說明服務器重啓了.
if (STALE_CLIENTID(&lock->lk_new_clientid, nn))
goto out;
/* validate and update open stateid and open seqid */
// 更新open stateid和open seqid
// 這裏查找的是OPEN操作的nfs4_opeowner結構.
status = nfs4_preprocess_confirmed_seqid_op(cstate,
lock->lk_new_open_seqid, // OPEN操作的序列號
&lock->lk_new_open_stateid, // OPEN操作的stateid.
&open_stp);
if (status)
goto out; // 沒有找到
// 找到nfs4_openowner結構.
open_sop = openowner(open_stp->st_stateowner); // 找到nfs4_openowner結構
status = nfserr_bad_stateid;
// 檢查是不是同一個客戶端
if (!same_clid(&open_sop->oo_owner.so_client->cl_clientid,
&lock->v.new.clientid))
goto out;
// 這個函數查找LOCK操作對應的nfs4_ol_stateid結構,如果不存在就創建一個。
// 因爲這是第一個LOCK操作(lock->lk_is_new),因此一定是新創建的.
status = lookup_or_create_lock_state(cstate, open_stp, lock,
&lock_stp, &new_state);
} else
// 查找LOCK操作對應的nfs4_ol_stateid結構
status = nfs4_preprocess_seqid_op(cstate,
lock->lk_old_lock_seqid,
&lock->lk_old_lock_stateid,
NFS4_LOCK_STID, &lock_stp);
if (status)
goto out;
// 取出LOCK操作所屬於的nfs4_lockowner結構.
lock_sop = lockowner(lock_stp->st_stateowner);
// 取出文件鎖類型,返回值是RD_STATE或WR_STATE
lkflg = setlkflg(lock->lk_type);
// 檢查文件的打開權限,比如OPEN操作中以只讀權限打開了一個文件,那麼就不能加寫鎖.
status = nfs4_check_openmode(lock_stp, lkflg);
if (status)
goto out; // 權限錯誤.
status = nfserr_grace;
if (locks_in_grace(SVC_NET(rqstp)) && !lock->lk_reclaim)
goto out;
status = nfserr_no_grace;
if (!locks_in_grace(SVC_NET(rqstp)) && lock->lk_reclaim)
goto out;
locks_init_lock(&file_lock); // 初始化文件鎖結構
// 查找相應權限的文件對象結構.
switch (lock->lk_type) { // 文件鎖類型.
case NFS4_READ_LT:
case NFS4_READW_LT: // 在nfs4_file中查找讀權限的文件對象結構.
filp = find_readable_file(lock_stp->st_file);
if (filp)
get_lock_access(lock_stp, NFS4_SHARE_ACCESS_READ);
file_lock.fl_type = F_RDLCK; // 這是一個讀鎖.
break;
case NFS4_WRITE_LT:
case NFS4_WRITEW_LT: // 在nfs4_file中查找寫權限的文件對象結構.
filp = find_writeable_file(lock_stp->st_file);
if (filp)
get_lock_access(lock_stp, NFS4_SHARE_ACCESS_WRITE);
file_lock.fl_type = F_WRLCK; // 這是一個寫鎖
break;
default:
status = nfserr_inval;
goto out;
}
if (!filp) { // 沒有找到合適的權限.
status = nfserr_openmode;
goto out;
}
// 設置文件鎖的內容.
file_lock.fl_owner = (fl_owner_t)lock_sop; // 文件鎖的所有者
file_lock.fl_pid = current->tgid; // pid
file_lock.fl_file = filp; // 文件對象結構
file_lock.fl_flags = FL_POSIX; // POSIX鎖
file_lock.fl_lmops = &nfsd_posix_mng_ops; // 這是文件鎖關聯的函數
file_lock.fl_start = lock->lk_offset; // 這是文件鎖的開始位置
file_lock.fl_end = last_byte_offset(lock->lk_offset, lock->lk_length); // 這是文件鎖的結束位置
nfs4_transform_lock_offset(&file_lock);
/*
* Try to lock the file in the VFS.
* Note: locks.c uses the BKL to protect the inode's lock list.
*/
// 調用VFS層的函數設置文件鎖.
err = vfs_lock_file(filp, F_SETLK, &file_lock, &conflock);
switch (-err) {
case 0: /* success! */ // 成功了.
// 更新stateid.
update_stateid(&lock_stp->st_stid.sc_stateid); // lock_stp->st_stid.sc_stateid->si_generation++
memcpy(&lock->lk_resp_stateid, &lock_stp->st_stid.sc_stateid,
sizeof(stateid_t)); // 爲組裝應答消息做準備
status = 0;
break;
case (EAGAIN): /* conflock holds conflicting lock */
// 文件鎖發生了衝突.
status = nfserr_denied;
dprintk("NFSD: nfsd4_lock: conflicting lock found!\n");
nfs4_set_lock_denied(&conflock, &lock->lk_denied);
break;
case (EDEADLK):
status = nfserr_deadlock;
break;
default:
dprintk("NFSD: nfsd4_lock: vfs_lock_file() failed! status %d\n",err);
status = nfserrno(err);
break;
}
out:
if (status && new_state)
release_lockowner(lock_sop);
if (!cstate->replay_owner)
nfs4_unlock_state();
return status;
}
講解這個函數之前得再說說服務器端與OPEN和LOCK相關的一些數據結構,但是講解前有兩點需要說明:
(1)NFS是跨操作系統的,爲了便於說明,這裏假設客戶端和服務器端都是Linux系統。
(2)客戶端可以多次將一個NFS文件系統掛載在不同的目錄下,爲了簡便起見,下面的說明只針對一次掛載的情況。
服務器端各個數據結構之間的關係如下圖所示:
(1)conf_id_hashtbl:這是一個全局hash表,這個表中保存的數據結構是nfs4_client
(2)nfs4_client:這是一個客戶端的文件系統,如果NFS文件系統被多個客戶端掛載,則每個客戶端用一個nfs4_client結構表示,這個結構中保存了客戶端的基本信息。
(3)nfs4_openowner:客戶端有多個用戶,每個用戶可以打開多個文件,每個用戶用一個nfs4_openowner結構表示,這個結構中保存了一個用戶的信息。
(4)nfs4_ol_stateid(open):因爲每個用戶可以打開多個文件,所以nfs4_openowner中包含了多個nfs4_ol_stateid結構,每個nfs4_ol_stateid結構表示用戶打開的一個文件。如果用戶多次打開了同一個文件,則服務器端只有一個nfs4_ol_stateid結構。
(5)nfs4_lockowner:因爲文件鎖操作跟進程相關,同一個用戶創建兩個進程,那麼這兩個進程中的文件鎖也不能發生衝突。簡單來說,nfs4_lockowner結構表示用戶創建的一個進程。如果用戶在同一個進程上創建了多個鎖,則服務器端只有以額nfs4_lockowner結構。
(6)nfs4_ol_stateid(lock):目前的實現中,nfs4_ol_stateid和nfs4_lockowner是一一對應的關係,可能是爲了處理方便才分成了兩個數據結構。
現在可以講解LOCK的處理過程了,nfsd4_lock()的流程基本分爲下面幾個步驟:
(1)解析文件句柄,找到對應的文件,這是通過fh_verify()實現的。
(2)查找或者創建一個新的nfs4_ol_stateid(lock)結構。lock->lk_is_new表示這是進程中創建的第一個文件鎖,這種情況下首先需要調用nfs4_preprocess_confirmed_seqid_op()查找所屬於的nfs4_ol_stateid(open)結構,然後調用lookup_or_create_lock_state()創建一個新的nfs4_ol_stateid(lock)結構,當然還包括一個新的nfs4_lockowner結構。如果不是進程中創建的一個文件鎖,則調用nfs4_preprocess_seqid_op()查找以前創建的ns4_ol_stateid(lock)結構。
(3)根據LOCK請求中的數據創建一個文件鎖的數據結構,然後調用VFS層的處理函數vfs_lock_file()設置文件鎖。