现在可以讲解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);
}