客户端用户执行完读写操作之后就需要关闭文件了,一般情况下客户端用户调用close(2)关闭文件。NFS文件系统中有两个请求与关闭文件相关:CLOSE和OPEN_DOWNGRADE。
CLOSE:这个请求的作用是释放OPEN请求中申请的stateid。客户端在OPEN操作中向服务器申请了stateid,stateid表示了客户端用户请求的对文件的访问权限。当执行CLOSE请求后,客户端用户就放弃了这种访问权限,就不能继续使用这个stateid读写文件了。如果想再次访问文件,必须再次执行OPEN操作申请访问权限。
OPEN_DOWNGRADE:表示用户释放了部分访问权限,比如用户申请了读权限和写权限,可以通过OPEN_DOWNGRADE放弃部分访问权限。
下面先以几个具体例子说明什么情况下会发起CLOSE请求,什么情况下会发起OPEN_DOWNGRADE请求。
情况1:
fd1 = open(file1, O_RDONLY);
close(fd1);
这是最简单的情况,当执行close(fd1)时会向服务器发起CLOSE请求。
情况2:
fd1 = open(file1, O_RDONLY);
fd2 = open(file1, O_RDWR);
close(fd2);
close(fd1);
执行close(fd2)时会向服务器发起OPEN_DOWNGRADE请求,放弃写权限。执行close(fd1)时会向服务器发起CLOSE请求。
情况3:
fd1 = open(file1, O_RDONLY);
fd2 = open(file1, O_RDWR);
close(fd1);
close(fd2);
执行close(fd1)时不会向服务器发起任何请求,因为fd2仍然需要读写权限,执行close(fd2)时会向服务器发起CLOSE请求。
情况4:
fd1 = open(file1, O_RDONLY);
fd2 = open(file1, O_RDONLY);
close(fd1);
close(fd2);
执行close(fd1)时不会向服务器发起任何请求,因为fd2仍然需要读权限,执行close(fd2)时会向服务器发起CLOSE请求。
总结:如果执行open(2)后用户的访问权限不变,则不会向服务器发起任何请求。如果执行open(2)后用户的访问权限变小了,但是仍有访问权限,则执行open(2)时向服务器发起OPEN_DOWNGRADE请求。如果执行open(2)后用户没有任何访问权限了,则执行open(2)时向服务器发起CLOSE请求。
情况5:
如果用户通过delegation访问文件,则关闭文件时不会执行任何请求。
user1:
fd1 = open(file1, O_RDONLY);
sleep(100);
close(fd1);
user2:
fd2 = open(file1, O_RDONLY);
close(fd2);
在这个例子中,user1先以只读权限打开了文件file1(假设服务器分配了delegation),然后等待了100秒。在user1等待的过程中user2也执行了open()操作,然后马上执行了close()。执行close(fd2)不会发起任何请求,执行close(fd1)时会向服务器发起CLOSE请求。
当用户执行close(2)时,客户端会执行到函数__nfs4_close(),这个函数代码如下:
参数state:这是OPEN操作中创建的nfs4_state结构,包含了申请的stateid.
参数fmode:这是关闭的访问权限。
参数gfp_mask:分配内存的标志位
参数wait:同步操作还是异步操作
static void __nfs4_close(struct nfs4_state *state,
fmode_t fmode, gfp_t gfp_mask, int wait)
{
// 找到nfs4_state_owner结构,这里包含了用户信息.
struct nfs4_state_owner *owner = state->owner;
int call_close = 0;
fmode_t newstate;
atomic_inc(&owner->so_count); // 先增加引用计数,处理完毕后再减少引用计数.
/* Protect against nfs4_find_state() */
spin_lock(&owner->so_lock); // 加锁
// 先减少相应访问权限的计数.
switch (fmode & (FMODE_READ | FMODE_WRITE)) {
case FMODE_READ: // 我们释放的是读权限
state->n_rdonly--;
break;
case FMODE_WRITE: // 我们释放的是写权限
state->n_wronly--;
break;
case FMODE_READ|FMODE_WRITE: // 我们释放的是读写权限
state->n_rdwr--;
}
// newstate表示剩余的访问权限
newstate = FMODE_READ|FMODE_WRITE;
// 读写权限的计数为0了,说明没有以O_RDWR权限打开文件的进程了
if (state->n_rdwr == 0) {
if (state->n_rdonly == 0) { // 以O_RDONLY权限打开文件的进程数量也为0了
newstate &= ~FMODE_READ; // 表示可以释放读权限了
call_close |= test_bit(NFS_O_RDONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
}
if (state->n_wronly == 0) { // 以O_WRONLY权限打开文件的进程数量为0了
newstate &= ~FMODE_WRITE; // 表示可以释放写权限了
call_close |= test_bit(NFS_O_WRONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
}
if (newstate == 0)
clear_bit(NFS_DELEGATED_STATE, &state->flags);
}
// 设置nfs4_state结构中现在的访问权限.
nfs4_state_set_mode_locked(state, newstate);
spin_unlock(&owner->so_lock); // 解锁
// 访问权限没有变化,不需要向服务器发起CLOSE或者OPEN_DOWNGRADE请求
if (!call_close) {
nfs4_put_open_state(state); // 只需减少state引用计数就可以了,当计数减到0时释放内存.
nfs4_put_state_owner(owner); // 减少owner引用计数
} else {
bool roc = pnfs_roc(state->inode);
// 这就需要向服务器发请求了.
nfs4_do_close(state, gfp_mask, wait, roc);
}
}
这个函数理解起来有一定的难度,因为这里包含了多种情况。先说说参数fmode的含义,fmode表示用户执行close(2)时关闭的访问权限,也就是执行open(2)时指定的权限。nfs4_state结构中的n_rdonly、n_wronly、n_rdwr分别表示这个用户以只读权限、只写权限、读写权限打开文件的次数,每执行一次close(2)操作都需要减少相应权限的计数,一般情况下,当n_rdonly和n_rdwr都减到0时就可以向服务器发起请求释放读权限了,当n_wronly和n_rdwr都减到0时就可以向服务器发起请求释放写权限了。下面这段代码就是在计算究竟需要释放哪些权限。
int call_close = 0;
newstate = FMODE_READ|FMODE_WRITE;
if (state->n_rdwr == 0) {
if (state->n_rdonly == 0) {
newstate &= ~FMODE_READ;
call_close |= test_bit(NFS_O_RDONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
}
if (state->n_wronly == 0) {
newstate &= ~FMODE_WRITE;
call_close |= test_bit(NFS_O_WRONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
}
if (newstate == 0)
clear_bit(NFS_DELEGATED_STATE, &state->flags);
}
这里定义了两个变量:newstate和call_close,起作用的是call_close。如果n_rdwr!=0,或者n_rdwr==0 && n_rdonly!=0 &&n_wronly!=0,则call_close就是初始值0,则不向服务器发起任何请求。前面讲过,一般情况下:当n_rdonly==0 && n_rdwr==0时就可以释放读权限,当n_wronly==0 && n_rdwr==0时就可以释放写权限了。有一般情况那就有特殊情况,上段程序中的四个test_bit()函数就是在测试特殊情况,这种情况就是有delegation的情况。假设user1通过OPEN请求以只读权限打开了文件,且服务器分配了一个delegation,user2就可以使用这个delegation以只读权限访问这个文件,而不必向服务器发起OPEN请求了。客户端会分别为user1和user2创建一个nfs4_state结构,并且n_rdonly的计数都是1,但是user1中的nfs4_state结构会设置标志位NFS_O_RDONLY,user2中的nfs4_state结构中不会设置标志位NFS_O_RDONLY。因此在上段代码中,对于user1执行call_close |= test_bit(NFS_O_RDONLY_STATE, &state->flags)后call_close的值就不为0了,就需要向服务器发起CLOSE或者OPEN_DOWNGRADE请求,具体发起哪种请求由后面的程序处理。而对于user2执行后call_close的值仍然为0,就不需要发起请求了。为什么user2不需要发起CLOSE或OPEN_DOWNGRADE请求呢?最直接的解释是user2打开文件时也没有发起OPEN请求,关闭文件时当然也不需要发起CLOSE请求了。
if (!call_close) {
nfs4_put_open_state(state);
nfs4_put_state_owner(owner);
} else {
bool roc = pnfs_roc(state->inode);
nfs4_do_close(state, gfp_mask, wait, roc);
}
CLOSE请求中需要传递两个数据:
(1)seqid:这是一个操作顺序号,保证请求按顺序执行。
(2)stateid:这是客户端指定的stateid,服务器释放这个stateid上关联的所有访问权限。
OPEN_DOWNGRADE请求中需要传递四个数据:
(1)seqid:这是操作顺序号,保证请求按顺序执行。
(2)stateid:这是客户端指定的stateid,就是要释放这个stateid上关联的部分访问权限。
(3)share_access:这是请求执行完毕后,stateid上剩余的访问权限。
(4)share_deny:这是请求执行完毕后,stateid上剩余的禁止访问权限。Linux中这个值永远为0,不用考虑它了。
接下来看看nfs_do_close()的处理过程
int nfs4_do_close(struct nfs4_state *state, gfp_t gfp_mask, int wait, bool roc)
{
....
struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CLOSE],
.rpc_cred = state->owner->so_cred,
};
struct rpc_task_setup task_setup_data = {
.rpc_client = server->client,
.rpc_message = &msg,
.callback_ops = &nfs4_close_ops,
.workqueue = nfsiod_workqueue,
.flags = RPC_TASK_ASYNC,
};
....
}
这个函数逻辑很简单,就是创建了一个RPC任务,设置了请求报文中的数据,然后发起RPC请求,因此就不详细介绍这个函数了,但是需要注意两点:(1)这个函数将请求设置成了CLOSE,没有任何判断条件,就是直接设置程了CLOSE。(2)这个RPC任务中关联了辅助函数nfs4_close_ops。这个RPC任务中的辅助函数如下:
static const struct rpc_call_ops nfs4_close_ops = {
.rpc_call_prepare = nfs4_close_prepare, // 这个函数在向服务器发送请求报文前执行
.rpc_call_done = nfs4_close_done, // 这个函数在解析完应答报文后执行
.rpc_release = nfs4_free_closedata, // 这个函数在解析完应答报文或者处理过程出错时执行
};
我们重点关注发送报文前的准备函数,正是这个函数确定了到底发送CLOSE请求还是OPEN_DOWNGRADE请求。
static void nfs4_close_prepare(struct rpc_task *task, void *data)
{
struct nfs4_closedata *calldata = data;
struct nfs4_state *state = calldata->state; // 取出nfs4_state结构.
int call_close = 0;
dprintk("%s: begin!\n", __func__);
// 步骤1:判断RPC请求是否可以马上执行
if (nfs_wait_on_sequence(calldata->arg.seqid, task) != 0)
return;
// 步骤2:初始化请求为OPEN_DOWNGRADE
task->tk_msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_DOWNGRADE];
// 步骤3:计算需要释放的权限.
if (state->n_rdwr == 0) {
if (state->n_rdonly == 0) {
call_close |= test_bit(NFS_O_RDONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
calldata->arg.fmode &= ~FMODE_READ;
}
if (state->n_wronly == 0) {
call_close |= test_bit(NFS_O_WRONLY_STATE, &state->flags);
call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
calldata->arg.fmode &= ~FMODE_WRITE;
}
}
// 步骤4:确定请求
if (!call_close) { // 权限没有改变,不需要发起请求了.
/* Note: exit _without_ calling nfs4_close_done */
task->tk_action = NULL;
goto out;
}
// n_rdwr、n_rdonly、n_wronly的值都是0了,发起CLOSE请求.
// 否则就发起OPEN_DOWNGRADE请求.
if (calldata->arg.fmode == 0) {
task->tk_msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CLOSE];
....
}
....
rpc_call_start(task);
out:
dprintk("%s: done!\n", __func__);
}
为了清晰起见,我删除了NFSv4.1相关的代码。这个函数的主要作用是确定发起CLOSE请求还是OPEN_DOWNGRADE请求,但是我们看到计算方法和__nfs4_close()的计算方法完全一样,都是计算n_rdonly、n_wronly、n_rdwr三个变量的值,那么同样的代码为什么要写两边呢?问题出在nfs_wait_on_sequence(calldata->arg.seqid, task)。NFSv4中,OPEN、OPEN_CONFIRM、OPEN_DOWNGRADE、CLOSE需要保证序列化。如果想发起一个CLOSE请求,但是发现客户端正在处理一个OPEN请求,则这个CLOSE操作必须等待,直到OPEN请求处理结束后才能处理CLOSE请求,而这时n_rdonly、n_wronly、n_rdwr的值可能由于OPEN操作已经发生了变化,因此真正发送CLOSE/OPEN_DOWNGRADE请求报文前需要再次计算这三个值。
服务器端,CLOSE请求和OPEN_DOWNGRADE请求的处理过程是分开的。如果客户端发起了CLOSE请求,则执行函数nfsd4_close()。如果客户端发起了OPEN_DOWNGRADE则执行函数nfsd4_open_downgrade()。
__be32
nfsd4_open_downgrade(struct svc_rqst *rqstp,
struct nfsd4_compound_state *cstate,
struct nfsd4_open_downgrade *od)
{
__be32 status;
struct nfs4_ol_stateid *stp;
nfs4_lock_state();
// 步骤1:根据stateid查找nfs4_ol_stateid结构
status = nfs4_preprocess_confirmed_seqid_op(cstate, od->od_seqid,
&od->od_stateid, &stp);
if (status)
goto out; // 没有找到.
status = nfserr_inval;
// 步骤2:检查允许访问权限
if (!test_access(od->od_share_access, stp)) {
dprintk("NFSD: access not a subset current bitmap: 0x%lx, input access=%08x\n",
stp->st_access_bmap, od->od_share_access);
goto out;
}
// 步骤3:重新设置访问权限
nfs4_stateid_downgrade(stp, od->od_share_access);
// 步骤4:更新stateid.
update_stateid(&stp->st_stid.sc_stateid);
memcpy(&od->od_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
status = nfs_ok;
out:
if (!cstate->replay_owner)
nfs4_unlock_state();
return status;
}
我去掉了函数中NFSv4.1相关的代码和share_deny相关的代码,不考虑这些内容了,OPEN_DOWNGRADE的处理过程可以分为四个步骤。
步骤1:查找nfs4_ol_stateid结构。
nfs4_ol_stateid结构跟客户端nfs4_state结构对应。这个步骤的主要作用是检查客户端传递过来的stateid是否有效。如果客户端传递过来一个非法的stateid,那服务器就不需要处理了,服务器只处理有效的stateid。如果stateid有效,就查找对应的nfs4_ol_stateid结构。这里简单介绍检查和查找过程就可以了,就不讲解代码了,这个步骤中的检查和查找过程是通过idr机制实现的。简单来说,idr机制是一种身份认证机制,和我们的身份证机制类似。公安部门为全国人们分配一个身份证号,一个身份证号和一个人对应,公安部门可以保证每个人的身份证号不相同,可以根据身份证号查找到对应的人。在我们的代码中,idr机制就相当于公安部门,stateid就相当于身份证号,nfs4_ol_stateid结构就相当于人。nfs4_preprocess_confirmed_seqid_op()根据stateid就可以找到对应的nfs4_ol_stateid结构了。
步骤2:检查访问权限。
这个函数在检查客户端传递过来的share_access字段,share_access中保存的是请求执行完毕后剩余的访问权限。OPEN_DOWNGRADE的作用是释放部分访问权限。如果这个stateid本来只关联了写权限,而share_access是读权限,这就不对了。OPEN_DOWNGRADE不能增加新权限。
步骤3:重新设置访问权限,这是实际的操作函数。
步骤4:更新stateid
stateid中也包含一个顺序号,更新这个顺序号。这个顺序号跟RPC报文中的顺序号seqid是有区别的。客户端可能会打开多个文件,无论对哪个文件进行操作,RPC报文中的顺序号都需要更新。而stateid只关联一个文件,只有操作这个文件时,stateid中的顺序号才需要更新。
下面看看步骤3的代码,这个函数根据请求报文中的share_access字段关闭了其他的访问权限
nfsd4_open_downgrade --> nfs4_stateid_downgrade
static inline void nfs4_stateid_downgrade(struct nfs4_ol_stateid *stp, u32 to_access)
{
// to_access对应请求报文中的share_access字段
switch (to_access) {
case NFS4_SHARE_ACCESS_READ: // 只保留读权限
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_WRITE);
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_BOTH);
break;
case NFS4_SHARE_ACCESS_WRITE: // 只保留写权限
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_READ);
nfs4_stateid_downgrade_bit(stp, NFS4_SHARE_ACCESS_BOTH);
break;
case NFS4_SHARE_ACCESS_BOTH: // 保存读写权限
break;
default:
BUG();
}
}
最终调用了一个处理函数nfs4_stateid_downgrade_bit(),这个函数的作用是关闭相应的访问权限,参数access表示要关闭的访问权限,代码如下:
nfsd4_open_downgrade --> nfs4_stateid_downgrade --> nfs4_stateid_downgrade_bit
static inline void nfs4_stateid_downgrade_bit(struct nfs4_ol_stateid *stp, u32 access)
{
if (!test_access(access, stp)) // 根本没有这种访问权限,也就不需要处理了.
return;
// 关闭相应的文件对象结构
nfs4_file_put_access(stp->st_file, nfs4_access_to_omode(access));
// 清除相应权限标志位
clear_access(access, stp);
}
关闭访问权限包含两个步骤:(1)清除相应访问权限标志位;(2)关闭相应权限的文件对象结构。
nfsd4_open_downgrade --> nfs4_stateid_downgrade --> nfs4_stateid_downgrade_bit --> nfs4_file_put_access
static void nfs4_file_put_access(struct nfs4_file *fp, int oflag)
{
if (oflag == O_RDWR) {
__nfs4_file_put_access(fp, O_RDONLY);
__nfs4_file_put_access(fp, O_WRONLY);
} else
__nfs4_file_put_access(fp, oflag);
}
struct nfs4_ol_stateid {
....
unsigned long st_access_bmap;
....
};
st_access_bpam的类型是unsigned long,在32位系统中长度是4字节,共32bit。但是其实这是一个状态标志位,只使用了其中的3个bit。
bit 0:没有使用
bit 1:表示用户是否具有只读权限,置位表示有只读权限,复位表示没有只读权限
bit 2:表示用户是否具有只写权限,置位表示有只写权限,复位表示没有只写权限
bit 3:表示用户是否具有读写权限,置位表示有读写权限,复位表示没有读写权限
clear_access()的作用就是将相应权限的标志位置0,用户就没有相应的访问权限了。
struct nfs4_file {
....
struct file * fi_fds[3];
atomic_t fi_access[2];
};
fi_fds是一个指针数组,保存的是文件对象结构
fi_fds[0]:保存的是只读权限的文件对象结构
fi_fds[1]:保存的是只写权限的文件对象结构
fi_fds[2]:保存的是读写权限的文件对象结构
fi_access是一个数组,保存的是访问权限的计数
fi_access[0]:保存的是读权限的计数
fi_access[1]:保存的是写权限的计数。
注意fi_access数组中只有两个元素。当用户以只读权限打开文件时,fi_access[0]++。当用户以只写权限打开文件时,fi_access[1]++。当用户以读写权限打开文件时fi_access[0]++,且fi_access[1]++。
__nfs4_file_put_access()的作用就是减少fi_access中相应权限的计数,当计数减到0时释放相应的文件对象结构。
nfsd4_open_downgrade --> nfs4_stateid_downgrade --> nfs4_stateid_downgrade_bit --> nfs4_file_put_access --> __nfs4_file_put_access
static void __nfs4_file_put_access(struct nfs4_file *fp, int oflag)
{
// 计数减少1,如果结果为0返回真,否则返回假.
if (atomic_dec_and_test(&fp->fi_access[oflag])) {
nfs4_file_put_fd(fp, oflag); // 好吧,释放对应的文件对象结构
/*
* It's also safe to get rid of the RDWR open *if*
* we no longer have need of the other kind of access
* or if we already have the other kind of open:
*/
// 判断是否需要释放读写权限的文件对象结构.
if (fp->fi_fds[1-oflag]
|| atomic_read(&fp->fi_access[1 - oflag]) == 0)
nfs4_file_put_fd(fp, O_RDWR);
}
}
函数中oflag的取值只能是O_RDONLY或者O_WRONLYD。我们以O_RDONLY为例进行讲解。函数首先减少fi_access[0]的计数,当计数减到0时表示需要释放读权限,因此调用nfs4_file_put_fd()将fi_fds[0]设置为NULL。同还还需要检查是否需要释放读写权限。那么什么情况下需要释放读写权限呢?两种情况:(1)fi_fds[1]!=NULL,表示还有只写权限的文件对象结构,这种情况下可以保证用户仍能以只写权限访问文件,因此就可以释放读写权限了。(2)fi_access[1]==0,表示写权限计数也已经降到0了,用户以后再也不会通过写权限访问文件了,因此也可以释放读写权限了。
__be32
nfsd4_close(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
struct nfsd4_close *close)
{
__be32 status;
struct nfs4_openowner *oo;
struct nfs4_ol_stateid *stp;
nfs4_lock_state();
// 步骤1: 查找nfs4_ol_stateid结构
status = nfs4_preprocess_seqid_op(cstate, close->cl_seqid,
&close->cl_stateid,
NFS4_OPEN_STID|NFS4_CLOSED_STID,
&stp);
if (status)
goto out;
oo = openowner(stp->st_stateowner);
// 步骤2: 更新stateid
status = nfs_ok;
update_stateid(&stp->st_stid.sc_stateid); // 更新stateid.
memcpy(&close->cl_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
// 步骤3: 撤销stateid
nfsd4_close_open_stateid(stp);
oo->oo_last_closed_stid = stp;
// 步骤4:如果nfs4_openowner中没有nfs4_ol_stateid了,处理.
if (list_empty(&oo->oo_owner.so_stateids)) {
if (cstate->minorversion) {
release_openowner(oo);
cstate->replay_owner = NULL;
} else {
/*
* In the 4.0 case we need to keep the owners around a
* little while to handle CLOSE replay.
*/
move_to_close_lru(oo);
}
}
out:
if (!cstate->replay_owner)
nfs4_unlock_state();
return status;
}
CLOSE和OPEN_DOWNGRADE的处理过程基本相同,最主要的差别是OPEN_DOWNGRADE撤销了部分访问权限,而CLOSE撤销了全部访问权限。CLOSE处理过程基本上分为下面几个步骤:
步骤1:检查stateid,查找对应的nfs4_ol_stateid结构,OPEN_DOWNGRADE也有这个步骤。
步骤2:更新stateid,OPEN_DOWNGRADE也有这个步骤。
步骤3:撤销stateid,这是最主要的操作。
步骤4:如果用户关闭了所有的文件,则这个用户对应的nfs4_openowner结构就可以处理了,,NFSv4.0和NFSv4.1的处理过程不同。NFSv4.1直接释放nfs4_openowner结构了,而NFSV4.0则将nfs4_openowner放入到一个lru链表中了。
因此,nfsd4_close()中最主要的处理函数是nfsd4_close_open_stateid(),其实这个函数很简单,函数代码如下:
nfsd4_close --> nfsd4_close_open_stateid
static void nfsd4_close_open_stateid(struct nfs4_ol_stateid *s)
{
// 这个函数在处理nfs4_ol_stateid结构中的各种信息.
unhash_open_stateid(s);
// 将其设置为NFS4_CLOSE_STID,以后就不会使用它了.
s->st_stid.sc_type = NFS4_CLOSED_STID;
}
nfsd4_close --> nfsd4_close_open_stateid --> unhash_open_stateid
static void unhash_open_stateid(struct nfs4_ol_stateid *stp)
{
// 将stp从各种链表中摘除.
unhash_generic_stateid(stp);
// 这个函数在处理文件锁,如果文件中还有未释放的文件锁,需要先释放这些文件锁.
release_stateid_lockowners(stp);
// 这个函数在清除stp中访问权限标志位,撤销与文件的关联.
close_generic_stateid(stp);
}
nfsd4_close --> nfsd4_close_open_stateid --> unhash_open_stateid --> unhash_generic_stateid
static void unhash_generic_stateid(struct nfs4_ol_stateid *stp)
{
list_del(&stp->st_perfile); // 从nfs4_file结构中删除
list_del(&stp->st_perstateowner); // 从nfs4_stateowner结构中删除
}
nfsd4_close --> nfsd4_close_open_stateid --> unhash_open_stateid --> close_generic_stateid
static void close_generic_stateid(struct nfs4_ol_stateid *stp)
{
release_all_access(stp); // 清除访问权限标志位
put_nfs4_file(stp->st_file); // 这个函数减少nfs4_file结构的计数
stp->st_file = NULL;
}
从上面几个短小的函数我们可以看清楚整个处理过程。就是清除了nfs4_ol_stateid结构中的访问权限标志位st_access_bmap,然后将类型设置为NFS4_CLOSED_STID,撤销了与nfs4_file结构的关联,然后将nfs4_ol_stateid结构从所在的链表中摘除了,就这么简单。但是需要注意的是:这个处理过程中并没有释放nfs4_ol_stateid结构占用的内存,而是用nfs4_openowner结构中的指针oo_last_closed_stid执行了待删除的ns4_ol_stateid结构。为什么这么处理呢?主要是为了处理RPC重传的问题。如果服务器处理完请求后马上删除nfs4_ol_stateid结构,在处理重传问题时比较麻烦,相关说明见RFC3530 8.10.1。那么什么时候可以释放内存呢?当再次接收到该用户发送过来的OPEN、OPEN_CONFIRM、OPEN_DOWNGRADE、CLOSE请求时就可以释放了,释放过程是在组装应答报文的函数是完成的。如果这是用户关闭的最后一个文件,以后再也不会发送请求了,则服务器等待一段时间后也会释放内存。