1. 程式人生 > >NFS系統write呼叫過程(三)

NFS系統write呼叫過程(三)

    NFS檔案系統中WRITE操作比READ操作要複雜一些。READ操作中只需要將資料從伺服器中讀取到客戶端的快取頁中就可以了,但是WRITE操作中客戶端可能將資料寫入到伺服器的快取頁中,也可能寫入到伺服器的磁碟中。如果資料寫入到伺服器的快取頁中了,客戶端還需要在適當的時候發起COMMIT請求將資料寫入到伺服器的磁碟中。

1.WRITE請求的結構

RFC1818規定了WRITE請求報文和應答報文的格式,請求報文格式如下:

      struct WRITE3args {
           nfs_fh3     file;    // 這是目標檔案的檔案控制代碼
           offset3     offset;  // 資料在檔案中的偏移值
           count3      count;   // WRITE請求中資料長度
           stable_how  stable;  // 資料同步方式
           opaque      data<>;  // WRITE請求中的資料
      };
應答報文格式如下:
      struct WRITE3resok {
           wcc_data    file_wcc;        // 檔案的屬性
           count3      count;           // 寫入到伺服器的資料量
           stable_how  committed;       // 資料在伺服器端的同步方式
           writeverf3  verf;            // 這是一個驗證資訊
      };

stable_how是資料同步方式,表示資料寫入到伺服器的快取頁中還是磁碟中,包含三個取值:
      enum stable_how {
           UNSTABLE  = 0,       // 不強求將資料和元資料寫入磁碟中
           DATA_SYNC = 1,       // 資料必須寫入磁碟中,元資料儘量寫入磁碟中
           FILE_SYNC = 2        // 資料和元資料必須寫入磁碟中
      };
請求報文中的stable_how表示客戶端的請求方式,而應答報文中的stable_how表示伺服器實際操作方式,對應關係如下:
請求報文 應答報文
UNSTABLE UNSTABLE、DATA_SYNC、FILE_SYNC
DATA_SYNC DATA_SYNC、FILE_SYNC
FILE_SYNC FILE_SYNC

    也就是說,如果客戶端要求將資料/元資料寫入到磁碟中,伺服器就必須將資料/元資料寫入到磁碟中。如果客戶端不強制將資料/元資料寫入磁碟中,伺服器端可以將資料/元資料寫入磁碟中,也可以只寫入快取中就返回。

應答訊息中的verf是伺服器傳遞給客戶端的一個cookie資訊,客戶端可以根據這個資訊判斷伺服器的狀態是否發生了變化。

2.COMMIT相關的資料結構

    如果伺服器只是將資料儲存在了快取頁中,那麼客戶端需要在適當的時候發起COMMIT請求,將資料重新整理到伺服器磁碟中。這個過程需要使用幾個資料結構。

struct nfs_commit_info:這個資料結構中儲存了COMMIT請求的處理函式和一個nfs_page結構的連結串列,這個連結串列中的資料需要重新整理到伺服器磁碟中。pNFS中COMMIT請求可能提交到MDS中,也可能提交到DS中,nfs_mds_commit_info和pnfs_ds_commit_info的作用相同。COMMIT請求提交到MDS中時使用nfs_mds_commit_info,COMMIT請求提交到DS中時使用pnfs_ds_commit_info,在未使用pNFS的情況下使用的是nfs_mds_commit_info。

struct nfs_commit_info {
        spinlock_t                      *lock;  // 自旋鎖
        // 當COMMIT請求提交到MDS時使用這個資料結構
        struct nfs_mds_commit_info      *mds;
        // 當COMMIT提交到DS時使用這個資料結構
        struct pnfs_ds_commit_info      *ds;
        // 這是直接IO使用的資料結構
        struct nfs_direct_req           *dreq;  /* O_DIRECT request */
        // 這是COMMIT請求的處理函式
        const struct nfs_commit_completion_ops *completion_ops;
};
struct nfs_mds_commit_info:這就是一個連結串列結構,連結串列中的資料結構是nfs_page,儲存了需要提交COMMIT請求的資料在檔案中的範圍。
struct nfs_mds_commit_info {
        atomic_t rpcs_out;              // 記錄了提交請求的次數
        unsigned long           ncommit;    // 這個結構中nfs_page結構的數量
        struct list_head        list;       // 這是一個連結串列,儲存了nfs_page結構
};
struct nfs_commit_completion_ops:這是一個操作函式集合,當客戶端提交COMMIT請求時使用這個集合中的函式。
struct nfs_commit_completion_ops {
        void (*error_cleanup) (struct nfs_inode *nfsi);
        void (*completion) (struct nfs_commit_data *data);
};

