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表示服务器实际操作方式,对应关系如下:请求报文 | 应答报文 |
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占用的缓存
}