NFS请求处理过程

来源:互联网 发布:福莱软件介绍 编辑:程序博客网 时间:2024/05/22 06:53

        前面的文章中我们讲解了一个RPC请求的处理流程。一个RPC请求的基本处理过程包含三个步骤:(1)解析RPC报文头;(2)验证用户身份;(3)处理这个请求。前两个步骤我们已经讲过了,这篇文章详细讲讲第三个步骤的具体处理过程。为了简单起见,这里挑选了一个简单的请求REMOVE。

一个NFS请求的处理过程分成三个步骤
1.解码RPC请求报文净荷中的NFS数据。
2.处理NFS请求。
3.将处理结果组装到RPC应答消息的净荷中。

        前面的文章中我们提到过,一个RPC例程的数据结构是

struct svc_procedure {        // 这是RPC请求的处理函数        svc_procfunc            pc_func;        /* process the request */        // 这是RPC请求的解码函数,RPC报文的内容是pc_func的参数,        // 这个函数负责解析这些内容        kxdrproc_t              pc_decode;      /* XDR decode args */        // 这是RPC请求的编码函数,服务器端需要将pc_func的处理结果封装到        // RPC应答报文中,这就是封装函数        kxdrproc_t              pc_encode;      /* XDR encode result */        // 这是释放内存的一个函数,因为pc_func可能需要分配额外的内存        kxdrproc_t              pc_release;     /* XDR free result */        // 这是RPC请求报文中数据的长度        unsigned int            pc_argsize;     /* argument struct size */        // 这是RPC应答报文中数据的长度        unsigned int            pc_ressize;     /* result struct size */        // 这是这个例程的调用次数,就是一个统计量        unsigned int            pc_count;       /* call count */        // 这是缓存类型,NFS中某些请求可以缓存处理结果。当再次接收到相同的请求后,        // 就不处理了,直接将缓存中的数据返回给客户端就可以了。        unsigned int            pc_cachetype;   /* cache info (NFS) */        // 这是调整RPC应答消息缓存的一个数据量        unsigned int            pc_xdrressize;  /* maximum size of XDR reply */};

对于REMOVE操作来说,这个数据结构如下

.pc_func = (svc_procfunc) nfsd3_proc_remove,.pc_decode = (kxdrproc_t) nfs3svc_decode_diropargs,.pc_encode = (kxdrproc_t) nfs3svc_encode_wccstatres,.pc_release = (kxdrproc_t) nfs3svc_release_fhandle,.pc_argsize = sizeof(struct nfsd3_diropargs),.pc_ressize = sizeof(struct nfsd3_wccstatres),.pc_cachetype = RC_REPLBUFF,.pc_xdrressize = ST+WC,

步骤1:解码REMOVE请求参数

REMOVE请求的参数保存在RPC请求报文的净荷中。根据RFC1813的规定,REMOVE请求包含两个参数:父目录的文件句柄和要删除文件的名称。下一篇文件中我们会详细讲解文件句柄,这里只需要知道服务器端可以根据文件句柄查找到父目录就可以了。REMOVE请求的解码函数如下:

int nfs3svc_decode_diropargs(struct svc_rqst *rqstp, __be32 *p,                                        struct nfsd3_diropargs *args){        if (!(p = decode_fh(p, &args->fh))      // 解码父目录的文件句柄         || !(p = decode_filename(p, &args->name, &args->len)))    // 解码要删除文件的名称                return 0;        return xdr_argsize_check(rqstp, p);    // 这是一个检查函数,检查数据是否超范围了}

首先说明三个参数的含义
qstp表示一条RPC请求消息

p是RPC请求报文的指针,前面已经解析了RPC报文头和认证信息,现在指向了这个RPC请求消息的净荷,也就是REMOVE操作函数的参数。

args是REMOVE操作需要的数据结构,nfs3svc_decode_diropargs的作用就是解析RPC请求报文净荷,用解析出的数据填充args。nfsd3_diropargs的定义如下:

struct nfsd3_diropargs {    struct svc_fh       fh;     // 父目录的句柄    char *          name;       // 目标文件的名称    unsigned int        len;    // 目标文件名称的长度};

父目录文件句柄的解析函数如下:

static __be32 *decode_fh(__be32 *p, struct svc_fh *fhp){        unsigned int size;        fh_init(fhp, NFS3_FHSIZE);        size = ntohl(*p++);         // 先解析父目录文件句柄的长度        if (size > NFS3_FHSIZE)     // 文件句柄长度超出了RFC1813的规定,出错                return NULL;        memcpy(&fhp->fh_handle.fh_base, p, size);       // 解析父目录文件句柄的内容        fhp->fh_handle.fh_size = size;        return p + XDR_QUADLEN(size);       // 返回下一个数据的位置}

目标文件名称解析函数如下:

static __be32 *decode_filename(__be32 *p, char **namp, unsigned int *lenp){        char            *name;        unsigned int    i;        // xdr_decode_string_inplace()是RPC中标准的字符串解析函数        if ((p = xdr_decode_string_inplace(p, namp, lenp, NFS3_MAXNAMLEN)) != NULL) {                for (i = 0, name = *namp; i < *lenp; i++, name++) {                        if (*name == '\0' || *name == '/')                                return NULL;                }        }        return p;}

xdr_decode_string_inplace()是RPC中的函数,按照XDR的规则解析字符串。

步骤2:删除文件

REMOVE请求的处理函数是

static __be32nfsd3_proc_remove(struct svc_rqst *rqstp, struct nfsd3_diropargs *argp,                                          struct nfsd3_attrstat  *resp){        __be32  nfserr;        dprintk("nfsd: REMOVE(3)   %s %.*s\n",                                SVCFH_fmt(&argp->fh),                                argp->len,                                argp->name);        /* Unlink. -S_IFDIR means file must not be a directory */        fh_copy(&resp->fh, &argp->fh);      // 拷贝父目录的文件句柄        nfserr = nfsd_unlink(rqstp, &resp->fh, -S_IFDIR, argp->name, argp->len);        // 删除文件        fh_unlock(&resp->fh);        RETURN_STATUS(nfserr);}

可以看出,实际的处理函数是nfsd_unlink(),这个函数包含5个参数

__be32  nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,  char *fname, int flen)

rqstp 表示这个RPC请求

fhp 是父目录的文件句柄

type 表示目标文件的类型,这里给出的值是-S_IFDIR,表示我们要删除的文件不能是目录。

fname 是目标文件的名称

flen 是目标文件名称的长度

根据RFC1813的规定,NFSV3中删除目录的请求函数是REDIR,REMOVE不处理目录的删除过程,因此这里type取值为-S_IFDIR. 其实Linux中这两个请求的过程是一样的,都是调用函数nfsd_unlink()实现的。

nfsd_unlink()删除文件的过程如下:

(1)根据父目录的文件句柄查找父目录

err = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_REMOVE);dentry = fhp->fh_dentry;    // 父目录的目录项结构dirp = dentry->d_inode;     // 父目录的文件索引节点结构

fhp是父目录的文件句柄,文件句柄具有一定的格式,服务器端可以根据文件句柄的格式查找父目录。这个过程比较复杂,后面的文章中会详细讲解文件句柄的作用以及文件句柄的格式。fh_verify()做了两件事:(1)根据文件句柄查找父目录;(2)检查用户读父目录的访问权限。前面的文章中我们提到过UNIX认证函数只验证了客户端对服务器的访问权限,但是没有具体到文件系统。fh_verify()具体到了文件,检查了用户是否可以删除这个目录中的文件。后面的文章会详细讲解这个函数的流程,这里先跳过。

(2)查找要删除的目标文件

rdentry = lookup_one_len(fname, dentry, flen);

这就是VFS层的通用函数了,没什么好讲的了。

(3)删除文件

if (type != S_IFDIR)        host_err = vfs_unlink(dirp, rdentry);       // 普通文件,删除一个链接else        host_err = vfs_rmdir(dirp, rdentry);        // 目录,直接删除

这也是调用VFS层的函数实现的,也没什么好讲的了。

步骤3:编码REMOVE请求结果

删除文件后服务器端需要将处理结果封装到RPC应答消息的净荷中返还给客户端,根据RFC1813的规定,服务器端需要返回文件删除前后父目录的属性信息,客户端根据这些属性更新本地缓存。Linux中封装REMOVE处理结果的函数是 nfs3svc_encode_wccstat

#define nfs3svc_encode_wccstatres       nfs3svc_encode_wccstat

intnfs3svc_encode_wccstat(struct svc_rqst *rqstp, __be32 *p,                                        struct nfsd3_attrstat *resp){        p = encode_wcc_data(rqstp, p, &resp->fh);        return xdr_ressize_check(rqstp, p);}

xdr_ressize_check是一个缓存区检查函数,因此主要的工作是encode_wcc_data()完成的。

根据RFC1813的规定,REMOVE操作需要返回父目录的下列属性:

删除文件前:

      文件长度(size)

      数据最后修改时间(mtime)

      元数据最后修改时间(ctime)

删除文件后
      文件类型(type)
      访问权限(mode)
      链接数量(nlink)
      所有者(uid)
      所属组(gid)
      文件长度(size)
      文件实际长度(used)  (去除文件空洞)
      设备号(rdev)  (只适用于块设备和字符设备)
      文件系统标识符(fsid)
      文件标识符(fileid)
      最后访问时间(atime)
      数据最后修改时间(mtime)
      元数据最后修改时间(ctime)

但是这些属性不是必须返回的,RPC应答消息净荷中可以编码两个标志位,分别表示应答消息中是否包含了删除前父目录的属性信息和删除后父目录的属性信息,0表示不包含,1表示包含。encode_wcc_data()的处理流程如下:

static __be32 *encode_wcc_data(struct svc_rqst *rqstp, __be32 *p, struct svc_fh *fhp){        struct dentry   *dentry = fhp->fh_dentry;       // 找到文件的目录项结构        if (dentry && dentry->d_inode && fhp->fh_post_saved) {                if (fhp->fh_pre_saved) {                        *p++ = xdr_one;                        p = xdr_encode_hyper(p, (u64) fhp->fh_pre_size);                        p = encode_time3(p, &fhp->fh_pre_mtime);                        p = encode_time3(p, &fhp->fh_pre_ctime);                } else {                        *p++ = xdr_zero;                }                return encode_saved_post_attr(rqstp, p, fhp);        }        /* no pre- or post-attrs */        *p++ = xdr_zero;        return encode_post_op_attr(rqstp, p, fhp);}

参数rqstp表示REMOVE请求

参数p指向了RPC应答消息中的净荷,就是将数据封装到这个缓存区中,然后发送给客户端。

参数fhp是父目录的文件句柄

需要注意的是,这个函数被很多NFS请求调用,如REMOVE、RMDIR、RENAME、LINK。REMOVE请求中没有设置fhp->fh_post_saved,因此只执行了最后两条语句

*p++ = xdr_zero;    // RPC应答消息中不包含删除文件前父目录的属性信息

return encode_post_op_attr(rqstp, p, fhp);    // 组装删除文件后父目录的属性信息

static __be32 *encode_post_op_attr(struct svc_rqst *rqstp, __be32 *p, struct svc_fh *fhp){        struct dentry *dentry = fhp->fh_dentry;         // 取出父目录的目录项结构        if (dentry && dentry->d_inode) {                int err;                struct kstat stat;                // 调用VFS层的函数获取父目录的属性信息                err = vfs_getattr(fhp->fh_export->ex_path.mnt, dentry, &stat);                if (!err) {                        *p++ = xdr_one;         // RPC应答消息中包含删除文件后父目录的属性信息                        // lease_get_mtime是与文件锁相关的一个函数,可以暂时认为这个函数为空.                        lease_get_mtime(dentry->d_inode, &stat.mtime);                        return encode_fattr3(rqstp, p, fhp, &stat);                }        }        *p++ = xdr_zero;        return p;}

encode_post_op_attr包含两个主要的处理函数:

vfs_getattr: 获取父指定文件的属性信息,这里的文件就是父目录。这是VFS层的处理函数了,没什么好说的。

encode_fattr3(): 将vfs_getattr()中获取的属性信息封装到RPC应答消息中。

static __be32 *encode_fattr3(struct svc_rqst *rqstp, __be32 *p, struct svc_fh *fhp,              struct kstat *stat){        *p++ = htonl(nfs3_ftypes[(stat->mode & S_IFMT) >> 12]);     // 编码文件类型        *p++ = htonl((u32) stat->mode);         // 文件访问权限        *p++ = htonl((u32) stat->nlink);        // 文件链接数量        *p++ = htonl((u32) nfsd_ruid(rqstp, stat->uid));    // uid        *p++ = htonl((u32) nfsd_rgid(rqstp, stat->gid));    // gid        if (S_ISLNK(stat->mode) && stat->size > NFS3_MAXPATHLEN) {      // 文件长度                p = xdr_encode_hyper(p, (u64) NFS3_MAXPATHLEN);        } else {                p = xdr_encode_hyper(p, (u64) stat->size);        }        p = xdr_encode_hyper(p, ((u64)stat->blocks) << 9);      // 文件实际长度        *p++ = htonl((u32) MAJOR(stat->rdev));      // 设备文件主、次设备号        *p++ = htonl((u32) MINOR(stat->rdev));        p = encode_fsid(p, fhp);                    // fsid,这和文件系统相关        p = xdr_encode_hyper(p, stat->ino);         // fileid,这和文件相关        p = encode_time3(p, &stat->atime);          // 最后一次访问时间        p = encode_time3(p, &stat->mtime);          // 文件内容最后一次修改的时间        p = encode_time3(p, &stat->ctime);          // 文件属性最后一次修改的时间        return p;}

这就是一些简单的封装函数,但是封装规则是XDR定的,跟NFS没什么关系了,不讲了。

原创粉丝点击