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