Re: [PATCH 3/3] mm/swap: move swap_ops into file systems for file system-based swap
Chris Li <[email protected]> Wed, 22 Jul 2026 10:07:40 -0700
| Newsgroups | org.kernel.vger.linux-nfs,org.kernel.vger.linux-cifs,org.kvack.linux-mm |
|---|---|
| Message-ID | <CACePvbVFw6hzEHkaDPA2y4=kTweiKCiBmrD5pL3cFJ3AEUQ8rQ@mail.gmail.com> |
On Wed, Jul 22, 2026 at 6:07 AM Christoph Hellwig <[email protected]> wrote: > > Currently swap to and from file systems goes through two indirect calls > between the swap ops and the swap_rw method. Reduce this by directly > providing the swap_ops from the file system. > > For this refactor swap_fs_submit into a swap_fs_prepare_rw helper that > initializes the iov_iter on the callers stack so that file systems can > call it directly, and use that to initialize file system specific ops > in NFS and the SMB client, which then get passed to swap_fs_activate. > > Signed-off-by: Christoph Hellwig <[email protected]> I am very happy to see the direction this patch is going. Now I see how to get rid of the double indirection. You convinced me with flying colors. Acked-by: Chris Li <[email protected]> Chris > --- > Documentation/filesystems/locking.rst | 9 +--- > Documentation/filesystems/vfs.rst | 8 +--- > fs/nfs/direct.c | 20 --------- > fs/nfs/file.c | 42 ++++++++++++++++-- > fs/smb/client/file.c | 63 +++++++++++++++++---------- > include/linux/fs.h | 1 - > include/linux/nfs_fs.h | 1 - > include/linux/swap.h | 6 --- > include/linux/swap_ops.h | 5 +++ > mm/page_io.c | 34 +++------------ > 10 files changed, 93 insertions(+), 96 deletions(-) > > diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst > index 1a50d41a39a1..f58a8d7d5897 100644 > --- a/Documentation/filesystems/locking.rst > +++ b/Documentation/filesystems/locking.rst > @@ -266,7 +266,6 @@ prototypes:: > int (*error_remove_folio)(struct address_space *, struct folio *); > int (*swap_activate)(struct swap_info_struct *sis, struct file *f, sector_t *span) > int (*swap_deactivate)(struct file *); > - int (*swap_rw)(struct kiocb *iocb, struct iov_iter *iter); > > locking rules: > All except dirty_folio and free_folio may block > @@ -291,7 +290,6 @@ is_partially_uptodate: yes > error_remove_folio: yes > swap_activate: no > swap_deactivate: no > -swap_rw: yes, unlocks > ====================== ======================== ========= =============== > > ->write_begin(), ->write_end() and ->read_folio() may be called from > @@ -355,15 +353,12 @@ should perform any validation and preparation necessary to ensure that > writes can be performed with minimal memory allocation. It should call > add_swap_extent(), or the helper iomap_swapfile_activate(), and return > the number of extents added. If IO should be submitted through > -->swap_rw(), it should call swap_fs_activate, otherwise IO will be submitted > -directly to the block device ``sis->bdev``. > +the file system it should call swap_fs_activate, otherwise IO will be > +submitted directly to the block device ``sis->bdev``. > > ->swap_deactivate() will be called in the sys_swapoff() > path after ->swap_activate() returned success. > > -->swap_rw will be called for swap IO if swap_fs_activate was called by > -->swap_activate(). > - > file_lock_operations > ==================== > > diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst > index e7677423a20f..c437a342d4f3 100644 > --- a/Documentation/filesystems/vfs.rst > +++ b/Documentation/filesystems/vfs.rst > @@ -776,7 +776,6 @@ cache in your filesystem. The following members are defined: > int (*error_remove_folio)(struct mapping *mapping, struct folio *); > int (*swap_activate)(struct swap_info_struct *sis, struct file *f, sector_t *span) > int (*swap_deactivate)(struct file *); > - int (*swap_rw)(struct kiocb *iocb, struct iov_iter *iter); > }; > > ``read_folio`` > @@ -977,16 +976,13 @@ cache in your filesystem. The following members are defined: > can be performed with minimal memory allocation. It should call > add_swap_extent(), or the helper iomap_swapfile_activate(), and > return the number of extents added. If IO should be submitted > - through ->swap_rw(), it should call swap_fs_activate, otherwise IO will > - be submitted directly to the block device ``sis->bdev``. > + through the file system it should call swap_fs_activate, otherwise IO > + will be submitted directly to the block device ``sis->bdev``. > > ``swap_deactivate`` > Called during swapoff on files where swap_activate was > successful. > > -``swap_rw`` > - Called to read or write swap pages when swap_fs_activate was called. > - > The File Object > =============== > > diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c > index e626c72495e6..ccafdc1ce64d 100644 > --- a/fs/nfs/direct.c > +++ b/fs/nfs/direct.c > @@ -145,26 +145,6 @@ static void nfs_direct_file_adjust_size_locked(struct inode *inode, > } > } > > -/** > - * nfs_swap_rw - NFS address space operation for swap I/O > - * @iocb: target I/O control block > - * @iter: I/O buffer > - * > - * Perform IO to the swap-file. This is much like direct IO. > - */ > -int nfs_swap_rw(struct kiocb *iocb, struct iov_iter *iter) > -{ > - ssize_t ret; > - > - if (iov_iter_rw(iter) == READ) > - ret = nfs_file_direct_read(iocb, iter, true); > - else > - ret = nfs_file_direct_write(iocb, iter, true); > - if (ret < 0) > - return ret; > - return 0; > -} > - > static void nfs_direct_release_pages(struct page **pages, unsigned int npages) > { > unsigned int i; > diff --git a/fs/nfs/file.c b/fs/nfs/file.c > index 851d93a09988..e1bdd10b35f1 100644 > --- a/fs/nfs/file.c > +++ b/fs/nfs/file.c > @@ -29,9 +29,8 @@ > #include <linux/pagemap.h> > #include <linux/gfp.h> > #include <linux/rmap.h> > -#include <linux/swap.h> > #include <linux/compaction.h> > - > +#include <linux/swap_ops.h> > #include <linux/uaccess.h> > #include <linux/filelock.h> > > @@ -575,6 +574,38 @@ static int nfs_launder_folio(struct folio *folio) > return ret; > } > > +#ifdef CONFIG_SWAP > +static void nfs_swap_submit_write(struct swap_io_ctx *ctx) > +{ > + struct swap_iocb *sio = ctx->sio; > + struct iov_iter iter; > + int ret; > + > + swap_fs_prepare_rw(ctx, WRITE, &iter); > + ret = nfs_file_direct_write(&sio->iocb, &iter, true); > + if (ret != -EIOCBQUEUED) > + sio->iocb.ki_complete(&sio->iocb, ret); > +} > + > +static void nfs_swap_submit_read(struct swap_io_ctx *ctx) > +{ > + struct swap_iocb *sio = ctx->sio; > + struct iov_iter iter; > + int ret; > + > + swap_fs_prepare_rw(ctx, READ, &iter); > + ret = nfs_file_direct_read(&sio->iocb, &iter, true); > + if (ret != -EIOCBQUEUED) > + sio->iocb.ki_complete(&sio->iocb, ret); > +} > + > +static const struct swap_ops nfs_swap_ops = { > + .flags = SWAP_OPS_F_REQUIRE_NOFS, > + .submit_write = nfs_swap_submit_write, > + .submit_read = nfs_swap_submit_read, > + .can_merge = swap_fs_can_merge, > +}; > + > static int nfs_swap_activate(struct swap_info_struct *sis, struct file *file, > sector_t *span) > { > @@ -597,7 +628,7 @@ static int nfs_swap_activate(struct swap_info_struct *sis, struct file *file, > ret = rpc_clnt_swap_activate(clnt); > if (ret) > return ret; > - ret = swap_fs_activate(sis); > + ret = swap_fs_activate(sis, &nfs_swap_ops); > if (ret < 0) { > rpc_clnt_swap_deactivate(clnt); > return ret; > @@ -620,6 +651,10 @@ static void nfs_swap_deactivate(struct file *file) > if (cl->rpc_ops->disable_swap) > cl->rpc_ops->disable_swap(file_inode(file)); > } > +#else > +#define nfs_swap_activate NULL > +#define nfs_swap_deactivate NULL > +#endif /* CONFIG_SWAP */ > > const struct address_space_operations nfs_file_aops = { > .read_folio = nfs_read_folio, > @@ -636,7 +671,6 @@ const struct address_space_operations nfs_file_aops = { > .error_remove_folio = generic_error_remove_folio, > .swap_activate = nfs_swap_activate, > .swap_deactivate = nfs_swap_deactivate, > - .swap_rw = nfs_swap_rw, > }; > > /* > diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c > index 081bb16abeb4..190e24199fa1 100644 > --- a/fs/smb/client/file.c > +++ b/fs/smb/client/file.c > @@ -20,7 +20,7 @@ > #include <linux/delay.h> > #include <linux/mount.h> > #include <linux/slab.h> > -#include <linux/swap.h> > +#include <linux/swap_ops.h> > #include <linux/mm.h> > #include <asm/div64.h> > #include "cifsfs.h" > @@ -3351,6 +3351,38 @@ void cifs_oplock_break(struct work_struct *work) > cifs_done_oplock_break(cinode); > } > > +#ifdef CONFIG_SWAP > +static void cifs_swap_submit_write(struct swap_io_ctx *ctx) > +{ > + struct swap_iocb *sio = ctx->sio; > + struct iov_iter iter; > + int ret; > + > + swap_fs_prepare_rw(ctx, WRITE, &iter); > + ret = netfs_unbuffered_write_iter_locked(&sio->iocb, &iter, NULL); > + if (ret != -EIOCBQUEUED) > + sio->iocb.ki_complete(&sio->iocb, ret); > +} > + > +static void cifs_swap_submit_read(struct swap_io_ctx *ctx) > +{ > + struct swap_iocb *sio = ctx->sio; > + struct iov_iter iter; > + int ret; > + > + swap_fs_prepare_rw(ctx, READ, &iter); > + ret = netfs_unbuffered_read_iter_locked(&sio->iocb, &iter); > + if (ret != -EIOCBQUEUED) > + sio->iocb.ki_complete(&sio->iocb, ret); > +} > + > +static const struct swap_ops cifs_swap_ops = { > + .flags = SWAP_OPS_F_REQUIRE_NOFS, > + .submit_write = cifs_swap_submit_write, > + .submit_read = cifs_swap_submit_read, > + .can_merge = swap_fs_can_merge, > +}; > + > static int cifs_swap_activate(struct swap_info_struct *sis, > struct file *swap_file, sector_t *span) > { > @@ -3361,7 +3393,7 @@ static int cifs_swap_activate(struct swap_info_struct *sis, > > cifs_dbg(FYI, "swap activate\n"); > > - if (!swap_file->f_mapping->a_ops->swap_rw) > + if (swap_file->f_mapping->a_ops != &cifs_addr_ops) > /* Cannot support swap */ > return -EINVAL; > > @@ -3392,7 +3424,7 @@ static int cifs_swap_activate(struct swap_info_struct *sis, > * but we could add call to grab a byte range lock to prevent others > * from reading or writing the file > */ > - return swap_fs_activate(sis); > + return swap_fs_activate(sis, &cifs_swap_ops); > } > > static void cifs_swap_deactivate(struct file *file) > @@ -3408,26 +3440,10 @@ static void cifs_swap_deactivate(struct file *file) > > /* do we need to unpin (or unlock) the file */ > } > - > -/** > - * cifs_swap_rw - SMB3 address space operation for swap I/O > - * @iocb: target I/O control block > - * @iter: I/O buffer > - * > - * Perform IO to the swap-file. This is much like direct IO. > - */ > -static int cifs_swap_rw(struct kiocb *iocb, struct iov_iter *iter) > -{ > - ssize_t ret; > - > - if (iov_iter_rw(iter) == READ) > - ret = netfs_unbuffered_read_iter_locked(iocb, iter); > - else > - ret = netfs_unbuffered_write_iter_locked(iocb, iter, NULL); > - if (ret < 0) > - return ret; > - return 0; > -} > +#else > +#define cifs_swap_activate NULL > +#define cifs_swap_deactivate NULL > +#endif /* CONFIG_SWAP */ > > const struct address_space_operations cifs_addr_ops = { > .read_folio = netfs_read_folio, > @@ -3444,7 +3460,6 @@ const struct address_space_operations cifs_addr_ops = { > */ > .swap_activate = cifs_swap_activate, > .swap_deactivate = cifs_swap_deactivate, > - .swap_rw = cifs_swap_rw, > }; > > /* > diff --git a/include/linux/fs.h b/include/linux/fs.h > index 50ce731a2b78..87b5e9957c00 100644 > --- a/include/linux/fs.h > +++ b/include/linux/fs.h > @@ -438,7 +438,6 @@ struct address_space_operations { > int (*swap_activate)(struct swap_info_struct *sis, struct file *file, > sector_t *span); > void (*swap_deactivate)(struct file *file); > - int (*swap_rw)(struct kiocb *iocb, struct iov_iter *iter); > }; > > extern const struct address_space_operations empty_aops; > diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h > index ec17e602c979..764056498eba 100644 > --- a/include/linux/nfs_fs.h > +++ b/include/linux/nfs_fs.h > @@ -548,7 +548,6 @@ static inline const struct cred *nfs_file_cred(struct file *file) > /* > * linux/fs/nfs/direct.c > */ > -int nfs_swap_rw(struct kiocb *iocb, struct iov_iter *iter); > ssize_t nfs_file_direct_read(struct kiocb *iocb, > struct iov_iter *iter, bool swap); > ssize_t nfs_file_direct_write(struct kiocb *iocb, > diff --git a/include/linux/swap.h b/include/linux/swap.h > index 0544b2ec4c56..d8584f53a6e4 100644 > --- a/include/linux/swap.h > +++ b/include/linux/swap.h > @@ -334,8 +334,6 @@ extern void __meminit kswapd_run(int nid); > extern void __meminit kswapd_stop(int nid); > > #ifdef CONFIG_SWAP > - > -int swap_fs_activate(struct swap_info_struct *sis); > int add_swap_extent(struct swap_info_struct *sis, unsigned long start_page, > unsigned long nr_pages, sector_t start_block); > int generic_swapfile_activate(struct swap_info_struct *, struct file *, > @@ -461,10 +459,6 @@ static inline bool folio_free_swap(struct folio *folio) > return false; > } > > -static inline int swap_fs_activate(struct swap_info_struct *sis) > -{ > - return -EINVAL; > -} > static inline int add_swap_extent(struct swap_info_struct *sis, > unsigned long start_page, > unsigned long nr_pages, sector_t start_block) > diff --git a/include/linux/swap_ops.h b/include/linux/swap_ops.h > index e92b4f532604..57ac6c703f68 100644 > --- a/include/linux/swap_ops.h > +++ b/include/linux/swap_ops.h > @@ -36,4 +36,9 @@ struct swap_ops { > void (*submit_read)(struct swap_io_ctx *ctx); > }; > > +void swap_fs_prepare_rw(struct swap_io_ctx *ctx, int rw, struct iov_iter *iter); > +bool swap_fs_can_merge(struct folio *folio, struct folio *prev_folio, > + size_t prev_folio_size, int rw); > +int swap_fs_activate(struct swap_info_struct *sis, const struct swap_ops *ops); > + > #endif /* _MM_SWAP_OPS_H */ > diff --git a/mm/page_io.c b/mm/page_io.c > index e741e67d6592..88962571cb93 100644 > --- a/mm/page_io.c > +++ b/mm/page_io.c > @@ -650,11 +650,9 @@ const struct swap_ops swap_bdev_ops = { > .can_merge = swap_bdev_can_merge, > }; > > -static void swap_fs_submit(struct swap_io_ctx *ctx, int rw) > +void swap_fs_prepare_rw(struct swap_io_ctx *ctx, int rw, struct iov_iter *iter) > { > struct swap_iocb *sio = ctx->sio; > - struct iov_iter iter; > - int ret; > > init_sync_kiocb(&sio->iocb, ctx->sis->swap_file); > sio->iocb.ki_pos = swap_dev_pos(bvec_folio(&sio->bvecs[0])->swap); > @@ -663,40 +661,22 @@ static void swap_fs_submit(struct swap_io_ctx *ctx, int rw) > else > sio->iocb.ki_complete = swap_fs_read_complete; > > - iov_iter_bvec(&iter, rw == WRITE ? ITER_SOURCE : ITER_DEST, > + iov_iter_bvec(iter, rw == WRITE ? ITER_SOURCE : ITER_DEST, > sio->bvecs, sio->nr_bvecs, sio->len); > - ret = sio->iocb.ki_filp->f_mapping->a_ops->swap_rw(&sio->iocb, &iter); > - if (ret != -EIOCBQUEUED) > - sio->iocb.ki_complete(&sio->iocb, ret); > } > +EXPORT_SYMBOL_GPL(swap_fs_prepare_rw); > > -static void swap_fs_submit_write(struct swap_io_ctx *ctx) > -{ > - swap_fs_submit(ctx, WRITE); > -} > - > -static void swap_fs_submit_read(struct swap_io_ctx *ctx) > -{ > - swap_fs_submit(ctx, READ); > -} > - > -static bool swap_fs_can_merge(struct folio *folio, struct folio *prev_folio, > +bool swap_fs_can_merge(struct folio *folio, struct folio *prev_folio, > size_t prev_folio_size, int rw) > { > return swap_dev_pos(folio->swap) == > swap_dev_pos(prev_folio->swap) + prev_folio_size; > } > +EXPORT_SYMBOL_GPL(swap_fs_can_merge); > > -static const struct swap_ops swap_fs_ops = { > - .flags = SWAP_OPS_F_REQUIRE_NOFS, > - .submit_write = swap_fs_submit_write, > - .submit_read = swap_fs_submit_read, > - .can_merge = swap_fs_can_merge, > -}; > - > -int swap_fs_activate(struct swap_info_struct *sis) > +int swap_fs_activate(struct swap_info_struct *sis, const struct swap_ops *ops) > { > - sis->ops = &swap_fs_ops; > + sis->ops = ops; > return add_swap_extent(sis, 0, sis->max, 0); > } > EXPORT_SYMBOL_GPL(swap_fs_activate); > -- > 2.53.0 >