Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfs: Use new folio_queue data type and iterator instead of xarray iter

Make the netfs write-side routines use the new folio_queue struct to hold a
rolling buffer of folios, with the issuer adding folios at the tail and the
collector removing them from the head as they're processed instead of using
an xarray.

This will allow a subsequent patch to simplify the write collector.

The primary mark (as tested by folioq_is_marked()) is used to note if the
corresponding folio needs putting.

Signed-off-by: David Howells <dhowells@redhat.com>
cc: Jeff Layton <jlayton@kernel.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
Link: https://lore.kernel.org/r/20240814203850.2240469-16-dhowells@redhat.com/ # v2
Signed-off-by: Christian Brauner <brauner@kernel.org>

authored by

David Howells and committed by
Christian Brauner
cd0277ed c45ebd63

+150 -61
+8 -1
fs/netfs/internal.h
··· 7 7 8 8 #include <linux/slab.h> 9 9 #include <linux/seq_file.h> 10 + #include <linux/folio_queue.h> 10 11 #include <linux/netfs.h> 11 12 #include <linux/fscache.h> 12 13 #include <linux/fscache-cache.h> ··· 65 64 /* 66 65 * misc.c 67 66 */ 67 + int netfs_buffer_append_folio(struct netfs_io_request *rreq, struct folio *folio, 68 + bool needs_put); 69 + struct folio_queue *netfs_delete_buffer_head(struct netfs_io_request *wreq); 70 + void netfs_clear_buffer(struct netfs_io_request *rreq); 68 71 69 72 /* 70 73 * objects.c ··· 125 120 extern atomic_t netfs_n_wh_write_failed; 126 121 extern atomic_t netfs_n_wb_lock_skip; 127 122 extern atomic_t netfs_n_wb_lock_wait; 123 + extern atomic_t netfs_n_folioq; 128 124 129 125 int netfs_stats_show(struct seq_file *m, void *v); 130 126 ··· 159 153 loff_t start, 160 154 enum netfs_io_origin origin); 161 155 void netfs_reissue_write(struct netfs_io_stream *stream, 162 - struct netfs_io_subrequest *subreq); 156 + struct netfs_io_subrequest *subreq, 157 + struct iov_iter *source); 163 158 int netfs_advance_write(struct netfs_io_request *wreq, 164 159 struct netfs_io_stream *stream, 165 160 loff_t start, size_t len, bool to_eof);
+76
fs/netfs/misc.c
··· 8 8 #include <linux/swap.h> 9 9 #include "internal.h" 10 10 11 + /* 12 + * Append a folio to the rolling queue. 13 + */ 14 + int netfs_buffer_append_folio(struct netfs_io_request *rreq, struct folio *folio, 15 + bool needs_put) 16 + { 17 + struct folio_queue *tail = rreq->buffer_tail; 18 + unsigned int slot, order = folio_order(folio); 19 + 20 + if (WARN_ON_ONCE(!rreq->buffer && tail) || 21 + WARN_ON_ONCE(rreq->buffer && !tail)) 22 + return -EIO; 23 + 24 + if (!tail || folioq_full(tail)) { 25 + tail = kmalloc(sizeof(*tail), GFP_NOFS); 26 + if (!tail) 27 + return -ENOMEM; 28 + netfs_stat(&netfs_n_folioq); 29 + folioq_init(tail); 30 + tail->prev = rreq->buffer_tail; 31 + if (tail->prev) 32 + tail->prev->next = tail; 33 + rreq->buffer_tail = tail; 34 + if (!rreq->buffer) { 35 + rreq->buffer = tail; 36 + iov_iter_folio_queue(&rreq->io_iter, ITER_SOURCE, tail, 0, 0, 0); 37 + } 38 + rreq->buffer_tail_slot = 0; 39 + } 40 + 41 + rreq->io_iter.count += PAGE_SIZE << order; 42 + 43 + slot = folioq_append(tail, folio); 44 + /* Store the counter after setting the slot. */ 45 + smp_store_release(&rreq->buffer_tail_slot, slot); 46 + return 0; 47 + } 48 + 49 + /* 50 + * Delete the head of a rolling queue. 51 + */ 52 + struct folio_queue *netfs_delete_buffer_head(struct netfs_io_request *wreq) 53 + { 54 + struct folio_queue *head = wreq->buffer, *next = head->next; 55 + 56 + if (next) 57 + next->prev = NULL; 58 + netfs_stat_d(&netfs_n_folioq); 59 + kfree(head); 60 + wreq->buffer = next; 61 + return next; 62 + } 63 + 64 + /* 65 + * Clear out a rolling queue. 66 + */ 67 + void netfs_clear_buffer(struct netfs_io_request *rreq) 68 + { 69 + struct folio_queue *p; 70 + 71 + while ((p = rreq->buffer)) { 72 + rreq->buffer = p->next; 73 + for (int slot = 0; slot < folioq_nr_slots(p); slot++) { 74 + struct folio *folio = folioq_folio(p, slot); 75 + if (!folio) 76 + continue; 77 + if (folioq_is_marked(p, slot)) { 78 + trace_netfs_folio(folio, netfs_folio_trace_put); 79 + folio_put(folio); 80 + } 81 + } 82 + netfs_stat_d(&netfs_n_folioq); 83 + kfree(p); 84 + } 85 + } 86 + 11 87 /** 12 88 * netfs_dirty_folio - Mark folio dirty and pin a cache object for writeback 13 89 * @mapping: The mapping the folio belongs to.
+1
fs/netfs/objects.c
··· 141 141 } 142 142 kvfree(rreq->direct_bv); 143 143 } 144 + netfs_clear_buffer(rreq); 144 145 145 146 if (atomic_dec_and_test(&ictx->io_count)) 146 147 wake_up_var(&ictx->io_count);
+3 -1
fs/netfs/stats.c
··· 41 41 atomic_t netfs_n_wh_write_failed; 42 42 atomic_t netfs_n_wb_lock_skip; 43 43 atomic_t netfs_n_wb_lock_wait; 44 + atomic_t netfs_n_folioq; 44 45 45 46 int netfs_stats_show(struct seq_file *m, void *v) 46 47 { ··· 77 76 atomic_read(&netfs_n_wh_write), 78 77 atomic_read(&netfs_n_wh_write_done), 79 78 atomic_read(&netfs_n_wh_write_failed)); 80 - seq_printf(m, "Objs : rr=%u sr=%u wsc=%u\n", 79 + seq_printf(m, "Objs : rr=%u sr=%u foq=%u wsc=%u\n", 81 80 atomic_read(&netfs_n_rh_rreq), 82 81 atomic_read(&netfs_n_rh_sreq), 82 + atomic_read(&netfs_n_folioq), 83 83 atomic_read(&netfs_n_wh_wstream_conflict)); 84 84 seq_printf(m, "WbLock : skip=%u wait=%u\n", 85 85 atomic_read(&netfs_n_wb_lock_skip),
+44 -40
fs/netfs/write_collect.c
··· 82 82 } 83 83 84 84 /* 85 - * Get hold of a folio we have under writeback. We don't want to get the 86 - * refcount on it. 87 - */ 88 - static struct folio *netfs_writeback_lookup_folio(struct netfs_io_request *wreq, loff_t pos) 89 - { 90 - XA_STATE(xas, &wreq->mapping->i_pages, pos / PAGE_SIZE); 91 - struct folio *folio; 92 - 93 - rcu_read_lock(); 94 - 95 - for (;;) { 96 - xas_reset(&xas); 97 - folio = xas_load(&xas); 98 - if (xas_retry(&xas, folio)) 99 - continue; 100 - 101 - if (!folio || xa_is_value(folio)) 102 - kdebug("R=%08x: folio %lx (%llx) not present", 103 - wreq->debug_id, xas.xa_index, pos / PAGE_SIZE); 104 - BUG_ON(!folio || xa_is_value(folio)); 105 - 106 - if (folio == xas_reload(&xas)) 107 - break; 108 - } 109 - 110 - rcu_read_unlock(); 111 - 112 - if (WARN_ONCE(!folio_test_writeback(folio), 113 - "R=%08x: folio %lx is not under writeback\n", 114 - wreq->debug_id, folio->index)) { 115 - trace_netfs_folio(folio, netfs_folio_trace_not_under_wback); 116 - } 117 - return folio; 118 - } 119 - 120 - /* 121 85 * Unlock any folios we've finished with. 122 86 */ 123 87 static void netfs_writeback_unlock_folios(struct netfs_io_request *wreq, 124 88 unsigned long long collected_to, 125 89 unsigned int *notes) 126 90 { 91 + struct folio_queue *folioq = wreq->buffer; 92 + unsigned int slot = wreq->buffer_head_slot; 93 + 94 + if (slot >= folioq_nr_slots(folioq)) { 95 + folioq = netfs_delete_buffer_head(wreq); 96 + slot = 0; 97 + } 98 + 127 99 for (;;) { 128 100 struct folio *folio; 129 101 struct netfs_folio *finfo; 130 102 unsigned long long fpos, fend; 131 103 size_t fsize, flen; 132 104 133 - folio = netfs_writeback_lookup_folio(wreq, wreq->cleaned_to); 105 + folio = folioq_folio(folioq, slot); 106 + if (WARN_ONCE(!folio_test_writeback(folio), 107 + "R=%08x: folio %lx is not under writeback\n", 108 + wreq->debug_id, folio->index)) 109 + trace_netfs_folio(folio, netfs_folio_trace_not_under_wback); 134 110 135 111 fpos = folio_pos(folio); 136 112 fsize = folio_size(folio); ··· 131 155 wreq->cleaned_to = fpos + fsize; 132 156 *notes |= MADE_PROGRESS; 133 157 158 + /* Clean up the head folioq. If we clear an entire folioq, then 159 + * we can get rid of it provided it's not also the tail folioq 160 + * being filled by the issuer. 161 + */ 162 + folioq_clear(folioq, slot); 163 + slot++; 164 + if (slot >= folioq_nr_slots(folioq)) { 165 + if (READ_ONCE(wreq->buffer_tail) == folioq) 166 + break; 167 + folioq = netfs_delete_buffer_head(wreq); 168 + slot = 0; 169 + } 170 + 134 171 if (fpos + fsize >= collected_to) 135 172 break; 136 173 } 174 + 175 + wreq->buffer = folioq; 176 + wreq->buffer_head_slot = slot; 137 177 } 138 178 139 179 /* ··· 180 188 if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) 181 189 break; 182 190 if (__test_and_clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) { 191 + struct iov_iter source = subreq->io_iter; 192 + 193 + iov_iter_revert(&source, subreq->len - source.count); 183 194 __set_bit(NETFS_SREQ_RETRYING, &subreq->flags); 184 195 netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit); 185 - netfs_reissue_write(stream, subreq); 196 + netfs_reissue_write(stream, subreq, &source); 186 197 } 187 198 } 188 199 return; ··· 195 200 196 201 do { 197 202 struct netfs_io_subrequest *subreq = NULL, *from, *to, *tmp; 203 + struct iov_iter source; 198 204 unsigned long long start, len; 199 205 size_t part; 200 206 bool boundary = false; ··· 223 227 len += to->len; 224 228 } 225 229 230 + /* Determine the set of buffers we're going to use. Each 231 + * subreq gets a subset of a single overall contiguous buffer. 232 + */ 233 + source = from->io_iter; 234 + iov_iter_revert(&source, subreq->len - source.count); 235 + iov_iter_advance(&source, from->transferred); 236 + source.count = len; 237 + 226 238 /* Work through the sublist. */ 227 239 subreq = from; 228 240 list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) { ··· 253 249 boundary = true; 254 250 255 251 netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit); 256 - netfs_reissue_write(stream, subreq); 252 + netfs_reissue_write(stream, subreq, &source); 257 253 if (subreq == to) 258 254 break; 259 255 } ··· 320 316 boundary = false; 321 317 } 322 318 323 - netfs_reissue_write(stream, subreq); 319 + netfs_reissue_write(stream, subreq, &source); 324 320 if (!len) 325 321 break; 326 322
+13 -15
fs/netfs/write_issue.c
··· 213 213 * netfs_write_subrequest_terminated() when complete. 214 214 */ 215 215 static void netfs_do_issue_write(struct netfs_io_stream *stream, 216 - struct netfs_io_subrequest *subreq) 216 + struct netfs_io_subrequest *subreq, 217 + struct iov_iter *source) 217 218 { 218 219 struct netfs_io_request *wreq = subreq->rreq; 220 + size_t size = subreq->len - subreq->transferred; 219 221 220 222 _enter("R=%x[%x],%zx", wreq->debug_id, subreq->debug_index, subreq->len); 221 223 ··· 225 223 return netfs_write_subrequest_terminated(subreq, subreq->error, false); 226 224 227 225 // TODO: Use encrypted buffer 228 - if (test_bit(NETFS_RREQ_USE_IO_ITER, &wreq->flags)) { 229 - subreq->io_iter = wreq->io_iter; 230 - iov_iter_advance(&subreq->io_iter, 231 - subreq->start + subreq->transferred - wreq->start); 232 - iov_iter_truncate(&subreq->io_iter, 233 - subreq->len - subreq->transferred); 234 - } else { 235 - iov_iter_xarray(&subreq->io_iter, ITER_SOURCE, &wreq->mapping->i_pages, 236 - subreq->start + subreq->transferred, 237 - subreq->len - subreq->transferred); 238 - } 226 + subreq->io_iter = *source; 227 + iov_iter_advance(source, size); 228 + iov_iter_truncate(&subreq->io_iter, size); 239 229 240 230 trace_netfs_sreq(subreq, netfs_sreq_trace_submit); 241 231 stream->issue_write(subreq); 242 232 } 243 233 244 234 void netfs_reissue_write(struct netfs_io_stream *stream, 245 - struct netfs_io_subrequest *subreq) 235 + struct netfs_io_subrequest *subreq, 236 + struct iov_iter *source) 246 237 { 247 238 __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags); 248 - netfs_do_issue_write(stream, subreq); 239 + netfs_do_issue_write(stream, subreq, source); 249 240 } 250 241 251 242 static void netfs_issue_write(struct netfs_io_request *wreq, ··· 252 257 253 258 if (subreq->start + subreq->len > wreq->start + wreq->submitted) 254 259 WRITE_ONCE(wreq->submitted, subreq->start + subreq->len - wreq->start); 255 - netfs_do_issue_write(stream, subreq); 260 + netfs_do_issue_write(stream, subreq, &wreq->io_iter); 256 261 } 257 262 258 263 /* ··· 416 421 } else { 417 422 trace_netfs_folio(folio, netfs_folio_trace_store_plus); 418 423 } 424 + 425 + /* Attach the folio to the rolling buffer. */ 426 + netfs_buffer_append_folio(wreq, folio, false); 419 427 420 428 /* Move the submission point forward to allow for write-streaming data 421 429 * not starting at the front of the page. We don't do write-streaming
+4 -4
include/linux/netfs.h
··· 38 38 folio_set_private_2(folio); 39 39 } 40 40 41 - /* Marks used on xarray-based buffers */ 42 - #define NETFS_BUF_PUT_MARK XA_MARK_0 /* - Page needs putting */ 43 - #define NETFS_BUF_PAGECACHE_MARK XA_MARK_1 /* - Page needs wb/dirty flag wrangling */ 44 - 45 41 enum netfs_io_source { 46 42 NETFS_SOURCE_UNKNOWN, 47 43 NETFS_FILL_WITH_ZEROES, ··· 229 233 struct netfs_io_stream io_streams[2]; /* Streams of parallel I/O operations */ 230 234 #define NR_IO_STREAMS 2 //wreq->nr_io_streams 231 235 struct netfs_group *group; /* Writeback group being written back */ 236 + struct folio_queue *buffer; /* Head of I/O buffer */ 237 + struct folio_queue *buffer_tail; /* Tail of I/O buffer */ 232 238 struct iov_iter iter; /* Unencrypted-side iterator */ 233 239 struct iov_iter io_iter; /* I/O (Encrypted-side) iterator */ 234 240 void *netfs_priv; /* Private data for the netfs */ ··· 252 254 short error; /* 0 or error that occurred */ 253 255 enum netfs_io_origin origin; /* Origin of the request */ 254 256 bool direct_bv_unpin; /* T if direct_bv[] must be unpinned */ 257 + u8 buffer_head_slot; /* First slot in ->buffer */ 258 + u8 buffer_tail_slot; /* Next slot in ->buffer_tail */ 255 259 unsigned long long i_size; /* Size of the file */ 256 260 unsigned long long start; /* Start position */ 257 261 atomic64_t issued_to; /* Write issuer folio cursor */
+1
include/trace/events/netfs.h
··· 153 153 EM(netfs_folio_trace_mkwrite, "mkwrite") \ 154 154 EM(netfs_folio_trace_mkwrite_plus, "mkwrite+") \ 155 155 EM(netfs_folio_trace_not_under_wback, "!wback") \ 156 + EM(netfs_folio_trace_put, "put") \ 156 157 EM(netfs_folio_trace_read_gaps, "read-gaps") \ 157 158 EM(netfs_folio_trace_redirtied, "redirtied") \ 158 159 EM(netfs_folio_trace_store, "store") \