這篇文章接着講解nfs_readpages()的流程。上篇文章中,我們講到了read_cache_pages()将緩存頁添加到檔案緩存的radix樹中,為每個緩存頁建立了一個nfs_page結構,然後将這些緩存頁連結到nfs_pageio_descriptor結構中。nfs_pageio_descriptor中包含了多個緩存頁以及向伺服器請求資料的函數集合。接下來就是調用nfs_pageio_descriptor中的函數向伺服器請求資料了,這是通過函數nfs_pageio_complete()實作的,這個函數最終調用的是nfs_pageio_descriptor結構中pg_ops字段中的函數pg_doio(),讀操作中,這個函數是nfs_generic_pg_readpages()。
static int nfs_generic_pg_readpages(struct nfs_pageio_descriptor *desc)
{
struct nfs_read_header *rhdr;
struct nfs_pgio_header *hdr;
int ret;
// 步驟1 建立一個nfs_read_header結構
rhdr = nfs_readhdr_alloc();
if (!rhdr) { // 建立rhdr失敗了,不能處理desc中的緩存頁了,需要釋放這些緩存頁
desc->pg_completion_ops->error_cleanup(&desc->pg_list);
return -ENOMEM;
}
// 步驟2 初始化hdr
hdr = &rhdr->header; // 找到nfs_pgio_header結構
// 根據desc結構中的資訊初始化hdr,向hdr中添加了nfs_page結構,
// 設定了hdr->completion_ops、inode、io_start、good_bytes
// nfs_readhdr_free用來釋放nfs_read_header結構占用的記憶體
nfs_pgheader_init(desc, hdr, nfs_readhdr_free);
atomic_inc(&hdr->refcnt); // 增加這個結構的使用計數
// 步驟3 将desc中的緩存頁轉移到hdr中,設定READ請求的參數和傳回值
ret = nfs_generic_pagein(desc, hdr);
if (ret == 0)
// 步驟4 發起READ請求,向伺服器請求資料。
ret = nfs_do_multiple_reads(&hdr->rpc_list,
desc->pg_rpc_callops);
// 步驟5 READ請求執行完畢,進行一些收尾工作
if (atomic_dec_and_test(&hdr->refcnt))
hdr->completion_ops->completion(hdr);
return ret;
}
這個程式代碼雖然不長,但是内容比較多,一篇文章不一定可以講清楚。先講幾個資料結構。前面提到過nfs_pageio_descriptor結構中包含了多個緩存頁,用戶端向伺服器發送READ請求,用伺服器端傳回的資料填充這些緩存頁。如果nfs_pageio_descriptor結構中的資料比較多,超出了RPC的限制,則需要發起多個READ請求,每個READ請求用資料結構nfs_read_data表示。
// 這是READ請求中使用的資料結構,每個nfs_read_data結構對應一個READ請求。
struct nfs_read_data {
// nfs_pgio_header中儲存了READ請求中的通用資訊,多個相關的READ請求可以共享同一個nfs_pgio_header結構.
struct nfs_pgio_header *header;
// 一個nfs_pgio_header結構中包含了多個nfs_read_data結構,每個nfs_read_data結構代表一個READ請求,
// 這些nfs_read_data構成了一個連結清單,list指向了連結清單中相鄰的元素.
struct list_head list; // 作為指針連結到nfs_pgio_header結構中
struct rpc_task task; // 這是READ請求關聯的RPC任務
struct nfs_fattr fattr; /* fattr storage */ // 這裡儲存了檔案的屬性資訊
struct nfs_readargs args; // 這是READ請求的參數
struct nfs_readres res; // 這是READ請求的傳回值
// 這是一個時間戳,記錄了READ請求的發起時間.
unsigned long timestamp; /* For lease renewal */
// 這是READ請求完畢後調用的一個函數
int (*read_done_cb) (struct rpc_task *task, struct nfs_read_data *data);
// 這是當讀操作出錯時使用的一個字段,如果從伺服器擷取的資料量小于請求的資料量,
// 則設定這個值,再次發起READ請求,擷取上次失敗的資料。
__u64 mds_offset;
// 這裡是緩存頁連結清單指針,從伺服器擷取的資料填充這些緩存頁。
struct nfs_page_array pages; //這裡儲存了一些緩存頁面指針,這些緩存頁面指向了nfs_pgio_header結構中的pages
// 這是pNFS中和DS通信的用戶端
struct nfs_client *ds_clp; /* pNFS data server */
};
這些READ請求處理的是同一個nfs_pageio_descriptor結構中的資料,具有一些相同的資訊,這些相同的資訊用資料結構nfs_pgio_header表示,nfs_pageio_descriptor中所有的READ請求共享一個nfs_pgio_header結構中的資訊。
// 這是一個I/O操作頭的資料結構
struct nfs_pgio_header {
struct inode *inode; // 檔案索引節點,請求這個檔案中的資料
struct rpc_cred *cred; // 使用者資訊
// 這是一個連結清單,連結清單中的資料結構是nfs_page,這是從nfs_pageio_descriptor結構中轉移過來的.
struct list_head pages; // 這裡存放的應該是nfs_page結構
// 這是一個連結清單,連結清單中存放的是nfs_read_data,每個nfs_read_data表示一個READ請求
struct list_head rpc_list;
atomic_t refcnt; // 這個結構的引用計數
// 這是連結清單pages中第一個緩存頁的指針
struct nfs_page *req; // nfs_page結構,這個結構對應一個緩存頁
// 多個WRITE請求中也可以共享一個nfs_pgio_header結構,verf是WRITE請求中使用的一個字段
struct nfs_writeverf *verf;
// 這是檔案的layout資訊,供pNFS使用
struct pnfs_layout_segment *lseg;
// 這是請求的資料在檔案中的起始位置
loff_t io_start;
// 這是RPC操作中使用的函數
const struct rpc_call_ops *mds_ops;
// 這是一個錯誤處理函數,出錯後調用這個函數釋放nfs_pgio_header占用的記憶體
void (*release) (struct nfs_pgio_header *hdr);
// 這是READ請求完畢後執行的一個函數
const struct nfs_pgio_completion_ops *completion_ops;
// 這是直接IO相關的資料結構
struct nfs_direct_req *dreq;
spinlock_t lock; // 保護這個資料結構的自旋鎖
/* fields protected by lock */
int pnfs_error; // pNFS中的錯誤碼
int error; /* merge with pnfs_error */
// 實際讀取的資料量
unsigned long good_bytes; /* boundary of good data */
unsigned long flags; // 一些标志位
};
如果nfs_pageio_descriptor結構中的資料比較少,隻需要一個READ請求,那麼就可以使用資料結構nfs_read_header。
struct nfs_read_header {
struct nfs_pgio_header header; // 這是READ操作的通用資訊
struct nfs_read_data rpc_data; // 這是一次READ請求的資訊
};
如果nfs_pageio_descriptor結構中的資料比較多,需要發起多次READ請求,那麼就可以建立一個nfs_read_header結構和多個nfs_read_data結構。
現在可以講解nfs_generic_pg_readpages的流程了。
(1)建立一個nfs_read_header結構。
(2)初始化其中的nfs_pgio_header結構。
(3)根據nfs_pageio_descriptor結構中的資料建立若幹個nfs_read_data結構,将nfs_pageio_descriptor結構中的緩存頁配置設定給每個nfs_read_data結構。
(4)初始化READ請求的參數和傳回值,每個nfs_read_data結構發起一次READ請求。
(5)所有的nfs_read_data結構完畢後,執行一些收尾工作。