分发delegation

    delegation是NFSv4中保持文件同步的一种机制。当NFS客户端持有delegation时就认为自己和服务器保持同步了。多个客户端可以持有同一个文件的delegation,比如多个客户端以只读方式打开了同一个文件,这种情况下服务器为每个客户端分发一个delegation。这篇文章中我们讲讲delegation的分发过程。服务器端delegation的数据结构如下:

struct nfs4_delegation {
        // 这是一个nfs4_stid结构.
        struct nfs4_stid        dl_stid; /* must be first field */
        // 同一个文件中的delegation构成了一个链表,链表头是nfs4_file->fi_delegations
        // dl_perfile指向了链表中相邻的元素
        struct list_head        dl_perfile;
        // 同一个NFS客户端中的delegation构成了一个链表,链表头是nfs4_client->cl_delegations
        // dl_perclnt指向链表中相邻的元素
        struct list_head        dl_perclnt;
        // 系统中定义了一个LRU链表del_recall_lru,
        // nfsd_break_one_deleg()会将nfs4_delegation挂载到del_recall_lru中.
        struct list_head        dl_recall_lru;  /* delegation recalled */
        // delegation的引用计数
        atomic_t                dl_count;       /* ref count */
        // 这个delegation对应的文件
        struct nfs4_file        *dl_file;
        // 为什么不添加一个字段呢
        // struct nfs4_client   *dl_clnt;
        u32                     dl_type;        // delegation类型,读还是写,目前只支持写.
        // 这是nfs4_delegation的过期时间,当把nfs4_delegation挂载到del_recall_lru中时,设置这个时间.
        time_t                  dl_time;
        struct knfsd_fh         dl_fh;          // 这是delegation对应文件的文件句柄
        int                     dl_retries;     // 这是CALLBACK请求的重试次数
        struct nfsd4_callback   dl_recall;      // 这个结构和CALLBACK过程相关
};
nfs4_stid是nfs4_ol_stateid和nfs4_delegation共同使用的数据结构,这个数据的定义如下:

struct nfs4_stid {
#define NFS4_OPEN_STID 1                // 这是一个OPEN stateid
#define NFS4_LOCK_STID 2                // 这是一个LOCK stateid
#define NFS4_DELEG_STID 4               // 这是一个delegation stateid
/* For an open stateid kept around *only* to process close replays: */
#define NFS4_CLOSED_STID 8
        unsigned char sc_type;          // 这是type
        stateid_t sc_stateid;           // 这是stateid
        struct nfs4_client *sc_client;
};

    delegation是在OPEN请求中分发的,服务器端创建delegation的函数是nfs4_open_delegation(),这个函数的代码如下:

参数net:网络命令空间

参数fh:这是OPEN操作中打开文件的文件句柄

参数open:这里保存了OPEN操作相关的数据

参数stp:表示本次OPEN操作过程

nfsd4_open --> nfsd4_process_open2 --> nfs4_open_delegation

static void
nfs4_open_delegation(struct net *net, struct svc_fh *fh,
                     struct nfsd4_open *open, struct nfs4_ol_stateid *stp)
{
        struct nfs4_delegation *dp;
        // 找到nfs4_openowner结构
        struct nfs4_openowner *oo = container_of(stp->st_stateowner, struct nfs4_openowner, oo_owner);
        int cb_up;
        int status = 0, flag = 0;

