文件加鎖

1.客戶端處理程序

    這篇文章中我們講講文件鎖的加鎖過程,首先說說與文件鎖相關的數據結構

struct nfs4_lock_state {
        // nfs4_state結構中包含多個nfs4_lock_state結構,這些結構構成了一個鏈表,
        // ls_locks指向鏈表中相鄰的元素.
        struct list_head        ls_locks;       /* Other lock stateids */
        // 這個nfs4_lock_state結構所屬的nfs4_state結構
        struct nfs4_state *     ls_state;       /* Pointer to open state */
// nfs4_lock_state已經初始化完畢了.
#define NFS_LOCK_INITIALIZED 1
        int                     ls_flags;
        // 這個結構保證文件鎖操作按順序進行.
        struct nfs_seqid_counter        ls_seqid;
        nfs4_stateid            ls_stateid;     // 這是服務器返回來的stateid.
        atomic_t                ls_count;       // 這個數據結構的計數.
        struct nfs4_lock_owner  ls_owner;       // 這是這個nfs4_lock_state的名稱.
};

    這個數據結構跟LOCK操作相關,Linux中文件鎖跟進程有關,不同進程之間的文件鎖不能發生衝突,同一個進程可以對文件加多個鎖。nfs4_lock_state記錄了一個進程中加的所有文件鎖的信息。如果用戶在同一個進程中調用LOCK多文件加了多個鎖,那麼每次LOCK結果後都會更新這個數據結構中的值,最主要的是這個數據結構中記錄了一個stateid,服務器可以根據這個stateid檢查用戶對文件的訪問權限。

    上一遍文章中講過,當客戶端創建文件鎖時會調用nfs4_proc_setlk(),這個函數最終調用了_nfs4_do_setlk(),這個函數也很簡單,就是創建了一個RPC任務,根據參數信息填充了RPC請求報文,然後向服務器發起了LOCK請求。所以就不講解這個函數的處理流程了,我們對比RFC的規定,Linux是如何填充LOCK請求報文的。

static void encode_lock(struct xdr_stream *xdr, const struct nfs_lock_args *args, struct compound_hdr *hdr)
{
        __be32 *p;

        encode_op_hdr(xdr, OP_LOCK, decode_lock_maxsz, hdr);            // 這是LOCK請求編號, 12
        p = reserve_space(xdr, 28);
        // 這是文件鎖類型   4字節
        *p++ = cpu_to_be32(nfs4_lock_type(args->fl, args->block));
        // 是否是reclaim文件鎖,當服務器重啓後客戶端需要重新獲取文件鎖信息,稱爲reclaim.   4字節
        *p++ = cpu_to_be32(args->reclaim);
        // 這是文件鎖在文件中的偏移位置   8字節
        p = xdr_encode_hyper(p, args->fl->fl_start);
        // 這是文件鎖長度   8字節
        p = xdr_encode_hyper(p, nfs4_lock_length(args->fl));
        *p = cpu_to_be32(args->new_lock_owner);         // 1    4字節
        // 如果這是進程中第一個文件鎖
        if (args->new_lock_owner){
                // 這是OPEN操作的seqid編號
                encode_nfs4_seqid(xdr, args->open_seqid);       // 這是open --> lock --> unlock -->close  這個過程中的編號
                // 這是OPEN操作中的stateid
                encode_nfs4_stateid(xdr, args->open_stateid);   // 文件的stateid.
                // 這是LOCK操作中的seqid編號
                encode_nfs4_seqid(xdr, args->lock_seqid);
                // 這相當於是爲這個nfs4_lock_state起的名稱
                encode_lockowner(xdr, &args->lock_owner);       // owner
        }
        else {  // 如果不是進程中第一個文件鎖
                // 首先編碼上次文件鎖操作返回的stateid
                encode_nfs4_stateid(xdr, args->lock_stateid);
                // 這是LOCK操作中的seqid編號
                encode_nfs4_seqid(xdr, args->lock_seqid);
        }
}
RFC對LOCK請求報文中大部分字段都給出了明確的定義,唯一沒有給出明確定義的是名稱字段,Linux中名字字段構成如下:

static void encode_lockowner(struct xdr_stream *xdr, const struct nfs_lowner *lowner)
{
        __be32 *p;

        p = reserve_space(xdr, 32);
        p = xdr_encode_hyper(p, lowner->clientid);      // 8字節  clientid
        *p++ = cpu_to_be32(20);                         // 4字節   20
        p = xdr_encode_opaque_fixed(p, "lock id:", 8);  // 8字節
        *p++ = cpu_to_be32(lowner->s_dev);              // 4字節   設備號
        xdr_encode_hyper(p, lowner->id);                // 8字節
}
名字字段由三部分構成:字符串"lock id:" + 文件系統所在設備號 + 一個唯一的編號id。這個唯一的編號是根據idr機制分配的,額可以保證每個nfs4_lock_state結構中的編號都不同。


2.服務器端的處理函數

首先介紹幾個數據結構

struct nfs4_lockowner {
        struct nfs4_stateowner  lo_owner; /* must be first element */
        // 鏈接到lockowner_ino_hashtbl中
        struct list_head        lo_owner_ino_hash; /* hash by owner,file */
        // 同一個nfs4_openowner->nfs4_ol_stateid結構中所有的nfs4_lockowner構成了一個鏈表
        // lo_perstateid指向了鏈表中相鄰的元素.
        struct list_head        lo_perstateid; /* for lockowners only */
        // 這是一個臨時鏈表指針,將nfs4_lockowner結構放到一個臨時鏈表中時使用這個字段
        // RELEASE_LOCKOWNER請求中使用了這個字段.
        struct list_head        lo_list; /* for temporary uses */
};

服務器接收到LOCK請求報文後,執行nfsd4_decode_lock()解析報文內容,解析出的數據保存在數據結構struct nfsd4_lock中,這個數據結構的定義如下:

struct nfsd4_lock {
        /* request */
        // 文件鎖類型:READ_LT、WRITE_LK、READW_LT、WRITEW_LK.
        u32             lk_type;
        // 是否是reclaim,文件鎖reclaim過程發生在服務器宕機重啓後.
        u32             lk_reclaim;         /* boolean */
        u64             lk_offset;      // 文件鎖在文件中的偏移量
        u64             lk_length;      // 文件鎖長度
        u32             lk_is_new;      // 是否是第一個文件鎖.
        union {
                struct {
                        u32             open_seqid;     // OPEN操作中的seqid
                        stateid_t       open_stateid;   // OPEN操作中的stateid
                        u32             lock_seqid;     // LOCK操作中的seqid
                        clientid_t      clientid;       // 客戶端的clientid
                        struct xdr_netobj owner;        // 相當於一個名稱
                } new;  // 第一個文件鎖
                struct {
                        stateid_t       lock_stateid;   // 上次LOCK操作返回的stateid
                        u32             lock_seqid;     // LOCK操作的seqid
                } old;  // 不是第一個文件鎖
        } v;

        /* response */
        union {
                struct {
                        // 鎖操作正常,返回stateid
                        stateid_t               stateid;
                } ok;
                // 鎖發生衝突了,返回衝突鎖的信息.
                struct nfsd4_lock_denied        denied;
        } u;
};

服務器端處理LOCK請求的函數是nfsd4_lock(),這個函數的代碼如下:

__be32
nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
           struct nfsd4_lock *lock)
{
        struct nfs4_openowner *open_sop = NULL;
        struct nfs4_lockowner *lock_sop = NULL;
        struct nfs4_ol_stateid *lock_stp;
        struct file *filp = NULL;
        struct file_lock file_lock;
        struct file_lock conflock;
        __be32 status = 0;
        bool new_state = false;
        int lkflg;
        int err;
        struct nfsd_net *nn = net_generic(&init_net, nfsd_net_id);

        dprintk("NFSD: nfsd4_lock: start=%Ld length=%Ld\n",
                (long long) lock->lk_offset,
                (long long) lock->lk_length);

        // 檢查文件鎖的長度,超出了文件長度.
        if (check_lock_length(lock->lk_offset, lock->lk_length))
                 return nfserr_inval;

        // 解析文件句柄,檢查用戶的訪問權限.
        if ((status = fh_verify(rqstp, &cstate->current_fh,     // 目前的文件句柄
                                S_IFREG, NFSD_MAY_LOCK))) {     // 普通文件,加鎖權限.
                dprintk("NFSD: nfsd4_lock: permission denied!\n");
                return status;  // 沒有權限.
        }

