天天看点

连续两次打开同一个文件

        前面几篇文章中我们讲解了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结构中的信息,前面的文章中讲解过这个函数了。

继续阅读