        // 检查CALLBACK通道是否正常,因为delegation需要使用CALLBACK通道。
        // oo->oo_owner.so_client的结构类型是nfs4_client,表示一个NFS客户端
        cb_up = nfsd4_cb_channel_good(oo->oo_owner.so_client);
        flag = NFS4_OPEN_DELEGATE_NONE;         // 表示不需要创建delegation.
        open->op_recall = 0;
        switch (open->op_claim_type) {
                case NFS4_OPEN_CLAIM_PREVIOUS:
                        if (!cb_up)
                                open->op_recall = 1;
                        flag = open->op_delegate_type;
                        if (flag == NFS4_OPEN_DELEGATE_NONE)
                                goto out;
                        break;
                case NFS4_OPEN_CLAIM_NULL:      // 正常打开一个文件时执行的是这个分支
                        /* Let's not give out any delegations till everyone's
                         * had the chance to reclaim theirs.... */
                        if (locks_in_grace(net))
                                goto out;
			// 如果CALLBACK通道不连通,或者这是用户第一次执行OPEN操作,
			// 就不能创建delegation了。
                        if (!cb_up || !(oo->oo_flags & NFS4_OO_CONFIRMED))
                                goto out;
			// 根据OPEN请求的访问权限确定delegation的类型
                        if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE)
                                flag = NFS4_OPEN_DELEGATE_WRITE;	// 写类型的delegation
                        else
                                flag = NFS4_OPEN_DELEGATE_READ;		// 读类型的delegation
                        break;
                default:
                        goto out;
        }
	// 创建一个新的delegation.
        dp = alloc_init_deleg(oo->oo_owner.so_client, stp, fh, flag);
        if (dp == NULL)
                goto out_no_deleg;	// 分配内存失败了
        // 设置新创建的delegation了. Linux中采用租借锁实现delegation,这个函数在创建租借锁.
        status = nfs4_set_delegation(dp, flag);         // 设置delegation
        if (status)
                goto out_free;  // 设置delegation的过程可能失败.

	// 需要将delegation的stateid返回给NFS客户端.
        memcpy(&open->op_delegate_stateid, &dp->dl_stid.sc_stateid, sizeof(dp->dl_stid.sc_stateid));

        dprintk("NFSD: delegation stateid=" STATEID_FMT "\n",
                STATEID_VAL(&dp->dl_stid.sc_stateid));
out:
        open->op_delegate_type = flag;
        if (flag == NFS4_OPEN_DELEGATE_NONE) {
                if (open->op_claim_type == NFS4_OPEN_CLAIM_PREVIOUS &&
                    open->op_delegate_type != NFS4_OPEN_DELEGATE_NONE)
                        dprintk("NFSD: WARNING: refusing delegation reclaim\n");

                /* 4.1 client asking for a delegation? */
                if (open->op_deleg_want)
                        nfsd4_open_deleg_none_ext(open, status);
        }
        return;
out_free:
        nfs4_put_delegation(dp);
out_no_deleg:
        flag = NFS4_OPEN_DELEGATE_NONE;
        goto out;
}

delegation的创建过程分为四个步骤:

步骤1:检查反向通道是否连通

    因为delegation需要使用反向通道,所以服务器创建delegation直线需要先检查反向通道是否连通(nfsd4_cb_channel_good),如果反向通道不连通就不能创建delegation了。但是也并不是只要反向通道连通就可以创建delegation。前面的文章讲过,用户第一次执行OPEN操作后需要执行OPEN_CONFIRM进行确认,这种情况下也不能创建delegation。

步骤2:确定delegation类型

    根据OPEN请求中的访问权限确认delegation的类型。目前分为两种类型:NFS4_OPEN_DELEGATE_READ(读权限的delegation)和NFS4_OPEN_DELEGATE_WRITE(写权限的delegation)。

步骤3:创建一个nfs4_delegation结构

    nfs4_delegation是表示delegation的数据结构,每个nfs4_delegation表示一个delegation,创建nfs4_delegation结构的函数是alloc_init_deleg()。

步骤4:设置步骤3中创建的delegation

    Linux中采用租借锁实现delegation,同一个文件中所有的delegation共用同一个租借锁。如果这是第一次打开文件,则创建delegation时需要在文件上创建一个租借锁。当另一个客户端再次打开这个文件时就不需要创建租借锁了,只需要更新相应数据结构的计数就可以了。这是由函数nfs4_set_delegation实现的。


    检查反向通道是否连通的函数是nfsd4_cb_channel_good(),对于NFSv4.0来说,只需要检查是否设置了标志位NFSD4_CB_UP就可以了,这个函数的代码如下:

nfsd4_open --> nfsd4_process_open2 --> nfs4_open_delegation --> nfsd4_cb_channel_good

static bool nfsd4_cb_channel_good(struct nfs4_client *clp)
{
        if (clp->cl_cb_state == NFSD4_CB_UP)
                return true;
        /*
         * In the sessions case, since we don't have to establish a
         * separate connection for callbacks, we assume it's OK
         * until we hear otherwise:
         */
        return clp->cl_minorversion && clp->cl_cb_state == NFSD4_CB_UNKNOWN;
}

