天天看點

NFS系統read調用過程(五)

    這篇文章接着講解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結構完畢後,執行一些收尾工作。

繼續閱讀