📄 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/malloc.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 NFS_PARANOIA 1#define NFSDBG_FACILITY NFSDBG_PAGECACHE/* * Spinlock */spinlock_t nfs_wreq_lock = SPIN_LOCK_UNLOCKED;static atomic_t nfs_nr_requests = ATOMIC_INIT(0);/* * 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_page_cachep;static kmem_cache_t *nfs_wdata_cachep;static __inline__ struct nfs_page *nfs_page_alloc(void){ struct nfs_page *p; p = kmem_cache_alloc(nfs_page_cachep, SLAB_KERNEL); if (p) { memset(p, 0, sizeof(*p)); INIT_LIST_HEAD(&p->wb_hash); INIT_LIST_HEAD(&p->wb_list); init_waitqueue_head(&p->wb_wait); } return p;}static __inline__ void nfs_page_free(struct nfs_page *p){ kmem_cache_free(nfs_page_cachep, p);}static __inline__ struct nfs_write_data *nfs_writedata_alloc(void){ struct nfs_write_data *p; p = kmem_cache_alloc(nfs_wdata_cachep, SLAB_NFS); 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 = nfs_file_cred(file); lock_kernel(); 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); unlock_kernel(); 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; int status; req = nfs_update_request(file, inode, page, offset, count); status = (IS_ERR(req)) ? PTR_ERR(req) : 0; if (status < 0) goto out; nfs_release_request(req); nfs_strategy(inode); 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: if (!PageError(page) && NFS_SERVER(inode)->rsize >= PAGE_CACHE_SIZE) { err = nfs_writepage_async(NULL, inode, page, 0, offset); if (err >= 0) goto out_ok; } err = nfs_writepage_sync(NULL, inode, page, 0, offset); if ( err == offset) {out_ok: err = 0; }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 */static inline voidnfs_inode_add_request(struct inode *inode, struct nfs_page *req){ if (!list_empty(&req->wb_hash)) return; if (!NFS_WBACK_BUSY(req)) printk(KERN_ERR "NFS: unlocked request attempted hashed!\n"); if (list_empty(&inode->u.nfs_i.writeback)) atomic_inc(&inode->i_count); inode->u.nfs_i.npages++; list_add(&req->wb_hash, &inode->u.nfs_i.writeback); 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)) iput(inode); if (!nfs_have_writebacks(inode) && !nfs_have_read(inode)) inode_remove_flushd(inode); 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, *next; head = &inode->u.nfs_i.writeback; next = head->next; while (next != head) { struct nfs_page *req = nfs_inode_wb_entry(next); next = next->next; if (page_index(req->wb_page) != page_index(page)) continue; 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;}/* * Insert a write request into a sorted list */void nfs_list_add_request(struct nfs_page *req, struct list_head *head){ struct list_head *prev; if (!list_empty(&req->wb_list)) { printk(KERN_ERR "NFS: Add to list failed!\n"); return; } if (!NFS_WBACK_BUSY(req)) printk(KERN_ERR "NFS: unlocked request attempted added to list!\n"); prev = head->prev; while (prev != head) { struct nfs_page *p = nfs_list_entry(prev); if (page_index(p->wb_page) < page_index(req->wb_page)) break; prev = prev->prev; } list_add(&req->wb_list, prev); req->wb_list_head = head;}/* * Insert a write request into an inode */void nfs_list_remove_request(struct nfs_page *req){ if (list_empty(&req->wb_list)) return; if (!NFS_WBACK_BUSY(req)) printk(KERN_ERR "NFS: unlocked request attempted removed from list!\n"); list_del(&req->wb_list); INIT_LIST_HEAD(&req->wb_list); req->wb_list_head = NULL;}/* * 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); if (list_empty(&req->wb_list)) { nfs_list_add_request(req, &inode->u.nfs_i.dirty); inode->u.nfs_i.ndirty++; } spin_unlock(&nfs_wreq_lock); /* * NB: the call to inode_schedule_scan() must lie outside the * spinlock since it can run flushd(). */ inode_schedule_scan(inode, req->wb_timeout);}/* * 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); if (list_empty(&req->wb_list)) { nfs_list_add_request(req, &inode->u.nfs_i.commit); inode->u.nfs_i.ncommit++; } spin_unlock(&nfs_wreq_lock); /* * NB: the call to inode_schedule_scan() must lie outside the * spinlock since it can run flushd(). */ inode_schedule_scan(inode, req->wb_timeout);}#endif/* * Create a write request. * Page must be locked by the caller. This makes sure we never create * two different requests for the same page, and avoids possible deadlock * when we reach the hard limit on the number of dirty pages. * It should be safe to sleep here. */struct nfs_page *nfs_create_request(struct file *file, struct inode *inode, struct page *page, unsigned int offset, unsigned int count){ struct nfs_reqlist *cache = NFS_REQUESTLIST(inode); struct nfs_page *req = NULL; long timeout; /* Deal with hard/soft limits. */ do { /* If we're over the global soft limit, wake up all requests */ if (atomic_read(&nfs_nr_requests) >= MAX_REQUEST_SOFT) { dprintk("NFS: hit soft limit (%d requests)\n", atomic_read(&nfs_nr_requests)); if (!cache->task) nfs_reqlist_init(NFS_SERVER(inode)); nfs_wake_flushd(); } /* If we haven't reached the local hard limit yet, * try to allocate the request struct */ if (atomic_read(&cache->nr_requests) < MAX_REQUEST_HARD) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -