OPEN(二)

        這篇文章中我們接着講解服務器端的處理程序,首先介紹幾個與OPEN操作有關的數據結構。第1個數據結構是struct nfs4_openowner,這個數據結構跟客戶端的nfs4_state_owner相對應,保存了一個用戶的信息,這個數據結構的定義如下:

struct nfs4_openowner {
        struct nfs4_stateowner  oo_owner; /* must be first field */
        // 每個nfs4_client結構中包含多個nfs4_openowner結構,這些結構構成了一個鏈表
        // oo_perclient指向鏈表中相鄰的元素.
        struct list_head        oo_perclient;
        /*
         * We keep around openowners a little while after last close,
         * which saves clients from having to confirm, and allows us to
         * handle close replays if they come soon enough.  The close_lru
         * is a list of such openowners, to be reaped by the laundromat
         * thread eventually if they remain unused:
         */
        // 所有空閒未用的nfs4_openowner結構保存在一個全局鏈表(close_lru)中.
        struct list_head        oo_close_lru;
        struct nfs4_ol_stateid *oo_last_closed_stid;    // 最後使用的nfs4_ol_stateid結構
        // 放置到close_lru鏈表中的時間.
        time_t                  oo_time; /* time of placement on so_close_lru */
// 表示這個nfs4_openowner結構已經確認過了,可以使用了.
#define NFS4_OO_CONFIRMED   1           // OPEN_CONFIRM
// 設置這個標誌位後表示下次就需要釋放oo_last_closed_stid了
#define NFS4_OO_PURGE_CLOSE 2
// 這是一個新創建的nfs4_openowner結構,還不能使用.
#define NFS4_OO_NEW         4           // OPEN
        unsigned char           oo_flags;
};
        nfs4_openowner結構中第一個字段的數據類型是nfs4_stateowner,這個結構的定義如下:

struct nfs4_stateowner {
        // 所有的nfs4_stateowner(nfs4_openowner和nfs4_lockowner)保存在一個hash表中(ownerstr_hashtbl)
        struct list_head        so_strhash;   /* hash by op_name */
        // 這是一個鏈表,鏈表中的數據結構是nfs4_ol_stateid,表示了用戶打開的所有文件.
        struct list_head        so_stateids;    // 
        struct nfs4_client *    so_client;      // 這個nfs4_stateowner結構所屬的客戶端
        /* after increment in ENCODE_SEQID_OP_TAIL, represents the next
         * sequence id expected from the client: */
        // 這是一個序號,這個seqid和nfs4_state_owner->so_seqid.counter對應.
        u32                     so_seqid;       // 每次操作都會更新seqid
        // 這表示了一個用戶    open id:<server->s_dev><nfs_seqid_counter->create_time><nfs_seqid_counter->owner_id>
        // so_owner保存了這次OPEN操作的名稱,從OPEN請求報文中解析得到的.
        struct xdr_netobj       so_owner;     /* open owner name */
        struct nfs4_replay      so_replay;
        bool                    so_is_open_owner;       // 這是一個nfs4_opwnowner結構
};
        因此nfs4_stateowner結構中的信息也是nfs4_openowner結構中的信息,那麼爲什麼要把這些信息獨立出來組成一個新的數據結構呢?因爲我們現在只講到了OPEN操作,還沒有講解LOCK操作。服務器端保存LOCK信息的數據結構是nfs4_lockowner,nfs4_openowner和nfs4_lockowner有些相似之處,這些相似的信息提取出來構成了nfs4_stateowner。

        與客戶端中nfs4_state相對應的數據結構是nfs4_ol_stateid,這個數據結構保存了一次OPEN操作的信息,這個數據結構的定義如下:

struct nfs4_ol_stateid {
        // 這裏包含了這個stateid的類型和歸屬的客戶端(nfs4_client)
        struct nfs4_stid    st_stid; /* must be first field */
        // 同一個nfs4_file結構中所有的nfs4_ol_stateid結構構成了一個鏈表。
        // st_perfile指向了鏈表中相鄰的元素.
        struct list_head              st_perfile;
        // 一個nfs4_stateowner結構中包含多個nfs4_ol_stateid結構,st_perstateowner指向鏈表中下一個元素。
        struct list_head              st_perstateowner;
        // 這是一個鏈表,鏈表中的數據結構是nfs4_lockowner.
        struct list_head              st_lockowners;
        // 屬於的nfs4_stateowner結構,有可能是nfs4_openowner結構,也有可能是nfs4_lockowner結構.
        struct nfs4_stateowner      * st_stateowner;
        // 這個nfs4_ol_stateid結構所屬於的nfs4_file
        struct nfs4_file            * st_file;
        // 這是訪問權限標誌位
        unsigned long                 st_access_bmap;   // NONE READ WRITE BOTH
        unsigned long                 st_deny_bmap;     // NOEN READ WRITE BOTH
        // 如果這是一個(nfs4_lockowner-->nfs4_ol_stateid),指向所在的(nfs4_openlock->nfs4_ol_stateid)
        struct nfs4_ol_stateid         * st_openstp;
};


NFS服務器端還定義了一個數據結構nfs4_file,這個數據結構表示一個文件,包含了文件的一些信息,如文件索引節點等。每個文件用一個nfs4_file結構表示,這個數據結構定義如下:

struct nfs4_file {
        atomic_t                fi_ref;         // 這個結構的引用計數
        // fi_hash將這個nfs4_file結構鏈接到file_hashtbl中
        // file_hashtbl是一個全局hash表,保存了系統中所有的nfs4_file結構.
        struct list_head        fi_hash;    /* hash by "struct inode *" */
        // 這個文件上關聯了很多個nfs4_ol_stateid結構,這是鏈表頭.
        struct list_head        fi_stateids;
        // 這個文件上還關聯了很多nfs4_delegation結構,這些delegation共用一個租借鎖
        // 比如多個客戶端以只讀權限打開同一個文件,這是delegation的鏈表頭.
        struct list_head        fi_delegations;
        /* One each for O_RDONLY, O_WRONLY, O_RDWR: */
        // 這個文件可能打開了多次,有多個文件對象結構,保存在這裏.
        struct file *           fi_fds[3];
        /*
         * Each open or lock stateid contributes 0-4 to the counts
         * below depending on which bits are set in st_access_bitmap:
         *     1 to fi_access[O_RDONLY] if NFS4_SHARE_ACCES_READ is set
         *   + 1 to fi_access[O_WRONLY] if NFS4_SHARE_ACCESS_WRITE is set
         *   + 1 to both of the above if NFS4_SHARE_ACCESS_BOTH is set.
         */
        // 這是各種權限的引用計數.
        atomic_t                fi_access[2];
        // 關聯到的文件對象
        struct file             *fi_deleg_file;
        // 這是一個租借鎖,NFSv4用租借鎖機制實現delegation
        // 所有的delegation共用同一個租借鎖
        struct file_lock        *fi_lease;              // 這是一個文件鎖結構
        // 這應該是fi_lease指針的引用數量,這是一個租借鎖
        atomic_t                fi_delegees;
        struct inode            *fi_inode;              // 這是文件的索引節點結構
        // 文件鎖是否發生了衝突,當服務器刪除租借鎖時就會先將fi_had_conflict設置爲true,
        // 這時客戶端就不能申請delegation了.
        bool                    fi_had_conflict;
};



    NFS服務器端負責解析OPEN請求報文的函數是nfsd4_decode_open(),這個函數將解析結果填充在數據結構nfsd4_open中,這個數據結構的定義如下:
struct nfsd4_open {
        // CLAIM_DELEGATE_CUR
        u32             op_claim_type;      /* request */
        struct xdr_netobj op_fname;         /* request - everything but CLAIM_PREV */
        u32             op_delegate_type;   /* request - CLAIM_PREV only */
        // NFS4_OPEN_CLAIM_DELEGATE_CUR中這是客戶端傳過來的delegation的stateid
        stateid_t       op_delegate_stateid; /* request - response */
        u32             op_why_no_deleg;    /* response - DELEG_NONE_EXT only */
        u32             op_create;          /* request */
        u32             op_createmode;      /* request */
        u32             op_bmval[3];        /* request */
        struct iattr    iattr;              /* UNCHECKED4, GUARDED4, EXCLUSIVE4_1 */
        nfs4_verifier   op_verf __attribute__((aligned(32)));
                                            /* EXCLUSIVE4 */
        clientid_t      op_clientid;        /* request */
        struct xdr_netobj op_owner;           /* request */
        u32             op_seqid;           /* request */
        u32             op_share_access;    /* request */
        u32             op_share_deny;      /* request */
        u32             op_deleg_want;      /* request */
        stateid_t       op_stateid;         /* response */
        u32             op_recall;          /* recall */
        struct nfsd4_change_info  op_cinfo; /* response */
        u32             op_rflags;          /* response */
        bool            op_truncate;        /* used during processing */
        bool            op_created;         /* used during processing */
        struct nfs4_openowner *op_openowner; /* used during processing */
        struct nfs4_file *op_file;          /* used during processing */
        struct nfs4_ol_stateid *op_stp;     /* used during processing */
        struct nfs4_acl *op_acl;
};

    由於OPEN操作包含了多種情況,因此這個數據結構比較複雜。我們首先分析最基本的情況:打開一個服務器端存在的文件。與這個情況相關的幾個字段是

struct nfsd4_open {
        // CLAIM類型    CLAIM_NULL
        u32             op_claim_type;      /* request */
        // 這是要打開文件的名稱
        struct xdr_netobj op_fname;         /* request - everything but CLAIM_PREV */
        // 這是客戶端的clientid
        clientid_t      op_clientid;        /* request */
        // 這是owner的名稱,確定了一個nfs4_openowner結構.
        struct xdr_netobj op_owner;           /* request */
        // 這是序列號
        u32             op_seqid;           /* request */
        // 用戶請求的訪問權限
        u32             op_share_access;    /* request */
        // 0x00000000   OPEN4_SHARE_DENY_NONE
        u32             op_share_deny;      /* request */
        // 這是stateid,在應答消息中返回給客戶端
        stateid_t       op_stateid;         /* response */
        // 這個父目錄的信息,在應答消息中返回給客戶端
        struct nfsd4_change_info  op_cinfo; /* response */
        // 這是應答消息中的標誌信息
        u32             op_rflags;          /* response */
        // 這是這次操作所屬用戶的信息
        struct nfs4_openowner *op_openowner; /* used during processing */
        // 關聯了文件信息,如文件索引節點等。
        struct nfs4_file *op_file;          /* used during processing */
        // OPEN請求處理過程中使用的一個字段,查找是否存在stateid相同的nfs4_ol_stateid結構
        struct nfs4_ol_stateid *op_stp;     /* used during processing */
};

    OPEN請求的處理函數是nfsd4_open()

static __be32
nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
           struct nfsd4_open *open)

這個函數根據參數open中的信息創建或者更新服務器端與本次OPEN操作相關的數據結構,打開用戶請求的文件。由於OPEN操作包含多種情況,因此這個函數比較複雜,包含多個case語句。我們先介紹最常見的情況:打開服務器端存在的一個文件,後面的文章會對這個函數進行總結。簡化後這個函數流程如下:

static __be32
nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
           struct nfsd4_open *open)
{
        __be32 status;
        struct nfsd4_compoundres *resp;


        nfs4_lock_state();
        /* check seqid for replay. set nfs4_owner */
        resp = rqstp->rq_resp;          // 這是保存返回值的緩存
        // 這個函數主要在準備nfs4_openowner結構和nfs4_ol_stateid結構
        // nfs4_openowner表示一個用戶 nfs4_ol_stateid表示一次OPEN過程
        status = nfsd4_process_open1(&resp->cstate, open);
        if (status)
                goto out;

        // 打開文件,cstate->current_fh是父目錄的文件句柄.
        status = do_open_lookup(rqstp, &cstate->current_fh, open);
        if (status)
                goto out;

        // 這個函數在設置stateid以及delegation.
        status = nfsd4_process_open2(rqstp, &cstate->current_fh, open);
out:
        nfsd4_cleanup_open_state(open, status); // 前面過程出錯了,進行清理工作.
        if (open->op_openowner)
                cstate->replay_owner = &open->op_openowner->oo_owner;
        else
                nfs4_unlock_state();
        return status;
}

nfs4_openowner表示一個用戶,nfs4_ol_stateid表示一次OPEN過程。如果一個用戶執行了多個OPEN操作,則服務器端會創建多個nfs4_ol_stateid結構,共享同一個nfs4_openowner結構。nfsd4_process_open1()檢查服務器端是否已經爲用戶創建了nfs4_openower結構。如果沒有(第一次執行OPEN操作)就創建一個新的nfs4_openower結構。然後創建一個新的nfs4_ol_stateid結構代表本次OPEN操作。其實並不是每次操作都需要創建一個nfs4_ol_stateid結構,如果用戶先以讀權限打開文件,然後以寫權限再次打開權限,則第OPEN操作過程中並沒有創建nfs4_ol_stateid結構,而是修改了第一次OPEN操作中的nfs4_ol_stateid的信息。

nfsd4_open --> nfsd4_process_open1

__be32
nfsd4_process_open1(struct nfsd4_compound_state *cstate,
                    struct nfsd4_open *open)
{
        // 取出clientid,這代表一個NFS客戶端.
        clientid_t *clientid = &open->op_clientid;      // 取出clientid.
        struct nfs4_client *clp = NULL;
        unsigned int strhashval;
        struct nfs4_openowner *oo = NULL;
        __be32 status;
        struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);

        // 檢查clientid是否已經過期.   open->op_clientid.cl_boot是服務器的啓動時間.
        if (STALE_CLIENTID(&open->op_clientid, nn))     // clientid中包含了服務器端NFS服務的啓動時間
                return nfserr_stale_clientid;           // clientid過期了,說明服務器已經重啓了.
        /*
         * In case we need it later, after we've already created the
         * file and don't want to risk a further failure:
         */
        open->op_file = nfsd4_alloc_file();     // 創建一個新的nfs4_file結構
        if (open->op_file == NULL)
                return nfserr_jukebox;  // 分配內存失敗了

        // 計算用戶的hash值
        // clientid->cl_id表示了一個NFS客戶端   open->op_owner表示客戶端一個用戶.
        strhashval = ownerstr_hashval(clientid->cl_id, &open->op_owner);
        // 查找是否存在符合條件的nfs4_openowner結構,如果不存在就返回NULL
        // 查找條件是兩個 open->op_owner和open->op_clientid,這對應於一個NFS客戶端中的特定的用戶了.
        // 所有nfs4_openowner保存在全局鏈表ownerstr_hashtbl中了.
        oo = find_openstateowner_str(strhashval, open);
        open->op_openowner = oo;        // nfs4_openowner結構
        if (!oo) {              // 不存在
                clp = find_confirmed_client(clientid);          // 找到客戶端
                if (clp == NULL)
                        return nfserr_expired;          // 客戶端不存在
                goto new_owner; // 創建一個新的nfs4_openowner結構
        }
        // 存在符合條件的nfs4_openowner結構,還沒有經過CONFIRM確認.
        // 那我們就釋放掉這個nfs4_openowner,然後創建一個新的nfs4_openowner結構.
        if (!(oo->oo_flags & NFS4_OO_CONFIRMED)) {
                /* Replace unconfirmed owners without checking for replay. */
                clp = oo->oo_owner.so_client;   // 取出客戶端結構
                release_openowner(oo);          // 既然還沒有經過確認,就可以釋放掉。
                open->op_openowner = NULL;
                goto new_owner;
        }
        // 存在符合條件的nfs4_openowner,並且經過確認了. 一個人打開多個文件.  還需要檢查seqid
        // 順序必須一致
        status = nfsd4_check_seqid(cstate, &oo->oo_owner, open->op_seqid);
        if (status)
                return status;
        clp = oo->oo_owner.so_client;
        goto alloc_stateid;
new_owner:
        // 服務器中不存在符合<客戶端、用戶>的nfs4_openowner結構,創建一個新的nfs4_openowner結構
        oo = alloc_init_open_stateowner(strhashval, clp, open);
        if (oo == NULL)
                return nfserr_jukebox;
        open->op_openowner = oo;        // 設置nfs4_openowner結構
alloc_stateid:
        open->op_stp = nfs4_alloc_stateid(clp); // 分配一個新的nfs4_ol_stateid結構  對應客戶端的nfs4_state結構.
        if (!open->op_stp)      // 分配nfs4_ol_stateid失敗.
                return nfserr_jukebox;
        return nfs_ok;
}

查找用戶信息數據結構的函數是find_openstateowner_str()。系統中所有的nfs4_openowner和nfs4_lockowner結構保存在一個全局hash表ownerstr_hashtbl中。find_openstateowner_str()遍歷鏈表中的元素,比較客戶端信息和用戶信息。

nfsd4_open --> nfsd4_process_open1 --> find_openstateowner_str

static struct nfs4_openowner *
find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open)
{
        struct nfs4_stateowner *so;
        struct nfs4_openowner *oo;

        // 這個鏈表中保存的是nfs4_stateowner結構,nfs4_stateowner是nfs4_openowner結構中第一個字段
        // 遍歷鏈表中每一個元素
        list_for_each_entry(so, &ownerstr_hashtbl[hashval], so_strhash) {
                if (!so->so_is_open_owner)      // 這不是nfs4_openowner結構,查找下一個元素.
                        continue;
                // 客戶端相同,用戶相同,這就是符合條件的stateowner結構
                // 這裏只需要比較clientid和owner兩項就可以了
                if (same_owner_str(so, &open->op_owner, &open->op_clientid)) {
                        oo = openowner(so);     // 根據nfs4_stateowner結構找到nfs4_openowner結構
                        renew_client(oo->oo_owner.so_client);   // 檢查、更新這個客戶端的信息
                        return oo;
                }
        }
        return NULL;
}

NFSv4需要保證客戶端的操作序列化。如果查找到了用戶信息的數據結構,還需要檢查序列號是否相同,這是通過函數nfsd4_check_seqid()實現的。這個函數比較簡單,就是將nfs4_openowner結構中保存的序號和OPEN請求報文中傳遞過來的序號進行比較。

nfsd4_open --> nfsd4_process_open1 --> nfsd4_check_seqid

static __be32 nfsd4_check_seqid(struct nfsd4_compound_state *cstate, struct nfs4_stateowner *so, u32 seqid)
{
        if (nfsd4_has_session(cstate))
                return nfs_ok;
        if (seqid == so->so_seqid - 1)
                return nfserr_replay_me;
        if (seqid == so->so_seqid)
                return nfs_ok;
        return nfserr_bad_seqid;
}

如果用戶第一次打開文件,則find_openstateowner_str()的返回值是NULL,需要創建一個新的nfs4_openowner結構,這個函數是alloc_init_open_stateowner()。這個函數也很簡單,創建一個nfs4_openowner結構,然後添加到兩個鏈表中。

nfsd4_open --> nfsd4_process_open1 --> alloc_init_open_stateowner

static struct nfs4_openowner *
alloc_init_open_stateowner(unsigned int strhashval, struct nfs4_client *clp, struct nfsd4_open *open) {
        struct nfs4_openowner *oo;

        // 爲nfs4_openowner結構分配內存
        oo = alloc_stateowner(openowner_slab, &open->op_owner, clp);
        if (!oo)
                return NULL;
        oo->oo_owner.so_is_open_owner = 1;      // 這是一個nfs4_openowner結構
        oo->oo_owner.so_seqid = open->op_seqid;         // 這是一個序號  初始化爲客戶端傳遞過來的序號.
        oo->oo_flags = NFS4_OO_NEW;             // 這是新創建的一個結構
        oo->oo_time = 0;                        // 這個nfs4_opwnowner結構最後的更新時間
        oo->oo_last_closed_stid = NULL;         // oo_last_closed_stid表示這個用戶最後一個使用的nfs4_ol_stateid結構.
        INIT_LIST_HEAD(&oo->oo_close_lru);      // 當nfs4_openowner不再使用後會放入到LRU鏈表close_lru中
        // 將這個nfs4_openowner結構添加到兩個鏈表中:ownerstr_hashtbl和clp->cl_openowners
        hash_openowner(oo, clp, strhashval);
        return oo;
}
hash_openowner()將新創建的nfs4_openowner結構添加到了兩個鏈表中:

ownerstr_hashtbl:這是一個全局hash表,保存了系統中所有的nfs4_stateowner結構。nfs4_stateowner是nfs4_openowner結構和nfs4_lockowner結構中的第一個字段,因此ownerstr_hashtbl保存的是系統中所有的nfs4_openowner和nfs4_lockowner結構。

nfs4_client->cl_openowners:nfs4_client表示一個NFS客戶端,這個客戶端中包含多個用戶,同一個客戶端中的nfs4_openowner結構保存在這個鏈表中了。

創建nfs4_ol_stateid的函數是nfs4_alloc_stateid(),nfsd4_process_open1()沒有檢查是否存在符合條件的nfs4_ol_stateid,而是直接創建了一個新的nfs4_ol_stateid結構。因爲nfsd4_process_open1()只是一個準備函數,nfsd4_process_open2()進行了進一步檢查。如果存在符合條件的nfs4_ol_stateid結構,就會釋放這裏的nfs4_ol_stateid結構了。

同時nfsd4_process_open1()還創建了一個新的nfs4_file結構,創建nfs4_file結構的函數是nfsd4_alloc_file(),這個函數也很簡單,直接分配內存就可以了。

do_open_lookup()的作用是查找用戶請求的文件是否存在,然後組裝出這個文件的文件句柄。不考慮創建新文件的情況,簡化後的代碼如下:

參數current_fh是輸入參數,表示父目錄的文件句柄;同時還是輸出參數,函數結束後保存了目標文件的文件句柄。

參數open是輸入參數,保存了用戶請求文件的名稱。

nfsd4_open --> do_open_lookup

static __be32
do_open_lookup(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_open *open)
{
        struct svc_fh *resfh;
        __be32 status;

        // 爲目標文件申請文件句柄
        resfh = kmalloc(sizeof(struct svc_fh), GFP_KERNEL);
        if (!resfh)
                return nfserr_jukebox;
        fh_init(resfh, NFS4_FHSIZE);    // 初始化文件句柄
        open->op_truncate = 0;

        // 查找並打開指定的文件,然後組裝出了文件句柄.
        // current_fh是父目錄的文件句柄,
        // open->op_fname.data是目標文件的名稱
        // open->op_fname.len表示目標文件名稱的長度
        // resfh是輸出參數,表示目標文件的文件句柄
        status = nfsd_lookup(rqstp, current_fh,
                             open->op_fname.data, open->op_fname.len, resfh); 
        fh_unlock(current_fh);
        if (status)
                goto out;

        if (!open->op_created)
                // 檢查用戶的訪問權限
                status = do_open_permission(rqstp, resfh, open,
                                            NFSD_MAY_NOP);
        // 獲取父目錄的文件屬性,current_fh表示父目錄的文件句柄
        set_change_info(&open->op_cinfo, current_fh);
        fh_dup2(current_fh, resfh);     // 現在current_fh就表示目標文件的文件句柄了
out:
        fh_put(resfh);
        kfree(resfh);
        return status;
}

nfsd4_open()最後一個主要操作函數是nfsd4_process_open2(),nfsd4_process_open1()中只爲nfs4_ol_stateid分配了內存,但是沒有填充內容。nfsd4_process_open2()根據do_open_lookup()的結果填充nfs4_ol_stateid。nfsd4_process_open2()做的第二件事情是檢查是否可以給客戶端分配delegation,先跳過這部分內容,後面講解。這個函數的代碼如下:

nfsd4_open --> nfsd4_process_open2

參數rqstp:表示一次RPC請求

參數current_fh:這是目標文件的文件句柄

參數open:包含了本次OPEN操作中的信息

__be32
nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_open *open)
{
        struct nfsd4_compoundres *resp = rqstp->rq_resp;
        struct nfs4_client *cl = open->op_openowner->oo_owner.so_client;        // 這是NFS客戶端
        struct nfs4_file *fp = NULL;
        struct inode *ino = current_fh->fh_dentry->d_inode;     // 這是文件的索引節點
        struct nfs4_ol_stateid *stp = NULL;
        struct nfs4_delegation *dp = NULL;
        __be32 status;

        /*
         * Lookup file; if found, lookup stateid and check open request,
         * and check for delegations in the process of being recalled.
         * If not found, create the nfs4_file struct
         */
        // nfs4_file結構和一個文件對應.   ino是目標文件的索引節點結構.
        // 系統中所有的nfs4_file結構保存在全局hash表file_hashtbl中了,遍歷這個hash表,如果fp->fi_inode == ino就找到了.
        fp = find_file(ino);            // 檢查是否已經打開了這個文件
        if (fp) {       // 這個文件已經打開了
		// 檢查用戶信息
                if ((status = nfs4_check_open(fp, open, &stp))) // nfs4_ol_stateid
                        goto out;       // 訪問權限發生了衝突.
                // 如果設置了delegation,現在就找到delegation了.
                status = nfs4_check_deleg(cl, fp, open, &dp);   // nfs4_delegation
                if (status)
                        goto out;
        } else {        // 這是第一次打開
                status = nfserr_bad_stateid;
                if (nfsd4_is_deleg_cur(open))
                        goto out;
                status = nfserr_jukebox;
                fp = open->op_file;             // 這是新創建的一個nfs4_file結構,在nfsd4_process_open1()中創建的.
                open->op_file = NULL;           // 這裏也置空了.
                nfsd4_init_file(fp, ino);       // 根據inode初始化fp
        }
        /*
         * OPEN the file, or upgrade an existing OPEN.
         * If truncate fails, the OPEN fails.
         */
        if (stp) {      // 已經找到nfs4_ol_stateid結構了,不需要創建了.
		// 更新stp中的信息
                /* Stateid was found, this is an OPEN upgrade */
                status = nfs4_upgrade_open(rqstp, fp, current_fh, stp, open);
                if (status)
                        goto out;
        } else {        // 沒有找到,需要創建一個新的nfs4_ol_stateid結構.
                // 打開文件,關聯nfs4_file結構和文件對象結構
                status = nfs4_get_vfs_file(rqstp, fp, current_fh, open);
                if (status)
                        goto out;
                status = nfsd4_truncate(rqstp, current_fh, open);
                if (status)
                        goto out;
                stp = open->op_stp;             // 這是nfs4_ol_stateid結構
                open->op_stp = NULL;            // 這裏就置空了.
                // 根據fp和open中的信息初始化stp
                // 一個nfs4_file結構中包含多個nfs4_ol_stateid結構
                init_open_stateid(stp, fp, open);       // 這個時候才設置
        }
        update_stateid(&stp->st_stid.sc_stateid);       // 更新這個stateid
        memcpy(&open->op_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));
        if (nfsd4_has_session(&resp->cstate)) {
                open->op_openowner->oo_flags |= NFS4_OO_CONFIRMED;

                if (open->op_deleg_want & NFS4_SHARE_WANT_NO_DELEG) {
                        open->op_delegate_type = NFS4_OPEN_DELEGATE_NONE_EXT;
                        open->op_why_no_deleg = WND4_NOT_WANTED;
                        goto nodeleg;
                }
        }

        /*
        * Attempt to hand out a delegation. No error return, because the
        * OPEN succeeds even if we fail.
        */
        // 這個函數在處理delegation.   這裏才處理delegation.
        nfs4_open_delegation(SVC_NET(rqstp), current_fh, open, stp);
nodeleg:
        status = nfs_ok;

        dprintk("%s: stateid=" STATEID_FMT "\n", __func__,
                STATEID_VAL(&stp->st_stid.sc_stateid));
out:
        /* 4.1 client trying to upgrade/downgrade delegation? */
        if (open->op_delegate_type == NFS4_OPEN_DELEGATE_NONE && dp &&
            open->op_deleg_want)
                nfsd4_deleg_xgrade_none_ext(open, dp);

        if (fp)
                put_nfs4_file(fp);
        if (status == 0 && open->op_claim_type == NFS4_OPEN_CLAIM_PREVIOUS)
                nfs4_set_claim_prev(open, nfsd4_has_session(&resp->cstate));
        /*
        * To finish the open response, we just need to set the rflags.
        */
        // 下面在設置result flags
        // oo_flags是nfs4_openowner的標誌位信息.
        // 當創建一個新的nfs4_openowner結構時只設置了標誌位NFS4_OO_NEW
        open->op_rflags = NFS4_OPEN_RESULT_LOCKTYPE_POSIX;
        if (!(open->op_openowner->oo_flags & NFS4_OO_CONFIRMED) &&
            !nfsd4_has_session(&resp->cstate))
                open->op_rflags |= NFS4_OPEN_RESULT_CONFIRM;    // 需要執行OPEN_CONFIRM請求後纔可以使用這個文件.

        return status;
}


nfsd4_process_open2()首先檢查服務器端是否已經打開了客戶端請求的文件,比如同一個用戶多次打開一個文件,或者其他用戶已經打開這個文件了。這是通>過函數find_file()實現的,函數代碼如下:

nfsd4_open --> nfsd4_process_open2 --> find_file

參數ino:目標文件的索引節點結構

static struct nfs4_file *find_file(struct inode *ino)
{
        unsigned int hashval = file_hashval(ino);
        struct nfs4_file *fp;

        spin_lock(&recall_lock);
        list_for_each_entry(fp, &file_hashtbl[hashval], fi_hash) {
                if (fp->fi_inode == ino) {
                        get_nfs4_file(fp);
                        spin_unlock(&recall_lock);
                        return fp;
                }
        }
        spin_unlock(&recall_lock);
        return NULL;
}

    NFS服務器端,每個打開的文件用數據結構nfs4_file表示,系統中所有的nfs4_file結構保存在全局hash表file_hashtbl中。find_file()首先計算出文件的

hash值,然後遍歷這個鏈表中的nfs4_file結構。如果索引節點結構相同就表示這個文件已經打開了。如果文件還沒有打開就返回NULL。根據find_file()的結構,下面的程序分爲三種情況:

(1)這是第一次OPEN操作,文件還沒有打開。這種情況下需要打開文件,然後創建一個新的nfs4_file結構,然後根據文件的信息填充這個結構。還需要創建一個新的nfs4_ol_stateid結構代表這次OPEN操作,然後根據文件的信息填充這個結構。

(2)文件已經打開了,但是是其他用戶打開的。這種情況需要更新文件的nfs4_file結構。還需要創建一個新的nfs4_ol_stateid結構代表本次OPEN操作,然後根據文件的信息填充這個結構。

(3)文件已經打開了,並且是本次OPEN操作的用戶打開的。這種情況下需要更新文件的nfs4_file結構,還需要更新用戶的nfs4_ol_stateid結構。

    上面三種情況中新的nfs4_file結構和新的nfs4_ol_stateid結構並不是在nfsd4_process_open2()中創建的,而是在nfsd4_process_open1()中創建的,分別保存在了open->op_file和open->op_stp。

      按照上面三種情況,如果文件已經打開了,需要檢查是本次OPEN操作的用戶打開的,還是其他用戶打開的。這個檢查工作是由函數nfs4_check_open()完>成的,代碼如下:
nfsd4_open --> nfsd4_process_open2 --> nfs4_check_open

參數fp:這是一個文件的數據結構

參數open:包含了本次OPEN操作的信息,特別是保存了本次OPEN操作的用戶信息。

參數stpp:這是一個輸出參數。如果本次OPEN操作的用戶以前打開過這個文件則返回對應的nfs4_ol_stateid指針,否則返回NULL。

static __be32
nfs4_check_open(struct nfs4_file *fp, struct nfsd4_open *open, struct nfs4_ol_stateid **stpp)
{
        struct nfs4_ol_stateid *local;
        struct nfs4_openowner *oo = open->op_openowner;         // 這是用戶的信息.

        // 遍歷nfs4_file結構中每一個nfs4_ol_stateid結構
        list_for_each_entry(local, &fp->fi_stateids, st_perfile) {
                /* ignore lock owners */  // 這不是一個open stateid.
                if (local->st_stateowner->so_is_open_owner == 0)
                        continue;               // 這是一個lock stateid.
                /* remember if we have seen this open owner */
                if (local->st_stateowner == &oo->oo_owner)      // 屬於同一個用戶.
                        *stpp = local;          // 找到了.
                /* check for conflicting share reservations */
                if (!test_share(local, open))
                        return nfserr_share_denied;     // 訪問訪問權限發生了衝突。
        }
        return nfs_ok;
}

    NFS文件系統中多個用戶可以同時打開一個文件,每次打開過程用數據結構nfs4_ol_stateid結構表示。多個用戶還可以同時對一個文件加鎖,每次加鎖過程用數據結構也用數據結構nfs4_ol_stateid表示。這些nfs4_ol_stateid結構構成了一個鏈表,鏈表頭是nfs4_file結構的fi_stateids。nfs4_check_open()遍歷鏈表中每個元素,如果這個nfs4_ol_stateid結構的用戶是本次OPEN操作的用戶,就表示這個用戶以前已經打開過這個文件了。

    初始化一個新的nfs4_file結構的函數是nfsd4_init_file(),這個函數的代碼如下:

nfsd4_open --> nfsd4_process_open2 --> nfsd4_init_file

static void nfsd4_init_file(struct nfs4_file *fp, struct inode *ino)
{
        unsigned int hashval = file_hashval(ino);       // 計算hash值

        atomic_set(&fp->fi_ref, 1);             // 初始化引用計數爲1
        INIT_LIST_HEAD(&fp->fi_hash);
        INIT_LIST_HEAD(&fp->fi_stateids);       // 這是一個鏈表頭,關聯了這個文件上所有的nfs4_ol_stateid結構
        INIT_LIST_HEAD(&fp->fi_delegations);    // 這是一個鏈表頭,關聯了這個文件上所有的nfs4_delegation結構
        fp->fi_inode = igrab(ino);              // 關聯文件索引節點
        fp->fi_had_conflict = false;            // 沒有發生衝突
        fp->fi_lease = NULL;                    // 這是一個租借鎖,Linux中依靠租借鎖實現delegation,所有的delegation共享同一個租借鎖
        memset(fp->fi_fds, 0, sizeof(fp->fi_fds));
        memset(fp->fi_access, 0, sizeof(fp->fi_access));
        spin_lock(&recall_lock);
        list_add(&fp->fi_hash, &file_hashtbl[hashval]); // 添加到全局hash表file_hashtbl中
        spin_unlock(&recall_lock);
}

    更新nfs4_file結構的函數是nfs4_get_vfs_file(),這個函數檢查文件是否以本次OPEN操作請求的權限打開了。如果文件已經打開了,但是訪問權限不匹配,就調用函數nfsd_open()以相應的訪問權限再次打開文件,然後調用nfs4_file_get_access()增加相應權限的計數。

nfsd4_open --> nfsd4_process_open2 --> nfs4_get_vfs_file

static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp,
                struct svc_fh *cur_fh, struct nfsd4_open *open)
{
        __be32 status;
        int oflag = nfs4_access_to_omode(open->op_share_access);
        int access = nfs4_access_to_access(open->op_share_access);

        if (!fp->fi_fds[oflag]) {    // 文件沒有以OPEN操作請求的權限打開
                // 以OPEN操作請求的權限打開文件,獲取文件的文件對象結構。
                status = nfsd_open(rqstp, cur_fh, S_IFREG, access,
                        &fp->fi_fds[oflag]);
                if (status)
                        return status;
        }
        nfs4_file_get_access(fp, oflag);    // 增加相應訪問權限的計數

        return nfs_ok;
}
    再次看看nfs4_file結構。

struct nfs4_file {
        // 這裏保存了三個文件對象結構,fi_fds[0]表示只讀權限的文件對象,fi_fds[1]表示只寫權限的文件對象,fi_fds[2]表示讀寫權限的文件對象。
        struct file *           fi_fds[3];
        // 這是文件訪問權限的引用計數。fi_access[0]表示以讀權限打開這個文件的次數,fi_access[1]表示以寫權限打開這個文件的計數。
        // 如果以只讀權限打開文件則增加fi_access[0]的計數,如果以只寫權限打開文件則增加fi_access[1]的計數,如果以讀寫權限打開文件則同時增加fi_access[0]和fi_access[1]的計數
        atomic_t                fi_access[2];
};

