关闭文件

1.CLOSE和OPEN_DOWNGRADE

    客户端用户执行完读写操作之后就需要关闭文件了,一般情况下客户端用户调用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请求。

2.客户端代码

当用户执行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);
        }

当call_close的值为0时就不需要发起请求了,当call_close的值不为0时就需要调用nfs_do_close()发起请求了。上面代码中的roc是return on close的缩写,这是pNFS中的功能,后面讲到pNFS时再介绍。下面讲讲CLOSE请求和OPEN_DOWNGRADE请求中客户端分别需要向服务器传送哪些数据。

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请求报文前需要再次计算这三个值。

3.服务器端代码

服务器端,CLOSE请求和OPEN_DOWNGRADE请求的处理过程是分开的。如果客户端发起了CLOSE请求,则执行函数nfsd4_close()。如果客户端发起了OPEN_DOWNGRADE则执行函数nfsd4_open_downgrade()。

3.1nfsd4_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);
}

我们看看nfs4_ol_stateid和nfs4_file中相关字段的含义就能够明白了。

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了,用户以后再也不会通过写权限访问文件了,因此也可以释放读写权限了。

3.2nfsd4_close

__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请求时就可以释放了,释放过程是在组装应答报文的函数是完成的。如果这是用户关闭的最后一个文件,以后再也不会发送请求了,则服务器等待一段时间后也会释放内存。
    还有一点,nfsd4_close()中存在一个bug,为了方便处理客户端的CLOSE重传请求,RFC3530建议服务器执行完CLOSE请求后暂时不要释放stateid,而是等待接收到下一个OPEN、OPEN_CONFIRM等请求后再释放stateid,但是目前的处理过程存在问题,这个问题正在协商中。

你可能感兴趣的:(NFS文件系统)