nfs write2

来源:互联网 发布:做淘宝后感想 编辑:程序博客网 时间:2024/05/16 06:55

1.nfs_writepages

    nfs_file_write()是NFS系统中的写操作函数,这个函数将用户态缓冲区中的数据写入到内核态缓存页中。如果是同步写操作,则马上调用vfs_fsync()将缓存页中的数据刷新到服务器中。如果不是同步写操作,则客户端定期将缓存页中的数据刷新到服务器中。无论哪种情况,最终都需要调用address_space_operations结构中的writepages函数,NFS系统中这个函数是nfs_writepages(),这个函数的定义如下:

// 这是NFS文件系统中的writepages()函数.

[cpp] view plain copy
 print?
  1. // 将缓存中的数据刷新到NFS服务器中    wbc中记录了数据刷新范围  
  2. int nfs_writepages(struct address_space *mapping, struct writeback_control *wbc)  
  3. {  
  4.     struct inode *inode = mapping->host;        // 找到文件索引节点  
  5.     unsigned long *bitlock = &NFS_I(inode)->flags;  // 一些标志位  
  6.     struct nfs_pageio_descriptor pgio;  
  7.     int err;  
  8.   
  9.     /* Stop dirtying of new pages while we sync */  
  10.     // 等待标志位NFS_INO_FLUSHING复位,然后设置这个标志位. NFS_INO_FLUSHING表示正在刷新数据  
  11.     err = wait_on_bit_lock(bitlock, NFS_INO_FLUSHING,  
  12.             nfs_wait_bit_killable, TASK_KILLABLE);  
  13.     if (err)  
  14.         goto out_err;  
  15.   
  16.     nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGES); // 增加事件计数  
  17.   
  18.     // nfs_pageio_init_write()    初始化nfs_pageio_descriptor结构     步骤(1)  
  19.     // 这个函数主要设置下面两个参数  
  20.     // desc->pg_ops = nfs_pageio_write_ops  
  21.     // desc->pg_completion_ops = nfs_async_write_completion_ops  
  22.     NFS_PROTO(inode)->write_pageio_init(&pgio, inode, wb_priority(wbc), &nfs_async_write_completion_ops);  
  23.     // mm/page-writeback.c  
  24.     // write_cache_pages在缓存中查找脏页,对查找到的脏页执行函数nfs_writepages_callback  
  25.     // pgio是传递给nfs_writepages_callback的参数  
  26.     // nfs_writepages_callback()将查找到的符合条件的缓存页面添加到pgio中    步骤(2)  
  27.     // 现在pgio中就包含了大量的缓存页,这些缓存页需要处理.所有的缓存页添加到链表pgio->pg_list中了.  
  28.     err = write_cache_pages(mapping, wbc, nfs_writepages_callback, &pgio);  
  29.     // 这个函数根据pgio中的数据创建了一个nfs_pgio_header结构以及若干个nfs_write_data结构  
  30.     // 每个nfs_write_data结构发起一次WRITE请求,将缓存页中的数据写到服务器中  
  31.     nfs_pageio_complete(&pgio); // 这个函数负责处理数据写请求    步骤(3)  
  32.   
  33.     // 数据刷新完毕,可以清除标志位NFS_INO_FLUSHING了,其他用户可以向服务器传输数据了  
  34.     clear_bit_unlock(NFS_INO_FLUSHING, bitlock);  
  35.     smp_mb__after_clear_bit();  
  36.     wake_up_bit(bitlock, NFS_INO_FLUSHING);     // 唤醒其他进程  
  37.   
  38.     if (err < 0)  
  39.         goto out_err;  
  40.     err = pgio.pg_error;  
  41.     if (err < 0)  
  42.         goto out_err;  
  43.     return 0;  
  44. out_err:  
  45.     return err;  
  46. }  

这个函数的处理流程和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操作中的操作函数和错误处理函数,主要设置了下面两个操作函数集合

[cpp] view plain copy
 print?
  1. static const struct nfs_pageio_ops nfs_pageio_write_ops = {  
  2.         // 这是一个简单的测试函数,保证了desc中的数据量不会超过wsize限制  
  3.         // 向desc中添加nfs_page请求时需要执行这个函数.  
  4.         .pg_test = nfs_generic_pg_test,  
  5.         // 根据desc中的数据创建一个nfs_pgio_header结构以及多个nfs_write_data结构,  
  6.         // 每个nfs_write_data结构发起一个WRITE请求,向服务器刷新数据.  
  7.         .pg_doio = nfs_generic_pg_writepages,  
  8. };  
  9. static const struct nfs_pgio_completion_ops nfs_async_write_completion_ops = {  
  10.         // 当创建nfs_write_header结构失败时调用这个函数,删除相关的nfs_page结构  
  11.         .error_cleanup = nfs_async_write_error,  
  12.         // 所有的写操作完毕后执行这个函数,做一些收尾工作,后面会讲解这个函数  
  13.         .completion = nfs_write_completion,  
  14. };  


3.查找脏缓存页

查找文件中脏缓存页的函数是write_cache_pages(),这是VFS层的通用函数,函数声明如下:

[cpp] view plain copy
 print?
  1. int write_cache_pages(struct address_space *mapping,  
  2.                       struct writeback_control *wbc, writepage_t writepage,  
  3.                       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结构的指针,将脏缓存页添加到这个结构中。

[cpp] view plain copy
 print?
  1. // nfs_writepages函数调用write_cache_pages()查找脏的缓存页,对查找到的每个脏页执行函数nfs_writepages_callback  
  2. static int nfs_writepages_callback(struct page *page, struct writeback_control *wbc, void *data)  
  3. {  
  4.         int ret;  
  5.   
  6.         // data是nfs_pageio_descriptor结构的指针,这个函数将缓存页page添加到data中  
  7.         ret = nfs_do_writepage(page, wbc, data);  
  8.         unlock_page(page);        
  9.         return ret;  
  10. }  

实际的处理函数是nfs_do_writepage。

[cpp] view plain copy
 print?
  1. static int nfs_do_writepage(struct page *page, struct writeback_control *wbc, struct nfs_pageio_descriptor *pgio)  
  2. {  
  3.     // 找到文件的索引节点  
  4.         struct inode *inode = page_file_mapping(page)->host;  
  5.         int ret;  
  6.   
  7.         nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGE);      // 增加统计计数  
  8.         nfs_add_stats(inode, NFSIOS_WRITEPAGES, 1);  
  9.   
  10.         // 检查缓存页page和pgio中的数据是否相连。如果不相连就需要先处理pgio中的数据,  
  11.         // 否则page没办法添加到pgio中。如果相连就不需要处理了。  
  12.         nfs_pageio_cond_complete(pgio, page_file_index(page));    
  13.         // 将缓存页page添加到pgio中  
  14.         ret = nfs_page_async_flush(pgio, page, wbc->sync_mode == WB_SYNC_NONE);  
  15.         if (ret == -EAGAIN) {  
  16.                 redirty_page_for_writepage(wbc, page);  
  17.                 ret = 0;  
  18.         }  
  19.         return ret;  
  20. }  
pgio中所有的数据必须是连续的,如果缓存页page和pgio中的数据不连续就需要先处理pgio中的数据(向服务器发送WRITE请求),处理完毕后再将缓存页page添加到pgio中,将新的缓存页添加到pgio的函数是nfs_page_async_flush()。

[cpp] view plain copy
 print?
  1. static int nfs_page_async_flush(struct nfs_pageio_descriptor *pgio,  
  2.                                 struct page *page, bool nonblock)  
  3. {  
  4.         struct nfs_page *req;  
  5.         int ret = 0;  
  6.   
  7.         // 锁定这个写请求,req = page->private  
  8.         req = nfs_find_and_lock_request(page, nonblock);  
  9.         if (!req)  
  10.                 goto out;       // 还没有设置nfs_page.  
  11.         ret = PTR_ERR(req);  
  12.         if (IS_ERR(req))  
  13.                 goto out;  
  14.   
  15.         // page需要写数据了,这个函数在设置标志位PG_writeback  
  16.         ret = nfs_set_page_writeback(page);  
  17.         BUG_ON(ret != 0);  
  18.         BUG_ON(test_bit(PG_CLEAN, &req->wb_flags));  
  19.   
  20.         // 将请求添加到pgio中  
  21.         if (!nfs_pageio_add_request(pgio, req)) {  
  22.                 // 处理失败  
  23.                 // 将缓存页重新标记为脏,因为数据刷新请求失败了.  
  24.                 nfs_redirty_request(req);  
  25.                 ret = pgio->pg_error;  
  26.         }  
  27. out:  
  28.         return ret;  
  29. }  


4.将数据写入服务器中

NFS客户端调用write_cache_pages()查找到了文件中的缓存页,并添加到了nfs_pageio_descriptor结构中,现在就可以向服务器写数据了,这是通过nfs_pageio_complete()实现>的,这个函数最终调用了nfs_generic_pg_writepages(),这个函数的定义如下:

[cpp] view plain copy
 print?
  1. static int nfs_generic_pg_writepages(struct nfs_pageio_descriptor *desc)  
  2. {  
  3.         struct nfs_write_header *whdr;  
  4.         struct nfs_pgio_header *hdr;  
  5.         int ret;  
  6.   
  7.         // 步骤1   创建一个nfs_write_header结构  
  8.         whdr = nfs_writehdr_alloc();  
  9.         if (!whdr) {  
  10.                 // 出错了,释放desc中所有的写请求,重新将缓存页面标记为脏.  
  11.                 // 处理函数是nfs_async_write_error()  
  12.                 desc->pg_completion_ops->error_cleanup(&desc->pg_list);   
  13.                 return -ENOMEM;  
  14.         }  
  15.         hdr = &whdr->header;        // 这是nfs_pgio_header结构  
  16.         // 步骤2  初始化nfs_pgio_header结构,nfs_writehdr_free是释放nfs_write_header结构的函数  
  17.         nfs_pgheader_init(desc, hdr, nfs_writehdr_free);  
  18.         atomic_inc(&hdr->refcnt);       // 引用计数增加为1了  
  19.         // 步骤3  根据desc中的数据量创建一个或者多个nfs_write_data结构  
  20.         // 每个nfs_write_data代表一次WRITE请求  
  21.         ret = nfs_generic_flush(desc, hdr);  
  22.         if (ret == 0)  
  23.                 // 步骤4  发起WRITE请求  
  24.                 ret = nfs_do_multiple_writes(&hdr->rpc_list,  
  25.                                              desc->pg_rpc_callops,  
  26.                                              desc->pg_ioflags);  
  27.         // 步骤4  所有的数据处理完毕,进行收尾操作  
  28.         if (atomic_dec_and_test(&hdr->refcnt))  
  29.                 hdr->completion_ops->completion(hdr);  
  30.         return ret;  
  31. }  

    这个函数的处理流程和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请求。

原创粉丝点击