创建新的nfs4_delegation结构的函数是alloc_init_deleg(),这个函数的代码如下:

参数clp:这是一个NFS客户端的结构

参数stp:表示本次OPEN过程

参数current_fh:本次OPEN过程打开的文件

参数type:delegation的类型

nfsd4_open --> nfsd4_process_open2 --> nfs4_open_delegation --> alloc_init_deleg

static struct nfs4_delegation *
alloc_init_deleg(struct nfs4_client *clp, struct nfs4_ol_stateid *stp, struct svc_fh *current_fh, u32 type)
{
        struct nfs4_delegation *dp;             // 这是nfs4_delegation结构的指针
        struct nfs4_file *fp = stp->st_file;    // 这个stateid所属于的文件

        dprintk("NFSD alloc_init_deleg\n");
        /*
         * Major work on the lease subsystem (for example, to support
         * calbacks on stat) will be required before we can support
         * write delegations properly.
         */
        // Linux现在只支持读方式的delegation
        if (type != NFS4_OPEN_DELEGATE_READ)
                return NULL;
        // delegation发生了冲突,不能在文件上添加新的delegation了.
        if (fp->fi_had_conflict)
                return NULL;
        // 系统中delegation的数量超出了最大值,不能再创建了.
        if (num_delegations > max_delegations)
                return NULL;
        // 分配一个nfs4_delegation结构
        dp = delegstateid(nfs4_alloc_stid(clp, deleg_slab));
        if (dp == NULL)
                return dp;      // 内存分配失败
        init_stid(&dp->dl_stid, clp, NFS4_DELEG_STID);  // 初始化这个delegation的stateid.
        /*
         * delegation seqid's are never incremented.  The 4.1 special
         * meaning of seqid 0 isn't meaningful, really, but let's avoid
         * 0 anyway just for consistency and use 1:
         */
        // delegation中的seqid不增长,这里只是为了避开0
        dp->dl_stid.sc_stateid.si_generation = 1;
        num_delegations++;      // delegation分配成功,增加系统中delegation的数量.
        INIT_LIST_HEAD(&dp->dl_perfile);        // 同一个文件中的delegation构成了一个链表
        INIT_LIST_HEAD(&dp->dl_perclnt);        // 同一个NFS客户端的delegation构成了一个链表
        // 用于将delegation添加到lru链表del_recall_lru中
        INIT_LIST_HEAD(&dp->dl_recall_lru);     // recall
        get_nfs4_file(fp);      // 增加nfs4_file结构的引用计数
        dp->dl_file = fp;       // 这是delegation执行的文件
        dp->dl_type = type;     // delegation的类型
        fh_copy_shallow(&dp->dl_fh, ¤t_fh->fh_handle);    // 这是文件的句柄
        dp->dl_time = 0;        // 最后更新时间
        atomic_set(&dp->dl_count, 1);           // 初始化引用计数为1
        // 初始化这个工作结构,这个工作的处理函数是nfsd4_do_callback_rpc
        // 当发起RECALL请求时调用这个函数.
        INIT_WORK(&dp->dl_recall.cb_work, nfsd4_do_callback_rpc);
        return dp;
}

关于alloc_init_deleg()有几点需要说明:

1.Linux系统只支持读方式的delegation,不支持写方式的delegation,因为几个用户同时写一个文件不好保持文件同步,因此当检测到delegation的类型是NFS4_OPEN_DELEGATE_READ后,函数直接推出了。

2.创建delegation前alloc_init_deleg检查了文件访问过程是否发生了冲突。如果发生了冲突就不能创建delegation了,检查条件是

        if (fp->fi_had_conflict)
                return NULL;

这是什么意思呢?假设客户端A先以只读方式打开了文件file1.txt,服务器为客户端A创建了delegation。现在客户端B同样以只读方式访问文件file.txt,按理说服务器应该为客户端B创建一个delegation。但是假设客户端C同时以写权限访问file1.txt,这种情况下文件访问过程就发生了冲突,服务器会将nfs4_file结构中的fi_had_conflict设置程true。显然,这种情况下就没必要给客户端B创建delegation了。

