分發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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章