        nfs4_lock_state();
        if (lock->lk_is_new) {          // 這是進程中添加的第一個文件鎖
                struct nfs4_ol_stateid *open_stp = NULL;

                status = nfserr_stale_clientid;
                // clientid已經過期了,說明服務器重啓了.
                if (STALE_CLIENTID(&lock->lk_new_clientid, nn))
                        goto out;

                /* validate and update open stateid and open seqid */
                // 更新open stateid和open seqid
                // 這裏查找的是OPEN操作的nfs4_opeowner結構.
                status = nfs4_preprocess_confirmed_seqid_op(cstate,
                                        lock->lk_new_open_seqid,        // OPEN操作的序列號
                                        &lock->lk_new_open_stateid,     // OPEN操作的stateid.
                                        &open_stp);
                if (status)
                        goto out;       // 沒有找到
                // 找到nfs4_openowner結構.
                open_sop = openowner(open_stp->st_stateowner);          // 找到nfs4_openowner結構
                status = nfserr_bad_stateid;
                // 檢查是不是同一個客戶端
                if (!same_clid(&open_sop->oo_owner.so_client->cl_clientid,
                                                &lock->v.new.clientid))
                        goto out;
		// 這個函數查找LOCK操作對應的nfs4_ol_stateid結構,如果不存在就創建一個。
		// 因爲這是第一個LOCK操作(lock->lk_is_new),因此一定是新創建的.
                status = lookup_or_create_lock_state(cstate, open_stp, lock,
                                                        &lock_stp, &new_state);
        } else
		// 查找LOCK操作對應的nfs4_ol_stateid結構
                status = nfs4_preprocess_seqid_op(cstate,
                                       lock->lk_old_lock_seqid,
                                       &lock->lk_old_lock_stateid,
                                       NFS4_LOCK_STID, &lock_stp);
        if (status)
                goto out;
        // 取出LOCK操作所屬於的nfs4_lockowner結構.
        lock_sop = lockowner(lock_stp->st_stateowner);

	// 取出文件鎖類型,返回值是RD_STATE或WR_STATE
        lkflg = setlkflg(lock->lk_type);
        // 檢查文件的打開權限,比如OPEN操作中以只讀權限打開了一個文件,那麼就不能加寫鎖.
        status = nfs4_check_openmode(lock_stp, lkflg);
        if (status)
                goto out;       // 權限錯誤.

        status = nfserr_grace;
        if (locks_in_grace(SVC_NET(rqstp)) && !lock->lk_reclaim)
                goto out;
        status = nfserr_no_grace;
        if (!locks_in_grace(SVC_NET(rqstp)) && lock->lk_reclaim)
                goto out;

        locks_init_lock(&file_lock);    // 初始化文件鎖結構
	// 查找相應權限的文件對象結構.
        switch (lock->lk_type) {        // 文件鎖類型.
                case NFS4_READ_LT:
                case NFS4_READW_LT:     // 在nfs4_file中查找讀權限的文件對象結構.
                        filp = find_readable_file(lock_stp->st_file);
                        if (filp)
                                get_lock_access(lock_stp, NFS4_SHARE_ACCESS_READ);
                        file_lock.fl_type = F_RDLCK;    // 這是一個讀鎖.
                        break;
                case NFS4_WRITE_LT:
                case NFS4_WRITEW_LT:    // 在nfs4_file中查找寫權限的文件對象結構.
                        filp = find_writeable_file(lock_stp->st_file);
                        if (filp)
                                get_lock_access(lock_stp, NFS4_SHARE_ACCESS_WRITE);
                        file_lock.fl_type = F_WRLCK;    // 這是一個寫鎖
                        break;
                default:
                        status = nfserr_inval;
                goto out;
        }
        if (!filp) {    // 沒有找到合適的權限.
                status = nfserr_openmode;
                goto out;
        }

	// 設置文件鎖的內容.
        file_lock.fl_owner = (fl_owner_t)lock_sop;	// 文件鎖的所有者
        file_lock.fl_pid = current->tgid;               // pid
        file_lock.fl_file = filp;               // 文件對象結構
        file_lock.fl_flags = FL_POSIX;          // POSIX鎖
        file_lock.fl_lmops = &nfsd_posix_mng_ops;       // 這是文件鎖關聯的函數

        file_lock.fl_start = lock->lk_offset;   // 這是文件鎖的開始位置
        file_lock.fl_end = last_byte_offset(lock->lk_offset, lock->lk_length);  // 這是文件鎖的結束位置
        nfs4_transform_lock_offset(&file_lock);