3.Linux定义了系统中delegation的最大数量,用max_delegations表示,同时用num_delegations表示系统中已经创建的delegation的最大数量。alloc_init_deleg检查了系统中delegation的数量

        if (num_delegations > max_delegations)
                return NULL;

但是这里有个问题。当num_delegations和max_delegations相等后,再加上新创建的这个delegation就超出了max_delegations的限制,正确的判断条件是

        if (num_delegations >= max_delegations)
                return NULL;

当然这是一个小bug,对系统不会造成任何影响。


设置delegation的函数是,这个函数的代码如下:

参数dp:这是步骤3中新创建的delegation

参数flag:这是delegation的类型

nfsd4_open --> nfsd4_process_open2 --> nfs4_open_delegation --> nfs4_set_delegation

static int nfs4_set_delegation(struct nfs4_delegation *dp, int flag)
{
        struct nfs4_file *fp = dp->dl_file;

        // 如果文件中没有锁结构,表示这是第一次打开文件,需要创建一个新的租借锁.
        if (!fp->fi_lease)
                return nfs4_setlease(dp, flag);

        // 文件不是第一次打开,已经设置租借锁了
        spin_lock(&recall_lock);
        if (fp->fi_had_conflict) {      // 访问方式发生了冲突
                spin_unlock(&recall_lock);
                return -EAGAIN;
        }
        // 由于文件中增加了一个delegation,需要增加租借锁的引用计数
        atomic_inc(&fp->fi_delegees);
        // 将新创建的delegation链接到该文件中.
        // fp->fi_delegations是一个链表,保存了这个文件中所有的delegation.
        list_add(&dp->dl_perfile, &fp->fi_delegations);
        spin_unlock(&recall_lock);
        // 将新创建的delegation链接到所属的客户端中.
        // dp->dl_stid.sc_client->cl_delegations是一个链表,保存了这个客户端中所有的delegation.
        list_add(&dp->dl_perclnt, &dp->dl_stid.sc_client->cl_delegations);
        return 0;
}


如果文件是第一次打开,则需要创建一个租借锁,然后设置delegation,这是由函数nfs4_setlease()实现的。

参数dp:新创建的delegation

参数flag:这是文件的访问权限

nfsd4_open --> nfsd4_process_open2 --> nfs4_open_delegation --> nfs4_set_delegation --> nfs4_setlease

static int nfs4_setlease(struct nfs4_delegation *dp, int flag)
{
        struct nfs4_file *fp = dp->dl_file;     // 取出这个delegation对应的文件
        struct file_lock *fl;           // 这是文件锁的数据结构
        int status;

        // 分配一个文件锁结构,这是一个租借锁
        fl = nfs4_alloc_init_lease(dp, flag);
        if (!fl)
                return -ENOMEM;         // 分配内存过程失败了.
        // 炸找一个只读权限的文件对象结构,因为Linux只支持读权限的delegation.
        fl->fl_file = find_readable_file(fp);   // 这是对应的文件
        // 将这个租借锁添加到NFS客户端中
        list_add(&dp->dl_perclnt, &dp->dl_stid.sc_client->cl_delegations);
        // 设置租借锁,这是VFS层的函数,不讲了.
        status = vfs_setlease(fl->fl_file, fl->fl_type, &fl);
        if (status) {   // 租借锁设置过程出错了.
                list_del_init(&dp->dl_perclnt);
                locks_free_lock(fl);
                return -ENOMEM;
        }
        fp->fi_lease = fl;      // 这是文件关联的租借锁
        fp->fi_deleg_file = fl->fl_file;        // 设置文件租借锁使用的文件对象结构.
        get_file(fp->fi_deleg_file);    // 增加文件对象引用计数
        // 设置文件中delegation的数量,现在文件上只关联了一个delegation.
        atomic_set(&fp->fi_delegees, 1);
        // 将delegation添加到对应的文件中.
        list_add(&dp->dl_perfile, &fp->fi_delegations);
        return 0;
}



发布了70 篇原创文章 · 获赞 4 · 访问量 23万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章