1.nfs_writepages
nfs_file_write()是NFS系统中的写操作函数,这个函数将用户态缓冲区中的数据写入到内核态缓存页中。如果是同步写操作,则马上调用vfs_fsync()将缓存页中的数据刷新到服务器中。如果不是同步写操作,则客户端定期将缓存页中的数据刷新到服务器中。无论哪种情况,最终都需要调用address_space_operations结构中的writepages函数,NFS系统中这个函数是nfs_writepages(),这个函数的定义如下:
// 这是NFS文件系统中的writepages()函数.
-
- int nfs_writepages(struct address_space *mapping, struct writeback_control *wbc)
- {
- struct inode *inode = mapping->host;
- unsigned long *bitlock = &NFS_I(inode)->flags;
- struct nfs_pageio_descriptor pgio;
- int err;
-
-
-
- err = wait_on_bit_lock(bitlock, NFS_INO_FLUSHING,
- nfs_wait_bit_killable, TASK_KILLABLE);
- if (err)
- goto out_err;
-
- nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGES);
-
-
-
-
-
- NFS_PROTO(inode)->write_pageio_init(&pgio, inode, wb_priority(wbc), &nfs_async_write_completion_ops);
-
-
-
-
-
- err = write_cache_pages(mapping, wbc, nfs_writepages_callback, &pgio);
-
-
- nfs_pageio_complete(&pgio);
-
-
- clear_bit_unlock(NFS_INO_FLUSHING, bitlock);
- smp_mb__after_clear_bit();
- wake_up_bit(bitlock, NFS_INO_FLUSHING);
-
- if (err < 0)
- goto out_err;
- err = pgio.pg_error;
- if (err < 0)
- goto out_err;
- return 0;
- out_err:
- return err;
- }
这个函数的处理流程和read操作的处理流程相似,基本上分为三个步骤。
步骤1:初始化一个nfs_pageio_descriptor结构,这个结构中包含了WRITE操作中的操作函数和错误处理函数。
步骤2:查找文件中的脏缓存页,将脏缓存页添加到步骤1中创建的nfs_pageio_descriptor结构中。
步骤3:根据nfs_pageio_descriptor结构中的数据量创建一个或者多个nfs_write_data结构,每个nfs_write_data结构表示一个WRITE请求,向服务器发起WRITE请求,将数据写到服务器中。
2.初始化nfs_pageio_descriptor结构
这个结构中设置了WRITE操作中的操作函数和错误处理函数,主要设置了下面两个操作函数集合
- static const struct nfs_pageio_ops nfs_pageio_write_ops = {
-
-
- .pg_test = nfs_generic_pg_test,
-
-
- .pg_doio = nfs_generic_pg_writepages,
- };
- static const struct nfs_pgio_completion_ops nfs_async_write_completion_ops = {
-
- .error_cleanup = nfs_async_write_error,
-
- .completion = nfs_write_completion,
- };
3.查找脏缓存页
查找文件中脏缓存页的函数是write_cache_pages(),这是VFS层的通用函数,函数声明如下:
- int write_cache_pages(struct address_space *mapping,
- struct writeback_control *wbc, writepage_t writepage,
- void *data)
这个函数包含四个参数:参数mapping:这是文件缓存的数据结构,查找这个文件中的脏缓存页
参数wbc:这个结构中包含了查找范围,只查找这个范围内的脏缓存页
参数writepage:这是一个处理函数,write_cache_pages查找指定返回内的脏缓存页,writepage对脏缓存页进行处理,每个脏缓存页都需要执行一次这个函数
参数data:这是writepage使用的参数
NFS系统中的writepage是nfs_writepages_callback(),这个函数将查找到的脏缓存页添加到前面定义的nfs_pageio_descriptor结构中,函数流程如下:
参数page:查找到的一个脏缓存页
参数wbc:这个结构中包含了查找范围
参数data:这是一个nfs_pageio_descriptor结构的指针,将脏缓存页添加到这个结构中。
-
- static int nfs_writepages_callback(struct page *page, struct writeback_control *wbc, void *data)
- {
- int ret;
-
-
- ret = nfs_do_writepage(page, wbc, data);
- unlock_page(page);
- return ret;
- }
实际的处理函数是nfs_do_writepage。
- static int nfs_do_writepage(struct page *page, struct writeback_control *wbc, struct nfs_pageio_descriptor *pgio)
- {
-
- struct inode *inode = page_file_mapping(page)->host;
- int ret;
-
- nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGE);
- nfs_add_stats(inode, NFSIOS_WRITEPAGES, 1);
-
-
-
- nfs_pageio_cond_complete(pgio, page_file_index(page));
-
- ret = nfs_page_async_flush(pgio, page, wbc->sync_mode == WB_SYNC_NONE);
- if (ret == -EAGAIN) {
- redirty_page_for_writepage(wbc, page);
- ret = 0;
- }
- return ret;
- }
pgio中所有的数据必须是连续的,如果缓存页page和pgio中的数据不连续就需要先处理pgio中的数据(向服务器发送WRITE请求),处理完毕后再将缓存页page添加到pgio中,将新的缓存页添加到pgio的函数是nfs_page_async_flush()。- static int nfs_page_async_flush(struct nfs_pageio_descriptor *pgio,
- struct page *page, bool nonblock)
- {
- struct nfs_page *req;
- int ret = 0;
-
-
- req = nfs_find_and_lock_request(page, nonblock);
- if (!req)
- goto out;
- ret = PTR_ERR(req);
- if (IS_ERR(req))
- goto out;
-
-
- ret = nfs_set_page_writeback(page);
- BUG_ON(ret != 0);
- BUG_ON(test_bit(PG_CLEAN, &req->wb_flags));
-
-
- if (!nfs_pageio_add_request(pgio, req)) {
-
-
- nfs_redirty_request(req);
- ret = pgio->pg_error;
- }
- out:
- return ret;
- }
4.将数据写入服务器中
NFS客户端调用write_cache_pages()查找到了文件中的缓存页,并添加到了nfs_pageio_descriptor结构中,现在就可以向服务器写数据了,这是通过nfs_pageio_complete()实现>的,这个函数最终调用了nfs_generic_pg_writepages(),这个函数的定义如下:
- static int nfs_generic_pg_writepages(struct nfs_pageio_descriptor *desc)
- {
- struct nfs_write_header *whdr;
- struct nfs_pgio_header *hdr;
- int ret;
-
-
- whdr = nfs_writehdr_alloc();
- if (!whdr) {
-
-
- desc->pg_completion_ops->error_cleanup(&desc->pg_list);
- return -ENOMEM;
- }
- hdr = &whdr->header;
-
- nfs_pgheader_init(desc, hdr, nfs_writehdr_free);
- atomic_inc(&hdr->refcnt);
-
-
- ret = nfs_generic_flush(desc, hdr);
- if (ret == 0)
-
- ret = nfs_do_multiple_writes(&hdr->rpc_list,
- desc->pg_rpc_callops,
- desc->pg_ioflags);
-
- if (atomic_dec_and_test(&hdr->refcnt))
- hdr->completion_ops->completion(hdr);
- return ret;
- }
这个函数的处理流程和READ操作中nfs_generic_pg_readpages()的处理流程基本一致,根据desc中的数据量创建一个或者多个nfs_write_data结构,每个结构发起一次WRITE调用,就不详细讲解了。但是这里需要详细讲解步骤4中的收尾操作。READ操作中的收尾函数是nfs_read_completion(),这个函数的主要任务是删除nfs_page结构占用的内存。WRITE操作中的收尾函数是nfs_write_completion(),但是这个函数做的工作要多一些。当客户端向服务器发送数据时,这些数据可能保存在服务器磁盘中了,也可能只保存在服务器的缓存页中了,这种情况下数据还有丢失的风险。WRITE应答报文中会告诉客户端数据是刷新到磁盘中了还是在缓存页中。客户端解析应答报文,如果数据保存在缓存页中了,就暂时不能释放nfs_page占用的内存,而是将这些缓存页链接在文件索引节点的一个链表中。适当时候,客户端解析这个链表中的数据范围,向服务器发送COMMIT请求。