从nfs_page结构看PNFS读写流程

来源:互联网 发布:java的封装性 编辑:程序博客网 时间:2024/04/30 17:58

在pnfs中,每个radix tree 中page的private指针都会指向一个nfs_page结构,在代码中的变量名通常是req,即为读写请求,事实上,其存在的意义也是读写请求。

创建函数:

/**
 * nfs_create_request - Create an NFS read/write request.
 * @file: file descriptor to use
 * @inode: inode to which the request is attached
 * @page: page to write
 * @offset: starting offset within the page for the write
 * @count: number of bytes to read/write
 *
 * The page must be locked by the caller. This makes sure we never
 * create two different requests for the same page.
 * User should ensure it is safe to sleep in this function.
 */
struct nfs_page *
nfs_create_request(struct nfs_open_context *ctx, struct inode *inode,
           struct page *page,
           unsigned int offset, unsigned int count, void *fsdata)
{
    struct nfs_page        *req;

    for (;;) {
        /* try to allocate the request struct */
        req = nfs_page_alloc();
        if (req != NULL)
            break;

        if (fatal_signal_pending(current))
            return ERR_PTR(-ERESTARTSYS);
        yield();
    }

    /* Initialize the request struct. Initially, we assume a
     * long write-back delay. This will be adjusted in
     * update_nfs_request below if the region is not locked. */
    req->wb_page    = page;
    atomic_set(&req->wb_complete, 0);
    req->wb_index    = page->index;
    page_cache_get(page);
    BUG_ON(PagePrivate(page));
    BUG_ON(!PageLocked(page));
    BUG_ON(page->mapping->host != inode);
    req->wb_offset  = offset;
    req->wb_pgbase    = offset;
    req->wb_bytes   = count;
    req->wb_context = get_nfs_open_context(ctx);
    kref_init(&req->wb_kref);
    pnfs_modify_new_request(req, fsdata);
    return req;
}

此函数应在两个地方,要求持有page_lock锁,这是因为要修改page->private指针,保证没有其他req挂在此指针上。

1.nfs_write_end;

当把数据写到也缓存中时候,需要为脏页设置好一个req,当下刷的时候就可以直接用了。具体函数如下

static struct nfs_page * nfs_setup_write_request(struct nfs_open_context* ctx,
        struct page *page, unsigned int offset, unsigned int bytes,
        void *fsdata)
{
    struct inode *inode = page->mapping->host;
    struct nfs_page    *req;
    int error;

    req = nfs_try_to_update_request(inode, page, offset, bytes, fsdata);
    if (req != NULL)
        goto out;
    req = nfs_create_request(ctx, inode, page, offset, bytes, fsdata);
    if (IS_ERR(req))
        goto out;
    error = nfs_inode_add_request(inode, req);
    if (error != 0) {
        nfs_release_request(req);
        req = ERR_PTR(error);
    }
out:
    return req;
}

从代码来看,先去尝试更新一个已有的req,若没有,则尝试创建一个新的,并将其添加到nfs_inode结构中的radix tree中。

接着看更新一个已有的request的函数

tatic struct nfs_page *nfs_try_to_update_request(struct inode *inode,
        struct page *page,
        unsigned int offset,
        unsigned int bytes,
        void *fsdata)
{
    struct nfs_page *req;
    unsigned int rqend;
    unsigned int end;
    int error;

    if (!PagePrivate(page))
        return NULL;

    end = offset + bytes;
    spin_lock(&inode->i_lock);

    for (;;) {
        req = nfs_page_find_request_locked(page);
        if (req == NULL)
            goto out_unlock;

        rqend = req->wb_offset + req->wb_bytes;
        /*
         * Tell the caller to flush out the request if
         * the offsets are non-contiguous.
         * Note: nfs_flush_incompatible() will already
         * have flushed out requests having wrong owners.
         */
        if (offset > rqend
            || end < req->wb_offset
            || pnfs_do_flush(req, fsdata))
            goto out_flushme;

        if (nfs_set_page_tag_locked(req))
            break;

        /* The request is locked, so wait and then retry */
        spin_unlock(&inode->i_lock);
        error = nfs_wait_on_request(req);
        nfs_release_request(req);
        if (error != 0)
            goto out_err;
        spin_lock(&inode->i_lock);
    }

    if (nfs_clear_request_commit(req))
        radix_tree_tag_clear(&NFS_I(inode)->nfs_page_tree,
                req->wb_index, NFS_PAGE_TAG_COMMIT);

    /* Okay, the request matches. Update the region */
    if (offset < req->wb_offset) {
        req->wb_offset = offset;
        req->wb_pgbase = offset;
    }
    if (end > rqend)
        req->wb_bytes = end - req->wb_offset;
    else
        req->wb_bytes = rqend - req->wb_offset;
out_unlock:
    spin_unlock(&inode->i_lock);
    return req;
out_flushme:
    spin_unlock(&inode->i_lock);
    nfs_release_request(req);
    error = nfs_wb_page(inode, page);
out_err:
    return ERR_PTR(error);

}