COMMIT請求中這兩個函式如下:

static const struct nfs_commit_completion_ops nfs_commit_completion_ops = {
        .completion = nfs_commit_release_pages,
        .error_cleanup = nfs_commit_clear_lock,
};

3.nfs_write_completion

    nfs_write_completion()是WRITE請求的收尾函式,當所有的WRITE請求結束後會執行nfs_write_completion()。如果WRITE應答報文中返回的不是FILE_SYNC,則需要提交COMMIT請求,函式的完整定義如下:

static void nfs_write_completion(struct nfs_pgio_header *hdr)
{
	struct nfs_commit_info cinfo;
	unsigned long bytes = 0;

	// 當建立nfs_write_data結構出錯時就設定標誌位NFS_IOHDR_REDO,
	// 這種情況下根本沒有向伺服器傳輸資料,直接退出就可以了。
	if (test_bit(NFS_IOHDR_REDO, &hdr->flags))
		goto out;
	// 根據inode初始化cinfo,主要是下面兩個操作.
	// cinfo->mds = &NFS_I(inode)->commit_info;
	// cinfo->completion_ops = &nfs_commit_completion_ops;
	nfs_init_cinfo_from_inode(&cinfo, hdr->inode);	// 初始化cinfo
	while (!list_empty(&hdr->pages)) {	// 處理每一個nfs_page
		struct nfs_page *req = nfs_list_entry(hdr->pages.next);	// 取出一個nfs_page結構

		bytes += req->wb_bytes;	// req->wb_bytes是WRITE請求中寫到伺服器中的資料量
		nfs_list_remove_request(req);	// 從連結串列hdr->pages中刪除
		if (test_bit(NFS_IOHDR_ERROR, &hdr->flags) &&
		    (hdr->good_bytes < bytes)) {
			// 資料傳輸過程發生I/O錯誤,設定標誌位PG_error以及NFS_INO_INVALID_DATA
			// 本地快取中的資料標記為無效
			nfs_set_pageerror(req->wb_page);
			// WRITE請求過程出錯了
			nfs_context_set_write_error(req->wb_context, hdr->error);
			goto remove_req;
		}
		if (test_bit(NFS_IOHDR_NEED_RESCHED, &hdr->flags)) {	// 需要重新排程這個WRITE請求
			// 將快取頁面標記為髒(PG_dirty),將radix樹中的路徑標記為髒(PAGECACHE_TAG_DIRTY)
			// 將檔案索引節點標記為髒(I_DIRTY_PAGES)
			nfs_mark_request_dirty(req);
			goto next;
		}
		// 如果WRITE操作中快取頁的資料沒有重新整理到磁碟中,就會設定這個標誌位.
		if (test_bit(NFS_IOHDR_NEED_COMMIT, &hdr->flags)) {	// 資料需要重新整理到磁碟中.
			// 將提交頭部的verifier拷貝到nfs_page結構中.   拷貝verifier  8位元組
			memcpy(&req->wb_verf, &hdr->verf->verifier, sizeof(req->wb_verf));
			// 將一個請求新增到inode的提交連結串列中(cinfo->mds)
			nfs_mark_request_commit(req, hdr->lseg, &cinfo);	// 這個請求需要提交到cinfo中,然後呢
			goto next;	// 繼續檢查下一個快取頁.
		}
remove_req:
		// 從檔案中移除一個寫請求,當寫操作完成或者出錯後執行這個函式.
		// 撤銷了nfs_page與page的關聯,刪除了nfs_page結構
		nfs_inode_remove_request(req);	// 這個函式中已經執行過nfs_release_request()了,再次執行會不會出錯呢zzzz   bugbug
next:
		nfs_unlock_request(req);	// 清除了req中的標誌位PG_BUSY.
		// 主要是取消標誌位PG_writeback,減少nfs_server->writeback中快取頁面的數量.
		nfs_end_page_writeback(req->wb_page);

	        if (nfs_write_pageuptodate(req->wb_page, hdr->inode))
        	        dprintk("nfs_updatepage: PG_uptodate");
	        else
                	dprintk("nfs_updatepage: PG_notuptodate");

		nfs_release_request(req);
	}
out:
	hdr->release(hdr);	// 釋放hdr佔用的快取
}