diff options
Diffstat (limited to 'fs/nfs/pagelist.c')
-rw-r--r-- | fs/nfs/pagelist.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/fs/nfs/pagelist.c b/fs/nfs/pagelist.c new file mode 100644 index 000000000000..4f1ba723848d --- /dev/null +++ b/fs/nfs/pagelist.c | |||
@@ -0,0 +1,309 @@ | |||
1 | /* | ||
2 | * linux/fs/nfs/pagelist.c | ||
3 | * | ||
4 | * A set of helper functions for managing NFS read and write requests. | ||
5 | * The main purpose of these routines is to provide support for the | ||
6 | * coalescing of several requests into a single RPC call. | ||
7 | * | ||
8 | * Copyright 2000, 2001 (c) Trond Myklebust <trond.myklebust@fys.uio.no> | ||
9 | * | ||
10 | */ | ||
11 | |||
12 | #include <linux/config.h> | ||
13 | #include <linux/slab.h> | ||
14 | #include <linux/file.h> | ||
15 | #include <linux/sunrpc/clnt.h> | ||
16 | #include <linux/nfs3.h> | ||
17 | #include <linux/nfs4.h> | ||
18 | #include <linux/nfs_page.h> | ||
19 | #include <linux/nfs_fs.h> | ||
20 | #include <linux/nfs_mount.h> | ||
21 | |||
22 | #define NFS_PARANOIA 1 | ||
23 | |||
24 | static kmem_cache_t *nfs_page_cachep; | ||
25 | |||
26 | static inline struct nfs_page * | ||
27 | nfs_page_alloc(void) | ||
28 | { | ||
29 | struct nfs_page *p; | ||
30 | p = kmem_cache_alloc(nfs_page_cachep, SLAB_KERNEL); | ||
31 | if (p) { | ||
32 | memset(p, 0, sizeof(*p)); | ||
33 | INIT_LIST_HEAD(&p->wb_list); | ||
34 | } | ||
35 | return p; | ||
36 | } | ||
37 | |||
38 | static inline void | ||
39 | nfs_page_free(struct nfs_page *p) | ||
40 | { | ||
41 | kmem_cache_free(nfs_page_cachep, p); | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * nfs_create_request - Create an NFS read/write request. | ||
46 | * @file: file descriptor to use | ||
47 | * @inode: inode to which the request is attached | ||
48 | * @page: page to write | ||
49 | * @offset: starting offset within the page for the write | ||
50 | * @count: number of bytes to read/write | ||
51 | * | ||
52 | * The page must be locked by the caller. This makes sure we never | ||
53 | * create two different requests for the same page, and avoids | ||
54 | * a possible deadlock when we reach the hard limit on the number | ||
55 | * of dirty pages. | ||
56 | * User should ensure it is safe to sleep in this function. | ||
57 | */ | ||
58 | struct nfs_page * | ||
59 | nfs_create_request(struct nfs_open_context *ctx, struct inode *inode, | ||
60 | struct page *page, | ||
61 | unsigned int offset, unsigned int count) | ||
62 | { | ||
63 | struct nfs_server *server = NFS_SERVER(inode); | ||
64 | struct nfs_page *req; | ||
65 | |||
66 | /* Deal with hard limits. */ | ||
67 | for (;;) { | ||
68 | /* try to allocate the request struct */ | ||
69 | req = nfs_page_alloc(); | ||
70 | if (req != NULL) | ||
71 | break; | ||
72 | |||
73 | /* Try to free up at least one request in order to stay | ||
74 | * below the hard limit | ||
75 | */ | ||
76 | if (signalled() && (server->flags & NFS_MOUNT_INTR)) | ||
77 | return ERR_PTR(-ERESTARTSYS); | ||
78 | yield(); | ||
79 | } | ||
80 | |||
81 | /* Initialize the request struct. Initially, we assume a | ||
82 | * long write-back delay. This will be adjusted in | ||
83 | * update_nfs_request below if the region is not locked. */ | ||
84 | req->wb_page = page; | ||
85 | atomic_set(&req->wb_complete, 0); | ||
86 | req->wb_index = page->index; | ||
87 | page_cache_get(page); | ||
88 | req->wb_offset = offset; | ||
89 | req->wb_pgbase = offset; | ||
90 | req->wb_bytes = count; | ||
91 | atomic_set(&req->wb_count, 1); | ||
92 | req->wb_context = get_nfs_open_context(ctx); | ||
93 | |||
94 | return req; | ||
95 | } | ||
96 | |||
97 | /** | ||
98 | * nfs_unlock_request - Unlock request and wake up sleepers. | ||
99 | * @req: | ||
100 | */ | ||
101 | void nfs_unlock_request(struct nfs_page *req) | ||
102 | { | ||
103 | if (!NFS_WBACK_BUSY(req)) { | ||
104 | printk(KERN_ERR "NFS: Invalid unlock attempted\n"); | ||
105 | BUG(); | ||
106 | } | ||
107 | smp_mb__before_clear_bit(); | ||
108 | clear_bit(PG_BUSY, &req->wb_flags); | ||
109 | smp_mb__after_clear_bit(); | ||
110 | wake_up_all(&req->wb_context->waitq); | ||
111 | nfs_release_request(req); | ||
112 | } | ||
113 | |||
114 | /** | ||
115 | * nfs_clear_request - Free up all resources allocated to the request | ||
116 | * @req: | ||
117 | * | ||
118 | * Release page resources associated with a write request after it | ||
119 | * has completed. | ||
120 | */ | ||
121 | void nfs_clear_request(struct nfs_page *req) | ||
122 | { | ||
123 | if (req->wb_page) { | ||
124 | page_cache_release(req->wb_page); | ||
125 | req->wb_page = NULL; | ||
126 | } | ||
127 | } | ||
128 | |||
129 | |||
130 | /** | ||
131 | * nfs_release_request - Release the count on an NFS read/write request | ||
132 | * @req: request to release | ||
133 | * | ||
134 | * Note: Should never be called with the spinlock held! | ||
135 | */ | ||
136 | void | ||
137 | nfs_release_request(struct nfs_page *req) | ||
138 | { | ||
139 | if (!atomic_dec_and_test(&req->wb_count)) | ||
140 | return; | ||
141 | |||
142 | #ifdef NFS_PARANOIA | ||
143 | BUG_ON (!list_empty(&req->wb_list)); | ||
144 | BUG_ON (NFS_WBACK_BUSY(req)); | ||
145 | #endif | ||
146 | |||
147 | /* Release struct file or cached credential */ | ||
148 | nfs_clear_request(req); | ||
149 | put_nfs_open_context(req->wb_context); | ||
150 | nfs_page_free(req); | ||
151 | } | ||
152 | |||
153 | /** | ||
154 | * nfs_list_add_request - Insert a request into a sorted list | ||
155 | * @req: request | ||
156 | * @head: head of list into which to insert the request. | ||
157 | * | ||
158 | * Note that the wb_list is sorted by page index in order to facilitate | ||
159 | * coalescing of requests. | ||
160 | * We use an insertion sort that is optimized for the case of appended | ||
161 | * writes. | ||
162 | */ | ||
163 | void | ||
164 | nfs_list_add_request(struct nfs_page *req, struct list_head *head) | ||
165 | { | ||
166 | struct list_head *pos; | ||
167 | |||
168 | #ifdef NFS_PARANOIA | ||
169 | if (!list_empty(&req->wb_list)) { | ||
170 | printk(KERN_ERR "NFS: Add to list failed!\n"); | ||
171 | BUG(); | ||
172 | } | ||
173 | #endif | ||
174 | list_for_each_prev(pos, head) { | ||
175 | struct nfs_page *p = nfs_list_entry(pos); | ||
176 | if (p->wb_index < req->wb_index) | ||
177 | break; | ||
178 | } | ||
179 | list_add(&req->wb_list, pos); | ||
180 | req->wb_list_head = head; | ||
181 | } | ||
182 | |||
183 | /** | ||
184 | * nfs_wait_on_request - Wait for a request to complete. | ||
185 | * @req: request to wait upon. | ||
186 | * | ||
187 | * Interruptible by signals only if mounted with intr flag. | ||
188 | * The user is responsible for holding a count on the request. | ||
189 | */ | ||
190 | int | ||
191 | nfs_wait_on_request(struct nfs_page *req) | ||
192 | { | ||
193 | struct inode *inode = req->wb_context->dentry->d_inode; | ||
194 | struct rpc_clnt *clnt = NFS_CLIENT(inode); | ||
195 | |||
196 | if (!NFS_WBACK_BUSY(req)) | ||
197 | return 0; | ||
198 | return nfs_wait_event(clnt, req->wb_context->waitq, !NFS_WBACK_BUSY(req)); | ||
199 | } | ||
200 | |||
201 | /** | ||
202 | * nfs_coalesce_requests - Split coalesced requests out from a list. | ||
203 | * @head: source list | ||
204 | * @dst: destination list | ||
205 | * @nmax: maximum number of requests to coalesce | ||
206 | * | ||
207 | * Moves a maximum of 'nmax' elements from one list to another. | ||
208 | * The elements are checked to ensure that they form a contiguous set | ||
209 | * of pages, and that the RPC credentials are the same. | ||
210 | */ | ||
211 | int | ||
212 | nfs_coalesce_requests(struct list_head *head, struct list_head *dst, | ||
213 | unsigned int nmax) | ||
214 | { | ||
215 | struct nfs_page *req = NULL; | ||
216 | unsigned int npages = 0; | ||
217 | |||
218 | while (!list_empty(head)) { | ||
219 | struct nfs_page *prev = req; | ||
220 | |||
221 | req = nfs_list_entry(head->next); | ||
222 | if (prev) { | ||
223 | if (req->wb_context->cred != prev->wb_context->cred) | ||
224 | break; | ||
225 | if (req->wb_context->lockowner != prev->wb_context->lockowner) | ||
226 | break; | ||
227 | if (req->wb_context->state != prev->wb_context->state) | ||
228 | break; | ||
229 | if (req->wb_index != (prev->wb_index + 1)) | ||
230 | break; | ||
231 | |||
232 | if (req->wb_pgbase != 0) | ||
233 | break; | ||
234 | } | ||
235 | nfs_list_remove_request(req); | ||
236 | nfs_list_add_request(req, dst); | ||
237 | npages++; | ||
238 | if (req->wb_pgbase + req->wb_bytes != PAGE_CACHE_SIZE) | ||
239 | break; | ||
240 | if (npages >= nmax) | ||
241 | break; | ||
242 | } | ||
243 | return npages; | ||
244 | } | ||
245 | |||
246 | /** | ||
247 | * nfs_scan_list - Scan a list for matching requests | ||
248 | * @head: One of the NFS inode request lists | ||
249 | * @dst: Destination list | ||
250 | * @idx_start: lower bound of page->index to scan | ||
251 | * @npages: idx_start + npages sets the upper bound to scan. | ||
252 | * | ||
253 | * Moves elements from one of the inode request lists. | ||
254 | * If the number of requests is set to 0, the entire address_space | ||
255 | * starting at index idx_start, is scanned. | ||
256 | * The requests are *not* checked to ensure that they form a contiguous set. | ||
257 | * You must be holding the inode's req_lock when calling this function | ||
258 | */ | ||
259 | int | ||
260 | nfs_scan_list(struct list_head *head, struct list_head *dst, | ||
261 | unsigned long idx_start, unsigned int npages) | ||
262 | { | ||
263 | struct list_head *pos, *tmp; | ||
264 | struct nfs_page *req; | ||
265 | unsigned long idx_end; | ||
266 | int res; | ||
267 | |||
268 | res = 0; | ||
269 | if (npages == 0) | ||
270 | idx_end = ~0; | ||
271 | else | ||
272 | idx_end = idx_start + npages - 1; | ||
273 | |||
274 | list_for_each_safe(pos, tmp, head) { | ||
275 | |||
276 | req = nfs_list_entry(pos); | ||
277 | |||
278 | if (req->wb_index < idx_start) | ||
279 | continue; | ||
280 | if (req->wb_index > idx_end) | ||
281 | break; | ||
282 | |||
283 | if (!nfs_lock_request(req)) | ||
284 | continue; | ||
285 | nfs_list_remove_request(req); | ||
286 | nfs_list_add_request(req, dst); | ||
287 | res++; | ||
288 | } | ||
289 | return res; | ||
290 | } | ||
291 | |||
292 | int nfs_init_nfspagecache(void) | ||
293 | { | ||
294 | nfs_page_cachep = kmem_cache_create("nfs_page", | ||
295 | sizeof(struct nfs_page), | ||
296 | 0, SLAB_HWCACHE_ALIGN, | ||
297 | NULL, NULL); | ||
298 | if (nfs_page_cachep == NULL) | ||
299 | return -ENOMEM; | ||
300 | |||
301 | return 0; | ||
302 | } | ||
303 | |||
304 | void nfs_destroy_nfspagecache(void) | ||
305 | { | ||
306 | if (kmem_cache_destroy(nfs_page_cachep)) | ||
307 | printk(KERN_INFO "nfs_page: not all structures were freed\n"); | ||
308 | } | ||
309 | |||