現在可以講解delegation的返還過程了。像上一篇文章中講到的那樣,當文件的訪問過程發生衝突時,服務器會向客戶端發送CB_RECALL請求。客戶端接收到CB_RECALL請求後,首先會更新關聯在這個文件上的open stateid,然後就向服務器發起DELEGRETURN請求,將delegation返回給服務器。這篇文章中主要講講delegation的返還過程。
服務器發起CB_RECALL請求時向客戶端傳遞了三個參數:
callback_ident:這是客戶端文件系統的編號,用來區分客戶端掛載的多個NFS文件系統,這是客戶端發起SETCLIENTID時告訴服務器的。
stateid:這是要返還的delegation的stateid
filehandle:這是要返還的delegation的文件句柄。
客戶端接收到CB_RECALL請求後,會根據callback_ident選擇NFS文件系統,然後遍歷這個文件系統中所有已經打開的文件,將文件系統跟filehandle進行比較,如果二者匹配,就說明找到了對應的文件,就是要返還這個文件關聯的delegation。客戶端處理delegation的函數如下:
參數inode:這是客戶端查找到的文件索引節點,就是要返還這個文件上關聯的delegation。
參數delegation:這是文件上關聯的delegation
參數issync:表示同步操作還是異步操作
static int __nfs_inode_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
{
struct nfs_inode *nfsi = NFS_I(inode); // 找到nfs_inode結構
int err;
down_write(&nfsi->rwsem);
// 步驟1 更新文件中關聯的open stateid
err = nfs_delegation_claim_opens(inode, &delegation->stateid);
up_write(&nfsi->rwsem);
if (err)
goto out;
// 步驟2 返回delegation
err = nfs_do_return_delegation(inode, delegation, issync);
out:
return err;
}
這個函數包含兩個步驟:
步驟1:更新文件上關聯的open stateid,確保返還delegation後所有的用戶還可以繼續發起READ請求,上篇文章中已經講解過這個函數了。
步驟2:調用nfs_do_return_delegation(),向服務器發起DELEGRETURN請求,返還delegation,這是這篇文章中需要講解的函數。
static int nfs_do_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
{
int res = 0;
// 將delegation返還給服務器端 這個函數發起了DELEGRETURN請求.
res = nfs4_proc_delegreturn(inode, delegation->cred, &delegation->stateid, issync);
nfs_free_delegation(delegation); // 處理完畢,釋放delegation佔用的內存.
return res;
}
nfs4_proc_delegreturn()的作用是創建一個RPC任務,構建DELEGRETURN請求報文,然後向服務器發送DELEGRETURN請求,這個構成沒什麼好講的,略過了。只需要說明一點:這個RPC任務會發起三個請求PUTFH、GETATTR、DELEGRETURN。PUTFH會將文件句柄傳遞給服務器,DELEGRETURN會將delegation的stateid傳遞給服務器,這就可以了。
接着講服務器端的處理程序。DELEGRETURN請求的處理函數是nfsd4_delegreturn(),當服務器接收到請求報文後就會執行這個函數。
__be32
nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfsd4_delegreturn *dr)
{
struct nfs4_delegation *dp; // 這是一個delegation的數據結構
stateid_t *stateid = &dr->dr_stateid; // 取出請求報文中的stateid
struct nfs4_stid *s;
struct inode *inode;
__be32 status;
// 解析文件句柄,檢查客戶端傳遞過來的是否是一個有效的文件句柄,
// 查找文件句柄對應的文件.
if ((status = fh_verify(rqstp, &cstate->current_fh, S_IFREG, 0)))
return status;
inode = cstate->current_fh.fh_dentry->d_inode; // 取出文件的索引節點結構
nfs4_lock_state();
// 根據stateid查找要刪除的delegation.
status = nfsd4_lookup_stateid(stateid, NFS4_DELEG_STID, &s);
if (status)
goto out;
dp = delegstateid(s);
// 這是一個檢查函數.
status = check_stateid_generation(stateid, &dp->dl_stid.sc_stateid, nfsd4_has_session(cstate));
if (status)
goto out;
unhash_delegation(dp); // 刪除這個delegation.
out:
nfs4_unlock_state();
return status;
}
fh_verify()的作用是解析客戶端傳遞過來的文件句柄,查找對應的文件,然後檢查用戶的訪問權限。對於客戶端來說,文件句柄是透明的,客戶端無法解析也不必要解析文件句柄。但是服務器清晰文件句柄的結構,fh_verify()可以解析文件句柄,檢查客戶端傳遞過來的是否是一個有效句柄,然後找到這個文件句柄對應的文件,設置文件的索引節點結構。
nfsd4_lookup_stateid()的作用是根據客戶端傳遞過來的stateid查找delegation。
nfsd4_delegreturn --> nfsd4_lookup_stateid
static __be32 nfsd4_lookup_stateid(stateid_t *stateid, unsigned char typemask, struct nfs4_stid **s)
{
struct nfs4_client *cl;
struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);
// 這是兩個預留的stateid,不允許使用.
if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))
return nfserr_bad_stateid;
// stateid中包含了clientid,因此可以檢查這個stateid是否已經過期了.
if (STALE_STATEID(stateid, nn)) // stateid過期了
return nfserr_stale_stateid;
// 查找是否存在這個NFS客戶端結構.
cl = find_confirmed_client(&stateid->si_opaque.so_clid);
if (!cl)
return nfserr_expired;
// 查找符合條件的nfs4_stid結構,可能是一個nfs4_delegation結構,也可能是一個nfs4_ol_stateid結構.
*s = find_stateid_by_type(cl, stateid, typemask);
if (!*s)
return nfserr_bad_stateid;
return nfs_ok;
}
stateid由四部分構成:
si_generation:這是一個操作序列號,執行OPEN, OPEN_CONFIRM, OPEN_DOWNGRADE後這個序列號會遞增. 4字節
cl_boot:這是服務器端NFS服務的開啓時間 4字節
cl_id:這是服務器分配給NFS客戶端編號,可以保證客戶端編號不重疊 4字節
so_id:這是服務器創建stateid生成的一個編號,服務器可以保證這個編號不重疊 4字節
cl_boot和cl_id就是客戶端的clientid。STALE_STATEID()將stateid中NFS服務的啓動時間跟服務器中NFS服務時間的啓動時間進行比較,判斷stateid是否過期了。如果兩個時間不相同,就說明服務器已經重啓了,但是客戶端還不知道這個信息,這時就會返回錯誤嗎nfserr_stale_stateid。客戶端接收到這個錯誤碼後就會進行狀態恢復操作。find_confirmed_client()的作用是檢查是否存在這個客戶端,代碼如下:
nfsd4_delegreturn --> nfsd4_lookup_stateid --> find_confirmed_client
static struct nfs4_client *
find_confirmed_client(clientid_t *clid)
{
struct nfs4_client *clp;
unsigned int idhashval = clientid_hashval(clid->cl_id); // 這是這個NFS客戶端的hash值
// 系統中所有的nfs4_client結構保存在全局鏈表conf_id_hashtbl中了.
list_for_each_entry(clp, &conf_id_hashtbl[idhashval], cl_idhash) {
if (same_clid(&clp->cl_clientid, clid)) { // clientid相同,是同一個客戶端.
renew_client(clp);
return clp;
}
}
return NULL;
}
find_stateid_by_type()的作用是根據stateid查找對應的nfs4_stid結構,nfs4_stid是什麼結構呢?nfs4_stid是nfs4_ol_stateid和nfs4_delegation中的一個字段,因此這個函數查找的是nfs4_ol_stateid或者nfs4_delegation結構。
參數cl表示一個NFS客戶端
參數t是要查找的stateid
參數typemask是stateid的類型,取值如下:NFS4_OPEN_STID、NFS4_LOCK_STID、NFS4_DELEG_STID
nfsd4_delegreturn --> nfsd4_lookup_stateid --> find_stateid_by_type
static struct nfs4_stid *find_stateid_by_type(struct nfs4_client *cl, stateid_t *t, char typemask)
{
struct nfs4_stid *s;
s = find_stateid(cl, t);
if (!s)
return NULL;
if (typemask & s->sc_type) // 比較類型
return s;
return NULL;
}
state中包含一個字段so_id,服務器可以保證客戶端中每個stateid中的so_id的值都不相同,這是通過idr機制實現的,find_stateid()就是通過idr機制查找的。查找到nfs4_stid結構後,還需要比較類型是否一致。
查找到delegation後,最後做的一件事情就是刪除delegation,這是通過unhash_delegation()實現的。
nfsd4_delegreturn --> unhash_delegation
static void
unhash_delegation(struct nfs4_delegation *dp)
{
unhash_stid(&dp->dl_stid); // 先釋放so_id
// 同一個客戶端中所有的delegation構成了一個鏈表,從這個鏈表中摘除.
list_del_init(&dp->dl_perclnt);
spin_lock(&recall_lock);
// 同一個文件中所有的delegation構成了一個鏈表,從這個鏈表中摘除.
list_del_init(&dp->dl_perfile);
// 這個delegation還可能掛載在一個lru鏈表中了(del_recall_lru),從這個鏈表中摘除.
list_del_init(&dp->dl_recall_lru);
spin_unlock(&recall_lock);
// 減少delegation所在文件的計數,因爲將delegation掛載到文件中時增加了文件的計數
nfs4_put_deleg_lease(dp->dl_file);
// 減少delegation自身計數,當計數減到0時釋放內存.
nfs4_put_delegation(dp);
}