設置一個新的nfs4_ol_stateid結構的函數是init_open_stateid(),這個函數的代碼如下:

nfsd4_open --> nfsd4_process_open2 --> init_open_stateid

static void init_open_stateid(struct nfs4_ol_stateid *stp, struct nfs4_file *fp, struct nfsd4_open *open) {
        // 一個nfs4_openowner結構包含多個nfs4_ol_stateid
        // 取出nfs4_openowner結構
        // 取出本次OPEN操作的用戶信息的數據結構
        struct nfs4_openowner *oo = open->op_openowner;         // 找到本次open對應的nfs4_openowner結構
        // 找到所屬於的客戶端結構
        struct nfs4_client *clp = oo->oo_owner.so_client;

        init_stid(&stp->st_stid, clp, NFS4_OPEN_STID);          // 設置stateid類型和客戶端
        // 這是一個鏈表頭,鏈表中的數據結構是nfs4_lockowner,與文件鎖相關。
        INIT_LIST_HEAD(&stp->st_lockowners);
        // 一個用戶可能打開了多個文件,用多個nfs4_ol_stateid結構表示,這些結構構成了一個鏈表。
        list_add(&stp->st_perstateowner, &oo->oo_owner.so_stateids);
        // 鏈接到nfs4_file結構中
        list_add(&stp->st_perfile, &fp->fi_stateids);
        stp->st_stateowner = &oo->oo_owner;     // 這個nfs4_ol_stateid結構所屬於的nfs4_stateowner結構
        get_nfs4_file(fp);              // 增加引用計數
        stp->st_file = fp;              // 這是文件的nfs4_file結構
        stp->st_access_bmap = 0;
        stp->st_deny_bmap = 0;
        // 設置本次OPEN操作的訪問權限,從OPEN請求報文中獲取
        set_access(open->op_share_access, stp);         // 設置stp->st_access_bmap
        // Linux中這個字段永遠是0.
        set_deny(open->op_share_deny, stp);             // 設置stp->st_deny_bmap
        // 如果這是一個(nfs4_lockowner-->nfs4_ol_stateid),指向所在的(nfs4_openlock->nfs4_ol_stateid)
        stp->st_openstp = NULL;         // 這是文件鎖相關的一個字段,OPEN操作中不需要設置這個字段.
}

更新nfs4_ol_stateid結構的函數是nfs4_upgrade_open(),這個函數的代碼如下:

nfsd4_open --> nfsd4_process_open2 --> nfs4_upgrade_open

static __be32
nfs4_upgrade_open(struct svc_rqst *rqstp, struct nfs4_file *fp, struct svc_fh *cur_fh, struct nfs4_ol_stateid *stp, struct nfsd4_open *open)
{
        u32 op_share_access = open->op_share_access;    // 取出新的訪問權限
        bool new_access;
        __be32 status;

        new_access = !test_access(op_share_access, stp);
        if (new_access) {
                // 這個函數更新了nfs4_file結構中的信息
                status = nfs4_get_vfs_file(rqstp, fp, cur_fh, open);
                if (status)
                        return status;
        }
        status = nfsd4_truncate(rqstp, cur_fh, open);
        if (status) {
                if (new_access) {
                        int oflag = nfs4_access_to_omode(op_share_access);
                        nfs4_file_put_access(fp, oflag);
                }
                return status;
        }
        /* remember the open */
        set_access(op_share_access, stp);       // 這是nfs4_file結構修改後的訪問權限。
        set_deny(open->op_share_deny, stp);

        return nfs_ok;
}
    nfsd4_process_open2()最後還設置了OPEN應答報文中的標誌位,這段代碼如下:

        open->op_rflags = NFS4_OPEN_RESULT_LOCKTYPE_POSIX;
        if (!(open->op_openowner->oo_flags & NFS4_OO_CONFIRMED) &&  
            !nfsd4_has_session(&resp->cstate))
                open->op_rflags |= NFS4_OPEN_RESULT_CONFIRM;
NFS4_OPEN_RESULT_LOCKTYPE_POSIX表示服務器支持POSIX鎖

NFS4_OPEN_RESULT_CONFIRM表示客戶端接收到OPEN應答消息後需要發起OPEN_CONFIRM請求進行確認。這個標誌位的設置是有條件的,並不是每個OPEN請求都需要確認。在NFSv4.0中這個條件是!(open->op_openowner->oo_flags & NFS4_OO_CONFIRMED)。什麼意思呢?open->op_openowner的數據結構是nfs4_openowner,表示客戶端的一個用戶。如果用戶打開了多個文件,則多次OPEN操作共享同一個nfs4_openowner結構,只需要第一次執行OPEN操作時創建nfs4_openowner就可以了。前面介紹過,創建nfs4_openowner結構的函數是alloc_init_open_stateowner(),這個函數只設置了標誌位NFS4_OO_NEW表示這是一個新>的nfs4_openowner結構,因此用戶第一次執行OPEN操作時一定需要執行OPEN_CONFIRM請求,OPEN_CONFIRM請求的處理函數會設置標誌位NFS4_OO_CONFIRMED。因>此只有用戶打開第一個文件時需要執行OPEN_CONFIRM請求。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章