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表示服务器实际操作方式,对应关系如下:

请求报文应答报文
UNSTABLEUNSTABLE、DATA_SYNC、FILE_SYNC
DATA_SYNCDATA_SYNC、FILE_SYNC
FILE_SYNCFILE_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占用的缓存
}








版权声明:本文为ycnian原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。