此函数的for循环中,先去尝试找到一个req,若存在,其对应区段与当前的不连续,则下刷之。然后修改对应的offset和length.

此函数返回后,在 nfs_setup_write_request()中,将其添加到error = nfs_inode_add_request(inode, req);中。

对应的函数是static void nfs_inode_remove_request(struct nfs_page *req)

此函数在nfs_write_release()中被调用,即为把脏页写回磁盘的回调函数;或者commit结束后调用。


×××××××××××××××××读写分割线×××××××××××××××××××××××××××××××××××××


PNFS从磁盘读数据时,调用流程是nfs_readpages ->read_cache_pages(mapping, pages, readpage_async_filler, &desc);

注意传进去的参数 readpage_async_filler,此函数代码如下:

static int
readpage_async_filler(void *data, struct page *page)
{
    struct nfs_readdesc *desc = (struct nfs_readdesc *)data;
    struct inode *inode = page->mapping->host;
    struct nfs_page *new;
    unsigned int len;
    int error;

    len = nfs_page_length(page);
    if (len == 0)
        return nfs_return_empty_page(page);

    new = nfs_create_request(desc->ctx, inode, page, 0, len, NULL);
    if (IS_ERR(new))
        goto out_error;

    if (len < PAGE_CACHE_SIZE)
        zero_user_segment(page, len, PAGE_CACHE_SIZE);
    if (!nfs_pageio_add_request(desc->pgio, new)) {
        error = desc->pgio->pg_error;
        goto out_unlock;
    }
    return 0;
out_error:
    error = PTR_ERR(new);
    SetPageError(page);
out_unlock:
    unlock_page(page);
    return error;
}

创建了一个req,并将其添加到desc->pgio这个链表中。此函数返回到nfs_readpages中后,nfs_pageio_compliete最终会根据此链表,发送到layoutdriver层。

在bio的回调函数中,释放req.



对nfs_page这个结构,读与写有什么区别?

1.下刷时候,脏页脱离了radix tree,而是挂到了nfs_inode的tree上,而从磁盘读时,页还在原来的radix tree.
其实这是个页缓存的问题,对于脏页,将其刷到磁盘后就应该将其释放,也就是从inode->i_mapping中删除。

但是在layout commit时候需要这棵树来表征标志值,所以在nfs inode中新建了一颗新树。

而读磁盘时候,是不同的,直接挂到inode->i_maping中。


2.在layout driver层,要求传递进来的页是逻辑连续的。

下刷时候,有相关判断,如果不连续就先刷一部分。

读取时候则没有,直接把一串页传递给layout层,这是因为由上层接口决定。

nfs_readpages()指定了一个区段,最终目标就是填满这个区段对应的radix tree,读取过程中,如果有脏页,则要先刷再读,这样就不存在不连续的情况。

而nfs_wb_all,则是要下刷所有脏页,脏页不连续很正常。



××××××××××××××××××××××××



今天终于比较透彻的理解了PNFS的读写代码。

花了两天的时间,还是很值得的。

不仅是对PNFS的理解,还有页缓存,更加宝贵的是,提高了阅读代码的信心和经验。




原创粉丝点击