        /*
        * Try to lock the file in the VFS.
        * Note: locks.c uses the BKL to protect the inode's lock list.
        */
	// 調用VFS層的函數設置文件鎖.
        err = vfs_lock_file(filp, F_SETLK, &file_lock, &conflock);
        switch (-err) {
        case 0: /* success! */  // 成功了.
                // 更新stateid.
                update_stateid(&lock_stp->st_stid.sc_stateid);  // lock_stp->st_stid.sc_stateid->si_generation++
                memcpy(&lock->lk_resp_stateid, &lock_stp->st_stid.sc_stateid,
                                sizeof(stateid_t));     // 爲組裝應答消息做準備
                status = 0;
                break;
        case (EAGAIN):          /* conflock holds conflicting lock */
		// 文件鎖發生了衝突.
                status = nfserr_denied;
                dprintk("NFSD: nfsd4_lock: conflicting lock found!\n");
                nfs4_set_lock_denied(&conflock, &lock->lk_denied);
                break;
        case (EDEADLK):
                status = nfserr_deadlock;
                break;
        default:
                dprintk("NFSD: nfsd4_lock: vfs_lock_file() failed! status %d\n",err);
                status = nfserrno(err);
                break;
        }
out:
        if (status && new_state)
                release_lockowner(lock_sop);
        if (!cstate->replay_owner)
                nfs4_unlock_state();
        return status;
}
講解這個函數之前得再說說服務器端與OPEN和LOCK相關的一些數據結構,但是講解前有兩點需要說明:

(1)NFS是跨操作系統的,爲了便於說明,這裏假設客戶端和服務器端都是Linux系統。

(2)客戶端可以多次將一個NFS文件系統掛載在不同的目錄下,爲了簡便起見,下面的說明只針對一次掛載的情況。

服務器端各個數據結構之間的關係如下圖所示:



(1)conf_id_hashtbl:這是一個全局hash表,這個表中保存的數據結構是nfs4_client

(2)nfs4_client:這是一個客戶端的文件系統,如果NFS文件系統被多個客戶端掛載,則每個客戶端用一個nfs4_client結構表示,這個結構中保存了客戶端的基本信息。

(3)nfs4_openowner:客戶端有多個用戶,每個用戶可以打開多個文件,每個用戶用一個nfs4_openowner結構表示,這個結構中保存了一個用戶的信息。

(4)nfs4_ol_stateid(open):因爲每個用戶可以打開多個文件,所以nfs4_openowner中包含了多個nfs4_ol_stateid結構,每個nfs4_ol_stateid結構表示用戶打開的一個文件。如果用戶多次打開了同一個文件,則服務器端只有一個nfs4_ol_stateid結構。

(5)nfs4_lockowner:因爲文件鎖操作跟進程相關,同一個用戶創建兩個進程,那麼這兩個進程中的文件鎖也不能發生衝突。簡單來說,nfs4_lockowner結構表示用戶創建的一個進程。如果用戶在同一個進程上創建了多個鎖,則服務器端只有以額nfs4_lockowner結構。

(6)nfs4_ol_stateid(lock):目前的實現中,nfs4_ol_stateid和nfs4_lockowner是一一對應的關係,可能是爲了處理方便才分成了兩個數據結構。


現在可以講解LOCK的處理過程了,nfsd4_lock()的流程基本分爲下面幾個步驟:

(1)解析文件句柄,找到對應的文件,這是通過fh_verify()實現的。

(2)查找或者創建一個新的nfs4_ol_stateid(lock)結構。lock->lk_is_new表示這是進程中創建的第一個文件鎖,這種情況下首先需要調用nfs4_preprocess_confirmed_seqid_op()查找所屬於的nfs4_ol_stateid(open)結構,然後調用lookup_or_create_lock_state()創建一個新的nfs4_ol_stateid(lock)結構,當然還包括一個新的nfs4_lockowner結構。如果不是進程中創建的一個文件鎖,則調用nfs4_preprocess_seqid_op()查找以前創建的ns4_ol_stateid(lock)結構。

(3)根據LOCK請求中的數據創建一個文件鎖的數據結構,然後調用VFS層的處理函數vfs_lock_file()設置文件鎖。





發佈了70 篇原創文章 · 獲贊 4 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章