📄 write.c
字号:
/* * linux/fs/nfs/write.c * * Writing file data over NFS. * * We do it like this: When a (user) process wishes to write data to an * NFS file, a write request is allocated that contains the RPC task data * plus some info on the page to be written, and added to the inode's * write chain. If the process writes past the end of the page, an async * RPC call to write the page is scheduled immediately; otherwise, the call * is delayed for a few seconds. * * Just like readahead, no async I/O is performed if wsize < PAGE_SIZE. * * Write requests are kept on the inode's writeback list. Each entry in * that list references the page (portion) to be written. When the * cache timeout has expired, the RPC task is woken up, and tries to * lock the page. As soon as it manages to do so, the request is moved * from the writeback list to the writelock list. * * Note: we must make sure never to confuse the inode passed in the * write_page request with the one in page->inode. As far as I understand * it, these are different when doing a swap-out. * * To understand everything that goes on here and in the NFS read code, * one should be aware that a page is locked in exactly one of the following * cases: * * - A write request is in progress. * - A user process is in generic_file_write/nfs_update_page * - A user process is in generic_file_read * * Also note that because of the way pages are invalidated in * nfs_revalidate_inode, the following assertions hold: * * - If a page is dirty, there will be no read requests (a page will * not be re-read unless invalidated by nfs_revalidate_inode). * - If the page is not uptodate, there will be no pending write * requests, and no process will be in nfs_update_page. * * FIXME: Interaction with the vmscan routines is not optimal yet. * Either vmscan must be made nfs-savvy, or we need a different page * reclaim concept that supports something like FS-independent * buffer_heads with a b_ops-> field. * * Copyright (C) 1996, 1997, Olaf Kirch <okir@monad.swb.de> */#include <linux/config.h>#include <linux/types.h>#include <linux/slab.h>#include <linux/swap.h>#include <linux/pagemap.h>#include <linux/file.h>#include <linux/sunrpc/clnt.h>#include <linux/nfs_fs.h>#include <linux/nfs_mount.h>#include <linux/nfs_flushd.h>#include <linux/nfs_page.h>#include <asm/uaccess.h>#include <linux/smp_lock.h>#define NFSDBG_FACILITY NFSDBG_PAGECACHE/* * Local structures * * This is the struct where the WRITE/COMMIT arguments go. */struct nfs_write_data { struct rpc_task task; struct inode *inode; struct rpc_cred *cred; struct nfs_writeargs args; /* argument struct */ struct nfs_writeres res; /* result struct */ struct nfs_fattr fattr; struct nfs_writeverf verf; struct list_head pages; /* Coalesced requests we wish to flush */};/* * Local function declarations */static struct nfs_page * nfs_update_request(struct file*, struct inode *, struct page *, unsigned int, unsigned int);static void nfs_strategy(struct inode *inode);static void nfs_writeback_done(struct rpc_task *);#ifdef CONFIG_NFS_V3static void nfs_commit_done(struct rpc_task *);#endif/* Hack for future NFS swap support */#ifndef IS_SWAPFILE# define IS_SWAPFILE(inode) (0)#endifstatic kmem_cache_t *nfs_wdata_cachep;static __inline__ struct nfs_write_data *nfs_writedata_alloc(void){ struct nfs_write_data *p; p = kmem_cache_alloc(nfs_wdata_cachep, SLAB_NOFS); if (p) { memset(p, 0, sizeof(*p)); INIT_LIST_HEAD(&p->pages); } return p;}static __inline__ void nfs_writedata_free(struct nfs_write_data *p){ kmem_cache_free(nfs_wdata_cachep, p);}static void nfs_writedata_release(struct rpc_task *task){ struct nfs_write_data *wdata = (struct nfs_write_data *)task->tk_calldata; nfs_writedata_free(wdata);}/* * This function will be used to simulate weak cache consistency * under NFSv2 when the NFSv3 attribute patch is included. * For the moment, we just call nfs_refresh_inode(). */static __inline__ intnfs_write_attributes(struct inode *inode, struct nfs_fattr *fattr){ if ((fattr->valid & NFS_ATTR_FATTR) && !(fattr->valid & NFS_ATTR_WCC)) { fattr->pre_size = NFS_CACHE_ISIZE(inode); fattr->pre_mtime = NFS_CACHE_MTIME(inode); fattr->pre_ctime = NFS_CACHE_CTIME(inode); fattr->valid |= NFS_ATTR_WCC; } return nfs_refresh_inode(inode, fattr);}/* * Write a page synchronously. * Offset is the data offset within the page. */static intnfs_writepage_sync(struct file *file, struct inode *inode, struct page *page, unsigned int offset, unsigned int count){ struct rpc_cred *cred = NULL; loff_t base; unsigned int wsize = NFS_SERVER(inode)->wsize; int result, refresh = 0, written = 0, flags; u8 *buffer; struct nfs_fattr fattr; struct nfs_writeverf verf; if (file) cred = get_rpccred(nfs_file_cred(file)); if (!cred) cred = get_rpccred(NFS_I(inode)->mm_cred); dprintk("NFS: nfs_writepage_sync(%x/%Ld %d@%Ld)\n", inode->i_dev, (long long)NFS_FILEID(inode), count, (long long)(page_offset(page) + offset)); buffer = kmap(page) + offset; base = page_offset(page) + offset; flags = ((IS_SWAPFILE(inode)) ? NFS_RW_SWAP : 0) | NFS_RW_SYNC; do { if (count < wsize && !IS_SWAPFILE(inode)) wsize = count; result = NFS_PROTO(inode)->write(inode, cred, &fattr, flags, base, wsize, buffer, &verf); nfs_write_attributes(inode, &fattr); if (result < 0) { /* Must mark the page invalid after I/O error */ ClearPageUptodate(page); goto io_error; } if (result != wsize) printk("NFS: short write, wsize=%u, result=%d\n", wsize, result); refresh = 1; buffer += wsize; base += wsize; written += wsize; count -= wsize; /* * If we've extended the file, update the inode * now so we don't invalidate the cache. */ if (base > inode->i_size) inode->i_size = base; } while (count); if (PageError(page)) ClearPageError(page);io_error: kunmap(page); if (cred) put_rpccred(cred); return written? written : result;}static intnfs_writepage_async(struct file *file, struct inode *inode, struct page *page, unsigned int offset, unsigned int count){ struct nfs_page *req; loff_t end; int status; req = nfs_update_request(file, inode, page, offset, count); status = (IS_ERR(req)) ? PTR_ERR(req) : 0; if (status < 0) goto out; if (!req->wb_cred) req->wb_cred = get_rpccred(NFS_I(inode)->mm_cred); nfs_unlock_request(req); nfs_strategy(inode); end = ((loff_t)page->index<<PAGE_CACHE_SHIFT) + (loff_t)(offset + count); if (inode->i_size < end) inode->i_size = end; out: return status;}/* * Write an mmapped page to the server. */intnfs_writepage(struct page *page){ struct inode *inode; unsigned long end_index; unsigned offset = PAGE_CACHE_SIZE; int err; struct address_space *mapping = page->mapping; if (!mapping) BUG(); inode = mapping->host; if (!inode) BUG(); end_index = inode->i_size >> PAGE_CACHE_SHIFT; /* Ensure we've flushed out any previous writes */ nfs_wb_page(inode,page); /* easy case */ if (page->index < end_index) goto do_it; /* things got complicated... */ offset = inode->i_size & (PAGE_CACHE_SIZE-1); /* OK, are we completely out? */ err = -EIO; if (page->index >= end_index+1 || !offset) goto out;do_it: lock_kernel(); if (NFS_SERVER(inode)->wsize >= PAGE_CACHE_SIZE && !IS_SYNC(inode)) { err = nfs_writepage_async(NULL, inode, page, 0, offset); if (err >= 0) err = 0; } else { err = nfs_writepage_sync(NULL, inode, page, 0, offset); if (err == offset) err = 0; } unlock_kernel();out: UnlockPage(page); return err; }/* * Check whether the file range we want to write to is locked by * us. */static intregion_locked(struct inode *inode, struct nfs_page *req){ struct file_lock *fl; loff_t rqstart, rqend; /* Don't optimize writes if we don't use NLM */ if (NFS_SERVER(inode)->flags & NFS_MOUNT_NONLM) return 0; rqstart = page_offset(req->wb_page) + req->wb_offset; rqend = rqstart + req->wb_bytes; for (fl = inode->i_flock; fl; fl = fl->fl_next) { if (fl->fl_owner == current->files && (fl->fl_flags & FL_POSIX) && fl->fl_type == F_WRLCK && fl->fl_start <= rqstart && rqend <= fl->fl_end) { return 1; } } return 0;}/* * Insert a write request into an inode * Note: we sort the list in order to be able to optimize nfs_find_request() * & co. for the 'write append' case. For 2.5 we may want to consider * some form of hashing so as to perform well on random writes. */static inline voidnfs_inode_add_request(struct inode *inode, struct nfs_page *req){ struct list_head *pos, *head; unsigned long pg_idx = page_index(req->wb_page); if (!list_empty(&req->wb_hash)) return; if (!NFS_WBACK_BUSY(req)) printk(KERN_ERR "NFS: unlocked request attempted hashed!\n"); head = &inode->u.nfs_i.writeback; if (list_empty(head)) igrab(inode); list_for_each_prev(pos, head) { struct nfs_page *entry = nfs_inode_wb_entry(pos); if (page_index(entry->wb_page) < pg_idx) break; } inode->u.nfs_i.npages++; list_add(&req->wb_hash, pos); req->wb_count++;}/* * Insert a write request into an inode */static inline voidnfs_inode_remove_request(struct nfs_page *req){ struct inode *inode; spin_lock(&nfs_wreq_lock); if (list_empty(&req->wb_hash)) { spin_unlock(&nfs_wreq_lock); return; } if (!NFS_WBACK_BUSY(req)) printk(KERN_ERR "NFS: unlocked request attempted unhashed!\n"); inode = req->wb_inode; list_del(&req->wb_hash); INIT_LIST_HEAD(&req->wb_hash); inode->u.nfs_i.npages--; if ((inode->u.nfs_i.npages == 0) != list_empty(&inode->u.nfs_i.writeback)) printk(KERN_ERR "NFS: desynchronized value of nfs_i.npages.\n"); if (list_empty(&inode->u.nfs_i.writeback)) { spin_unlock(&nfs_wreq_lock); iput(inode); } else spin_unlock(&nfs_wreq_lock); nfs_release_request(req);}/* * Find a request */static inline struct nfs_page *_nfs_find_request(struct inode *inode, struct page *page){ struct list_head *head, *pos; unsigned long pg_idx = page_index(page); head = &inode->u.nfs_i.writeback; list_for_each_prev(pos, head) { struct nfs_page *req = nfs_inode_wb_entry(pos); unsigned long found_idx = page_index(req->wb_page); if (pg_idx < found_idx) continue; if (pg_idx != found_idx) break; req->wb_count++; return req; } return NULL;}static struct nfs_page *nfs_find_request(struct inode *inode, struct page *page){ struct nfs_page *req; spin_lock(&nfs_wreq_lock); req = _nfs_find_request(inode, page); spin_unlock(&nfs_wreq_lock); return req;}/* * Add a request to the inode's dirty list. */static inline voidnfs_mark_request_dirty(struct nfs_page *req){ struct inode *inode = req->wb_inode; spin_lock(&nfs_wreq_lock); nfs_list_add_request(req, &inode->u.nfs_i.dirty); inode->u.nfs_i.ndirty++; __nfs_del_lru(req); __nfs_add_lru(&NFS_SERVER(inode)->lru_dirty, req); spin_unlock(&nfs_wreq_lock); mark_inode_dirty(inode);}/* * Check if a request is dirty */static inline intnfs_dirty_request(struct nfs_page *req){ struct inode *inode = req->wb_inode; return !list_empty(&req->wb_list) && req->wb_list_head == &inode->u.nfs_i.dirty;}#ifdef CONFIG_NFS_V3/* * Add a request to the inode's commit list. */static inline voidnfs_mark_request_commit(struct nfs_page *req){ struct inode *inode = req->wb_inode; spin_lock(&nfs_wreq_lock); nfs_list_add_request(req, &inode->u.nfs_i.commit); inode->u.nfs_i.ncommit++; __nfs_del_lru(req); __nfs_add_lru(&NFS_SERVER(inode)->lru_commit, req); spin_unlock(&nfs_wreq_lock);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -