前面几篇文章中我们讲解了OPEN操作中最基本的情况:打开服务器端存在的一个文件,接下来的几篇文章中,我们讨论几种特殊情况。这篇文章中我们先讨论第一种情况:同一个用户连续两次打开同一个文件。
用户user1以只读权限打开了文件file1,未关闭文件的情况下又以只读权限打开了文件file1。第一次执行正常的操作,向服务器发送OPEN请求。那么第二次呢?还需要向服务器发送OPEN请求吗?事实上第二次打开文件时就不向服务器发送OPEN请求了。RPC请求为应用程序预留了几个接口,应用程序在发起RPC请求的过程中可以注册几个处理函数。
struct rpc_call_ops {
void (*rpc_call_prepare)(struct rpc_task *, void *);
void (*rpc_call_done)(struct rpc_task *, void *);
void (*rpc_count_stats)(struct rpc_task *, void *);
void (*rpc_release)(void *);
};
rpc_call_prepare()是发起RPC请求报文前执行的函数
rpc_call_done()是处理完RPC应答报文后执行的函数
rpc_count_stats()是一个统计函数,也是在处理完RPC应答报文后执行
rpc_release()是释放资源的函数,比如释放RPC请求过程中申请的内存,当RPC执行完毕或者失败时都会调用这个函数。
OPEN操作中向RPC注册了几个处理函数,处理函数如下:
static const struct rpc_call_ops nfs4_open_ops = {
.rpc_call_prepare = nfs4_open_prepare,
.rpc_call_done = nfs4_open_done,
.rpc_release = nfs4_open_release,
};
这里我们重点关注nfs4_open_prepare(),这个函数代码如下:
// 发送RPC报文前调用这个函数.
static void nfs4_open_prepare(struct rpc_task *task, void *calldata)
{
struct nfs4_opendata *data = calldata; // 取出nfs4_opendata结构
struct nfs4_state_owner *sp = data->owner; // nfs4_state_owner结构
// NFSv4中,操作需要实现序列化。nfs_wait_on_sequence()判断OPEN操作是否可以马上执行,
// 如果不能马上执行就将OPEN操作放入队列中等待,执行推出就可以了。
if (nfs_wait_on_sequence(data->o_arg.seqid, task) != 0)
return;
/*
* Check if we still need to send an OPEN call, or if we can use
* a delegation instead.
*/
// data->state是nfs4_state结构。客户端调用nfs4_get_open_state()查找符合条件的nfs4_state结构,
// 如果不存在就创建一个新的nfs4_state结构,执行到这里时data->state不为NULL.
if (data->state != NULL) { // 这是一个nfs4_state结构.
struct nfs_delegation *delegation;
// 查看这个文件是否已经打开了.
if (can_open_cached(data->state, data->o_arg.fmode, data->o_arg.open_flags))
goto out_no_action; // 这个文件已经打开了,我们不需要再次执行OPEN操作了.
rcu_read_lock();
// 暂时不考虑delegation.
delegation = rcu_dereference(NFS_I(data->state->inode)->delegation);
if (data->o_arg.claim != NFS4_OPEN_CLAIM_DELEGATE_CUR &&
can_open_delegated(delegation, data->o_arg.fmode))
goto unlock_no_action;
rcu_read_unlock();
}
// 这个进程可能睡眠了一段时间,需要更新一些信息.
data->o_arg.clientid = sp->so_server->nfs_client->cl_clientid;
if (data->o_arg.claim == NFS4_OPEN_CLAIM_PREVIOUS) {
task->tk_msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_NOATTR];
nfs_copy_fh(&data->o_res.fh, data->o_arg.fh);
}
data->timestamp = jiffies; // 更新时间戳
if (nfs4_setup_sequence(data->o_arg.server,
&data->o_arg.seq_args,
&data->o_res.seq_res, task))
return;
rpc_call_start(task); // 第一次打开文件,发起OPEN请求.
return;
unlock_no_action:
rcu_read_unlock();
out_no_action:
task->tk_action = NULL; // 设置RPC中下一步操作为NULL,就不需要发起OPEN请求了.
}
data->state是nfs4_state结构,调用这个函数前客户端通过nfs4_get_open_state()查找符合条件的nfs4_state结构,如果不存在就创建一个新的结构。因此第一次OPEN过程中会创建一个新的nfs4_state结构,第二次OPEN过程中就不需要创建了。if语句中调用了一个函数can_open_cached(),这个函数的作用就是判断是否需要发起OPEN请求。如果这个函数的返回值不为0,代码跳转到out_no_action,设置task->tk_action=NULL,RPC请求直接退出,就不需要发送OPEN请求报文了。先给出结论:第二次OPEN过程中程序就从这里退出了。暂时不考虑delegation,则当can_open_cached()返回值为0时会执行到rpc_call_start(task),然后就发送请求报文了,第一次OPEN操作中就是这样执行的。接下来我们看看函数can_open_cached(),这个函数是如何判断是否需要发起OPEN请求的。
static int can_open_cached(struct nfs4_state *state, fmode_t mode, int open_mode)
{
int ret = 0;
if (open_mode & (O_EXCL|O_TRUNC))
goto out;
switch (mode & (FMODE_READ|FMODE_WRITE)) {
case FMODE_READ:
ret |= test_bit(NFS_O_RDONLY_STATE, &state->flags) != 0
&& state->n_rdonly != 0;
break;
case FMODE_WRITE:
ret |= test_bit(NFS_O_WRONLY_STATE, &state->flags) != 0
&& state->n_wronly != 0;
break;
case FMODE_READ|FMODE_WRITE:
ret |= test_bit(NFS_O_RDWR_STATE, &state->flags) != 0
&& state->n_rdwr != 0;
}
out:
return ret;
}
can_open_cached()查看了客户端是否已经以相同的访问权限打开了文件,比如两次以只读权限打开文件,则第一次OPEN过程会设置标志位NFS_O_RDONLY_STATE,并且增加n_rdonly的计数,因此第二次OPEN过程就从第一个case语句推出了。
我们接着看nfs4_state的设置函数,由于第二次文件打开过程中没有发起OPEN请求,因此data->rpc_done==0,因此执行的函数是nfs4_try_open_cached(data),代码如下:
static struct nfs4_state *nfs4_opendata_to_nfs4_state(struct nfs4_opendata *data)
{
struct inode *inode;
struct nfs4_state *state = NULL;
struct nfs_delegation *delegation;
int ret;
.
if (!data->rpc_done) { // 由于没有发起RPC请求,因此data->rpc_done==0
state = nfs4_try_open_cached(data);
goto out;
}
......
out:
return state;
err_put_inode:
iput(inode);
err:
return ERR_PTR(ret);
}
那么nfs4_try_open_cached()做了什么事情呢?暂时不考虑delegation,这个函数简化后的代码如下:
static struct nfs4_state *nfs4_try_open_cached(struct nfs4_opendata *opendata)
{
struct nfs4_state *state = opendata->state; // 这是使用的nfs4_state结构.
struct nfs_inode *nfsi = NFS_I(state->inode); // 这是文件索引节点
struct nfs_delegation *delegation;
int open_mode = opendata->o_arg.open_flags & (O_EXCL|O_TRUNC);
fmode_t fmode = opendata->o_arg.fmode; // 这是文件访问模式
nfs4_stateid stateid;
int ret = -EAGAIN;
for (;;) {
if (can_open_cached(state, fmode, open_mode)) {
spin_lock(&state->owner->so_lock); // 加锁
if (can_open_cached(state, fmode, open_mode)) {
update_open_stateflags(state, fmode); // 这个函数中增加了相应权限进程的个数.
spin_unlock(&state->owner->so_lock);
goto out_return_state;
}
spin_unlock(&state->owner->so_lock);
}
......
}
out:
return ERR_PTR(ret);
out_return_state:
atomic_inc(&state->count); // 这里增加使用计数了.
return state;
}
因此,nfs4_try_open_cached()直接调用了函数update_open_stateflags(),这个函数的作用是更新nfs4_state结构中的信息,前面的文章中讲解过这个函数了。