mbox series

[00/14] Small step toward KSM for file back page.

Message ID 20201007010603.3452458-1-jglisse@redhat.com (mailing list archive)
Headers show
Series Small step toward KSM for file back page. | expand

Message

Jerome Glisse Oct. 7, 2020, 1:05 a.m. UTC
From: Jérôme Glisse <jglisse@redhat.com>

This patchset is a step toward a larger objective: generalize existing
KSM into a mechanism allowing exclusive write control for a page; either
anonymous memory (like KSM today) or file back page (modulo GUP which
would block that like it does today for KSM).

Exclusive write control page allow multiple different features to be
implemented:

    - KSM kernel share page, ie de-duplicate pages with same content
      to use a single page for all. From many pages to one read only
      page. We have that today for anonymous memory only. The overall
      patchset extends it to file back page ie sharing the same struct
      page accross different file or accross same file. This can be
      be usefull for containers for instance ... or for deduplication
      in same file.

    - NUMA duplication, duplicate a page into multiple local read only
      copy. This is the opposite of KSM in a sense, instead of saving
      memory. Using more memory to get better memory access performance.
      For instance duplicating libc code to local node copy; or big
      read only dataset duplicated on each nodes.

    - Exclusive write access, owner of page write protection is the only
      that can write to the page (and must still abide by fs rules for
      fileback page in respect to writeback...). One use case is for
      fast atomic operation using non atomic instruction. For instance
      by PCIE device, if all mapping of the page is read only then PCIE
      device driver knows device write can not race with CPU write. This
      is a performance optimization.

    - Use main memory as cache for persistent memory ie the page is
      read only and write will trigger callback and different strategy
      can be use like write combining (ie acumulating change in main
      memory before copying to persistent memory).

Like KSM today such protection can be broken at _any_ time. The owner
of the protection gets a callback (KSM code for instance get calls) so
that it can unprotect the page. Breaking protection should not block
and must happens quickly (like KSM code today).


Convertion of existing KSM into generic mechanism is straightforward
for anonymous page (just factorize out KSM code that deals with page
protection from KSM code that deals with de-duplication).


The big changes here is the support for file back pages. The idea to
achieve it is that we almost always have the mapping a page belongs
to within the call stack as we operate on such page either from:
  - Syscall/kernel against a file (file -> inode -> mapping).
  - Syscall/kernel against virtual address (vma -> file -> mapping).
  - Write back for a given mapping (mapping -> pages).

They are few exceptions:
  - Reclaim, but reclaim does not care about mapping. Reclaim wants to
    unmap page to free it up. So all we have to do is provide special
    path to do that just like KSM does today for anonymous pages.

  - Compaction, again we do not care about the mapping for compaction.
    All we need is way to move page (ie migrate).

  - Flush data cache on some architecture the cache line are tag with
    the virtual address so when flushing a page we need to find all of
    its virtual addresses. Again we do not care about the mapping, we
    just need a way to find all virtual address in all process pointing
    to the page.

  - GUP user that want to set a page dirty. This is easy, we just do
    not allow protection to work on GUPed page and GUP also will break
    the protection. There is just no way to synchronize with GUP user
    as they violate all mm and fs rules anyway.

  - Some proc fs and other memory debugging API. Here we do not care
    about the mapping but about the page states. Also some of those
    API works on virtual address for which we can easily get the vma
    and thus the mapping.


So when we have the mapping for a page from the context and not from
page->mapping then we can use it as a key to lookup private and index
fields value for the page.

To avoid any regression risk, only protected pages sees their fields
overloaded. It means that if you are not using the page protection then
the page->mapping, page->private and page->index all stays as they are
today. Also page->mapping is always use as canonical place to lookup
the page mapping for unprotected page so that any existing code will
keep working as it does today even if the mapping we get from the
context does not match the page->mapping. More on this below.


Overview:
=========

The core idea is pretty dumb, it is just about passing new mapping
argument to every function that get a page and need the mapping
corresponding to that page. Most of the changes are done through
semantic patches. Adding new function argument on itself does not
bring any risk. The risk is in making sure that the mapping we pass as
function argument is the one corresponding to the page. To avoid any
regression we keep using page->mapping as the canonical mapping even
if it does not match what we get as a new argument ie:

Today:
    foo(struct page *page)
    {
        ... local variable declaration ...
        struct address_space *mapping = page->mapping;

        ... use mapping or inode from mapping ...
    }

After:
    foo(struct page *page, struct address_space *mapping)
    {
        ... local variable declaration ...

        mapping = fs_page_mapping(page, mapping);

        ... use mapping or inode from mapping ...
    }

With:
    struct address_space *fs_page_mapping(struct page *page,
                              struct address_space *mapping)
    {
        if (!PageProtected(page)) {
            WARN_ON(page->mapping != mapping);
            return page->mapping;
        }
        return mapping;
    }

So as long as you are not using page protection the end result before
and after the patchset is the same ie the same mapping value will be
use.


Strategy for passing down mapping of page:
==========================================

To make it easier to review that correct mapping is pass down, changes
are split in 3 steps:
    - First adds new __mapping argument to all functions that will need
      it and do not have it already (or inode as we can get mapping from
      inode). The argument is never use and thus in this step call site
      pass MAPPING_NULL which is just macro that alias to NULL but is
      easier to grep for.

    - Second replace MAPPING_NULL with the __mapping argument whereever
      possible. The rational is that if a function is passing down the
      mapping and already have it as a function argument then the real
      difficulty is not in that function but in caller of that function
      where we must ascertain that the mapping we pass down is the
      correct one.

      Replace any local MAPPING_NULL with local mapping variable or
      inode (some convertion are done manualy when they are multiple
      possible choice or when the automatic convertion would be wrong).

      At the end of this step there should not be any MAPPING_NULL left
      anywhere.

    - Finaly we replace any local mapping variable with __mapping and
      we add a check ie going from:
        foo(struct address_mapping *__mapping, struct page *page, ...){
            struct address_space *mapping = page->mapping;
            ...
        }
     To:
        foo(struct address_mapping *mapping, struct page *page, ...) {
            ...
            mapping = fs_page_mapping(mapping, page);
        }
     With:
        fs_page_mapping(struct address_mapping *mapping,
                        struct page *page) {
            if (!PageProtected(page))
                return page->mapping;
            return mapping;
        }

     Do the same for local inode that are looked up from page->mapping.

     So as long as you do not use the protection mechanism you will
     use the same mapping/inode as today.

I hope that over enough time we can build confidence so that people
start using page protection and KSM for file back pages without any
fear of regression. But in the meantime as explained in above section,
code will keep working as it does today if page is not protected.


----------------------------------------------------------------------


The present patchset just add mapping argument to the various vfs call-
backs. It does not make use of that new parameter to avoid regression.
I am posting this whole things as small contain patchset as it is rather
big and i would like to make progress step by step.


----------------------------------------------------------------------
FAQ:
----------------------------------------------------------------------

Why multiple pass in the coccinelle patches ?

    Two reasons for this:
      - To modify callback you first need to identify callback. It is
        common enough to have callback define in one file and set as
        callback in another. For that to work you would have one pass
        to identify all function use as a callback. Then anoter pass
        executed for each of the callback identified in the first pass
        to update each of them (like adding a new arugment).

      - Run time, some of the coccinelle patch are complex enough that
        they have a long runtime so to avoid spending hours on semantic
        patching it is better to run a first pass (with very simple
        semantic patch) that identify all the files that will need to
        be updated and then a second pass on each of the file with the
        actual update.


Why wrapping page->flags/index/private/mapping ?
    A protected page can (is not necessary the case) alias to multiple
    original page (also see "page alias" in glossary). Each alias do
    correspond to a different original page which had a different file
    (and thus mapping), likely at a different offset (page->index) and
    with different properties (page->flags and page->private). So we
    need to keep around all the original informations and we use the
    page and mapping (or anon_vma) as key to lookup alias and get the
    correct information for it.


Why keeping page->private for each page alias ?
    For KSM or NUMA duplication we expect the page to be uptodate and
    clean and thus we should be able to free buffers (or fs specific
    struct) and thus no need to keep around private.

    However for page exclusive access we will not alias the page with
    any other page and we might want to keep the page in a writeable
    state and thus we need to keep the page->private around.


Why a lot of patch move local variable out of declaration ?
    Many patch modify code from:
        foo(...) {
            some_type1 some_name1 = some_arg1;
            some_type2 some_name2 = some_arg2 || some_name1;
            DECLARATION...

            STATEMENT...
        }

    To:
        foo(...) {
            some_type1 some_name1;
            some_type2 some_name2;
            DECLARATION...

            some_name1 = some_arg1;
            some_name2 = some_arg2 || some_name1;

            STATEMENT...
        }

    This is done so that we can add a test to check if mapping/inode
    we get in argument does match the page->mapping field. We need to
    add such test after all declaration but before any assignment to
    local variable. Hence why declaration initializer need to become
    regular statement after all declarations.

    Saddly no way around that and this lead to a lot of code churn.


----------------------------------------------------------------------
Glossary:
----------------------------------------------------------------------

page alias
    A protected page can alias with different file and/or vma (and thus
    anon_vma). A page alias is just the information that correspond to
    each of the file/vma for that that.

    For instance let say that PA was a file back page it had:
        PA->mapping == mapping corresponding to the file PA belong
        PA->index   == offset into the file for PA
        PA->private == fs specific information (like buffer_head)
        PA->flags   == fs/mm specific informations

    PB was a anonymous page, it had:
        PB->mapping == anon_vma for the vma into which PB is map
        PB->index   == offset into the vma
        PB->private == usualy 0 but can contain a swap entry
        PB->flags   == fs/mm specific informations

    Now if PA and PB are merge together into a protect page then the
    protected page will have an alias struct for each of the original
    page ie:
        struct page_alias {
            struct list list;
            unsigned long flags;
            unsigned long index;
            void *mapping;
            void *private;
        };
        struct pxa {
            struct list aliases;
            ...
        };

    So now for the PXA (protected page) that replace PA and PB we have:
        PP->mapping == pointer to struct pxa for the page

    Note that PP->index/private are undefine for now but could be use
    for optimization like storing the most recent alias lookup.

    And the alias list will have one entry for PA and one for PB:
        PA_alias.mapping = PA.mapping
        PA_alias.private = PA.private
        PA_alias.index   = PA.index
        PA_alias.flags   = PA.flags

        PB_alias.mapping = PB.mapping
        PB_alias.private = PB.private
        PB_alias.index   = PB.index
        PB_alias.flags   = PB.flags


raw page
    A page that is not use as anonymous or file back. Many cases, can
    be a free page, a page use by a device driver or some kernel
    features. Each case can sometimes make use of the various struct
    page fields (mapping, private, index, lru, ...).

    To get proper education this patchset wrap usage of those field in
    those code with raw_page*() helpers. This also make it clears what
    kind of page we expect to see in such driver/feature.


Cc: linux-mm@kvack.org
Cc: linux-fsdevel@vger.kernel.org
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Tejun Heo <tj@kernel.org>
Cc: Jan Kara <jack@suse.cz>
Cc: Josef Bacik <jbacik@fb.com>


Jérôme Glisse (14):
  mm/pxa: page exclusive access add header file for all helpers.
  fs: define filler_t as a function pointer type
  fs: directly use a_ops->freepage() instead of a local copy of it.
  mm: add struct address_space to readpage() callback
  mm: add struct address_space to writepage() callback
  mm: add struct address_space to set_page_dirty() callback
  mm: add struct address_space to invalidatepage() callback
  mm: add struct address_space to releasepage() callback
  mm: add struct address_space to freepage() callback
  mm: add struct address_space to putback_page() callback
  mm: add struct address_space to launder_page() callback
  mm: add struct address_space to is_partially_uptodate() callback
  mm: add struct address_space to isolate_page() callback
  mm: add struct address_space to is_dirty_writeback() callback

 drivers/gpu/drm/i915/gem/i915_gem_shmem.c |  3 +-
 drivers/video/fbdev/core/fb_defio.c       |  3 +-
 fs/9p/vfs_addr.c                          | 24 ++++++---
 fs/adfs/inode.c                           |  6 ++-
 fs/affs/file.c                            |  9 ++--
 fs/affs/symlink.c                         |  4 +-
 fs/afs/dir.c                              | 15 ++++--
 fs/afs/file.c                             | 18 ++++---
 fs/afs/internal.h                         |  7 +--
 fs/afs/write.c                            |  9 ++--
 fs/befs/linuxvfs.c                        | 13 +++--
 fs/bfs/file.c                             |  6 ++-
 fs/block_dev.c                            | 10 ++--
 fs/btrfs/ctree.h                          |  3 +-
 fs/btrfs/disk-io.c                        | 16 +++---
 fs/btrfs/extent_io.c                      |  5 +-
 fs/btrfs/file.c                           |  2 +-
 fs/btrfs/free-space-cache.c               |  2 +-
 fs/btrfs/inode.c                          | 24 +++++----
 fs/btrfs/ioctl.c                          |  2 +-
 fs/btrfs/relocation.c                     |  2 +-
 fs/btrfs/send.c                           |  2 +-
 fs/buffer.c                               | 14 +++--
 fs/cachefiles/rdwr.c                      |  6 +--
 fs/ceph/addr.c                            | 26 +++++----
 fs/cifs/cifssmb.c                         |  2 +-
 fs/cifs/file.c                            | 15 ++++--
 fs/coda/symlink.c                         |  4 +-
 fs/cramfs/inode.c                         |  3 +-
 fs/ecryptfs/mmap.c                        |  8 ++-
 fs/efs/inode.c                            |  3 +-
 fs/efs/symlink.c                          |  4 +-
 fs/erofs/data.c                           |  4 +-
 fs/erofs/super.c                          |  8 +--
 fs/erofs/zdata.c                          |  4 +-
 fs/exfat/inode.c                          |  6 ++-
 fs/ext2/inode.c                           | 11 ++--
 fs/ext4/inode.c                           | 35 +++++++-----
 fs/f2fs/checkpoint.c                      |  8 +--
 fs/f2fs/data.c                            | 20 ++++---
 fs/f2fs/f2fs.h                            |  8 +--
 fs/f2fs/node.c                            |  8 +--
 fs/fat/inode.c                            |  6 ++-
 fs/freevxfs/vxfs_immed.c                  |  6 ++-
 fs/freevxfs/vxfs_subr.c                   |  6 ++-
 fs/fuse/dir.c                             |  4 +-
 fs/fuse/file.c                            |  9 ++--
 fs/gfs2/aops.c                            | 29 ++++++----
 fs/gfs2/inode.h                           |  3 +-
 fs/gfs2/meta_io.c                         |  4 +-
 fs/hfs/inode.c                            |  9 ++--
 fs/hfsplus/inode.c                        | 10 ++--
 fs/hostfs/hostfs_kern.c                   |  6 ++-
 fs/hpfs/file.c                            |  6 ++-
 fs/hpfs/namei.c                           |  4 +-
 fs/hugetlbfs/inode.c                      |  3 +-
 fs/iomap/buffered-io.c                    | 15 +++---
 fs/iomap/seek.c                           |  2 +-
 fs/isofs/compress.c                       |  3 +-
 fs/isofs/inode.c                          |  3 +-
 fs/isofs/rock.c                           |  4 +-
 fs/jffs2/file.c                           | 11 ++--
 fs/jffs2/os-linux.h                       |  3 +-
 fs/jfs/inode.c                            |  6 ++-
 fs/jfs/jfs_metapage.c                     | 15 ++++--
 fs/libfs.c                                | 13 +++--
 fs/minix/inode.c                          |  6 ++-
 fs/mpage.c                                |  2 +-
 fs/nfs/dir.c                              | 14 ++---
 fs/nfs/file.c                             | 16 +++---
 fs/nfs/read.c                             |  6 ++-
 fs/nfs/symlink.c                          |  7 +--
 fs/nfs/write.c                            |  9 ++--
 fs/nilfs2/inode.c                         | 11 ++--
 fs/nilfs2/mdt.c                           |  3 +-
 fs/nilfs2/page.c                          |  2 +-
 fs/nilfs2/segment.c                       |  4 +-
 fs/ntfs/aops.c                            | 10 ++--
 fs/ntfs/file.c                            |  2 +-
 fs/ocfs2/aops.c                           |  9 ++--
 fs/ocfs2/symlink.c                        |  4 +-
 fs/omfs/file.c                            |  6 ++-
 fs/orangefs/inode.c                       | 38 +++++++------
 fs/qnx4/inode.c                           |  3 +-
 fs/qnx6/inode.c                           |  3 +-
 fs/reiserfs/inode.c                       | 20 ++++---
 fs/romfs/super.c                          |  3 +-
 fs/squashfs/file.c                        |  4 +-
 fs/squashfs/symlink.c                     |  4 +-
 fs/sysv/itree.c                           |  6 ++-
 fs/ubifs/file.c                           | 21 +++++---
 fs/udf/file.c                             |  7 ++-
 fs/udf/inode.c                            |  8 +--
 fs/udf/symlink.c                          |  4 +-
 fs/ufs/inode.c                            |  6 ++-
 fs/vboxsf/file.c                          |  6 ++-
 fs/xfs/xfs_aops.c                         |  6 +--
 fs/zonefs/super.c                         |  6 ++-
 include/linux/balloon_compaction.h        | 11 ++--
 include/linux/buffer_head.h               | 12 +++--
 include/linux/fs.h                        | 38 +++++++------
 include/linux/iomap.h                     | 15 +++---
 include/linux/mm.h                        | 11 +++-
 include/linux/nfs_fs.h                    |  5 +-
 include/linux/page-xa.h                   | 66 +++++++++++++++++++++++
 include/linux/pagemap.h                   |  6 +--
 include/linux/swap.h                      | 10 ++--
 mm/balloon_compaction.c                   |  5 +-
 mm/filemap.c                              | 33 ++++++------
 mm/migrate.c                              |  6 +--
 mm/page-writeback.c                       | 16 +++---
 mm/page_io.c                              | 11 ++--
 mm/readahead.c                            |  6 +--
 mm/shmem.c                                |  5 +-
 mm/truncate.c                             |  9 ++--
 mm/vmscan.c                               | 12 ++---
 mm/z3fold.c                               |  6 ++-
 mm/zsmalloc.c                             |  6 ++-
 118 files changed, 722 insertions(+), 385 deletions(-)
 create mode 100644 include/linux/page-xa.h

Comments

Matthew Wilcox (Oracle) Oct. 7, 2020, 3:20 a.m. UTC | #1
On Tue, Oct 06, 2020 at 09:05:49PM -0400, jglisse@redhat.com wrote:
> The present patchset just add mapping argument to the various vfs call-
> backs. It does not make use of that new parameter to avoid regression.
> I am posting this whole things as small contain patchset as it is rather
> big and i would like to make progress step by step.

Well, that's the problem.  This patch set is gigantic and unreviewable.
And it has no benefits.  The idea you present here was discussed at
LSFMM in Utah and I recall absolutely nobody being in favour of it.
You claim many wonderful features will be unlocked by this, but I think
they can all be achieved without doing any of this very disruptive work.

>  118 files changed, 722 insertions(+), 385 deletions(-)

mmm.
Jerome Glisse Oct. 7, 2020, 2:48 p.m. UTC | #2
On Wed, Oct 07, 2020 at 04:20:13AM +0100, Matthew Wilcox wrote:
> On Tue, Oct 06, 2020 at 09:05:49PM -0400, jglisse@redhat.com wrote:
> > The present patchset just add mapping argument to the various vfs call-
> > backs. It does not make use of that new parameter to avoid regression.
> > I am posting this whole things as small contain patchset as it is rather
> > big and i would like to make progress step by step.
> 
> Well, that's the problem.  This patch set is gigantic and unreviewable.
> And it has no benefits.  The idea you present here was discussed at
> LSFMM in Utah and I recall absolutely nobody being in favour of it.
> You claim many wonderful features will be unlocked by this, but I think
> they can all be achieved without doing any of this very disruptive work.

You have any ideas on how to achieve them without such change ? I will
be more than happy for a simpler solution but i fail to see how you can
work around the need for a pointer inside struct page. Given struct
page can not grow it means you need to be able to overload one of the
existing field, at least i do not see any otherway.

Cheers,
Jérôme
Matthew Wilcox (Oracle) Oct. 7, 2020, 5:05 p.m. UTC | #3
On Wed, Oct 07, 2020 at 10:48:35AM -0400, Jerome Glisse wrote:
> On Wed, Oct 07, 2020 at 04:20:13AM +0100, Matthew Wilcox wrote:
> > On Tue, Oct 06, 2020 at 09:05:49PM -0400, jglisse@redhat.com wrote:
> > > The present patchset just add mapping argument to the various vfs call-
> > > backs. It does not make use of that new parameter to avoid regression.
> > > I am posting this whole things as small contain patchset as it is rather
> > > big and i would like to make progress step by step.
> > 
> > Well, that's the problem.  This patch set is gigantic and unreviewable.
> > And it has no benefits.  The idea you present here was discussed at
> > LSFMM in Utah and I recall absolutely nobody being in favour of it.
> > You claim many wonderful features will be unlocked by this, but I think
> > they can all be achieved without doing any of this very disruptive work.
> 
> You have any ideas on how to achieve them without such change ? I will
> be more than happy for a simpler solution but i fail to see how you can
> work around the need for a pointer inside struct page. Given struct
> page can not grow it means you need to be able to overload one of the
> existing field, at least i do not see any otherway.

The one I've spent the most time thinking about is sharing pages between
reflinked files.  My approach is to pull DAX entries into the main page
cache and have them reference the PFN directly.  It's not a struct page,
but we can find a struct page from it if we need it.  The struct page
would belong to a mapping that isn't part of the file.

For other things (NUMA distribution), we can point to something which
isn't a struct page and can be distiguished from a real struct page by a
bit somewhere (I have ideas for at least three bits in struct page that
could be used for this).  Then use a pointer in that data structure to
point to the real page.  Or do NUMA distribution at the inode level.
Have a way to get from (inode, node) to an address_space which contains
just regular pages.

Using main memory to cache DAX could be done today without any data
structure changes.  It just needs the DAX entries pulled up into the
main pagecache.  See earlier item.

Exclusive write access ... you could put a magic value in the pagecache
for pages which are exclusively for someone else's use and handle those
specially.  I don't entirely understand this use case.

I don't have time to work on all of these.  If there's one that
particularly interests you, let's dive deep into it and figure out how
you can do it without committing this kind of violence to struct page.
Jerome Glisse Oct. 7, 2020, 5:54 p.m. UTC | #4
On Wed, Oct 07, 2020 at 06:05:58PM +0100, Matthew Wilcox wrote:
> On Wed, Oct 07, 2020 at 10:48:35AM -0400, Jerome Glisse wrote:
> > On Wed, Oct 07, 2020 at 04:20:13AM +0100, Matthew Wilcox wrote:
> > > On Tue, Oct 06, 2020 at 09:05:49PM -0400, jglisse@redhat.com wrote:
> > > > The present patchset just add mapping argument to the various vfs call-
> > > > backs. It does not make use of that new parameter to avoid regression.
> > > > I am posting this whole things as small contain patchset as it is rather
> > > > big and i would like to make progress step by step.
> > > 
> > > Well, that's the problem.  This patch set is gigantic and unreviewable.
> > > And it has no benefits.  The idea you present here was discussed at
> > > LSFMM in Utah and I recall absolutely nobody being in favour of it.
> > > You claim many wonderful features will be unlocked by this, but I think
> > > they can all be achieved without doing any of this very disruptive work.
> > 
> > You have any ideas on how to achieve them without such change ? I will
> > be more than happy for a simpler solution but i fail to see how you can
> > work around the need for a pointer inside struct page. Given struct
> > page can not grow it means you need to be able to overload one of the
> > existing field, at least i do not see any otherway.
> 
> The one I've spent the most time thinking about is sharing pages between
> reflinked files.  My approach is to pull DAX entries into the main page
> cache and have them reference the PFN directly.  It's not a struct page,
> but we can find a struct page from it if we need it.  The struct page
> would belong to a mapping that isn't part of the file.

You would need to do a lot of filesystem specific change to make sure
the fs understand the special mapping. It is doable but i feel it would
have a lot of fs specific part.


> For other things (NUMA distribution), we can point to something which
> isn't a struct page and can be distiguished from a real struct page by a
> bit somewhere (I have ideas for at least three bits in struct page that
> could be used for this).  Then use a pointer in that data structure to
> point to the real page.  Or do NUMA distribution at the inode level.
> Have a way to get from (inode, node) to an address_space which contains
> just regular pages.

How do you find all the copies ? KSM maintains a list for a reasons.
Same would be needed here because if you want to break the write prot
you need to find all the copy first. If you intend to walk page table
then how do you synchronize to avoid more copy to spawn while you
walk reverse mapping, we could lock the struct page i guess. Also how
do you walk device page table which are completely hidden from core mm.


> Using main memory to cache DAX could be done today without any data
> structure changes.  It just needs the DAX entries pulled up into the
> main pagecache.  See earlier item.
> 
> Exclusive write access ... you could put a magic value in the pagecache
> for pages which are exclusively for someone else's use and handle those
> specially.  I don't entirely understand this use case.

For this use case you need a callback to break the protection and it
needs to handle all cases ie not only write by CPU through file mapping
but also file write syscall and other syscall that can write to page
(pipe, ...).


> I don't have time to work on all of these.  If there's one that
> particularly interests you, let's dive deep into it and figure out how

I care about KSM, duplicate NUMA copy (not only for CPU but also
device) and write protection or exclusive write access. In each case
you need a list of all the copy (for KSM of the deduplicated page)
Having a special entry in the page cache does not sound like a good
option in many code path you would need to re-look the page cache to
find out if the page is in special state. If you use a bit flag in
struct page how do you get to the callback or to the copy/alias,
walk all the page tables ?

> you can do it without committing this kind of violence to struct page.

I do not see how i am doing violence to struct page :) The basis of
my approach is to pass down the mapping. We always have the mapping
at the top of the stack (either syscall entry point on a file or
through the vma when working on virtual address).

But we rarely pass down this mapping down the stack into the fs code.
I am only passing down the mapping through the bottom of the stack so
we do not need to rely of page->mapping all the time. I am not trying
to remove the page->mapping field, it is still usefull, i just want
to be able to overload it so that we can make KSM code generic and
allow to reuse that generic part for other usecase.

Cheers,
Jérôme
Matthew Wilcox (Oracle) Oct. 7, 2020, 6:33 p.m. UTC | #5
On Wed, Oct 07, 2020 at 01:54:19PM -0400, Jerome Glisse wrote:
> On Wed, Oct 07, 2020 at 06:05:58PM +0100, Matthew Wilcox wrote:
> > On Wed, Oct 07, 2020 at 10:48:35AM -0400, Jerome Glisse wrote:
> > > On Wed, Oct 07, 2020 at 04:20:13AM +0100, Matthew Wilcox wrote:
> > > > On Tue, Oct 06, 2020 at 09:05:49PM -0400, jglisse@redhat.com wrote:
> > > > > The present patchset just add mapping argument to the various vfs call-
> > > > > backs. It does not make use of that new parameter to avoid regression.
> > > > > I am posting this whole things as small contain patchset as it is rather
> > > > > big and i would like to make progress step by step.
> > > > 
> > > > Well, that's the problem.  This patch set is gigantic and unreviewable.
> > > > And it has no benefits.  The idea you present here was discussed at
> > > > LSFMM in Utah and I recall absolutely nobody being in favour of it.
> > > > You claim many wonderful features will be unlocked by this, but I think
> > > > they can all be achieved without doing any of this very disruptive work.
> > > 
> > > You have any ideas on how to achieve them without such change ? I will
> > > be more than happy for a simpler solution but i fail to see how you can
> > > work around the need for a pointer inside struct page. Given struct
> > > page can not grow it means you need to be able to overload one of the
> > > existing field, at least i do not see any otherway.
> > 
> > The one I've spent the most time thinking about is sharing pages between
> > reflinked files.  My approach is to pull DAX entries into the main page
> > cache and have them reference the PFN directly.  It's not a struct page,
> > but we can find a struct page from it if we need it.  The struct page
> > would belong to a mapping that isn't part of the file.
> 
> You would need to do a lot of filesystem specific change to make sure
> the fs understand the special mapping. It is doable but i feel it would
> have a lot of fs specific part.

I can't see any way to make it work without filesystem cooperation.

> > For other things (NUMA distribution), we can point to something which
> > isn't a struct page and can be distiguished from a real struct page by a
> > bit somewhere (I have ideas for at least three bits in struct page that
> > could be used for this).  Then use a pointer in that data structure to
> > point to the real page.  Or do NUMA distribution at the inode level.
> > Have a way to get from (inode, node) to an address_space which contains
> > just regular pages.
> 
> How do you find all the copies ? KSM maintains a list for a reasons.
> Same would be needed here because if you want to break the write prot
> you need to find all the copy first. If you intend to walk page table
> then how do you synchronize to avoid more copy to spawn while you
> walk reverse mapping, we could lock the struct page i guess. Also how
> do you walk device page table which are completely hidden from core mm.

You have the inode and you iterate over each mapping, looking up the page
that's in each mapping.  Or you use the i_mmap tree to find the pages.

> > Using main memory to cache DAX could be done today without any data
> > structure changes.  It just needs the DAX entries pulled up into the
> > main pagecache.  See earlier item.
> > 
> > Exclusive write access ... you could put a magic value in the pagecache
> > for pages which are exclusively for someone else's use and handle those
> > specially.  I don't entirely understand this use case.
> 
> For this use case you need a callback to break the protection and it
> needs to handle all cases ie not only write by CPU through file mapping
> but also file write syscall and other syscall that can write to page
> (pipe, ...).

If the page can't be found in the page cache, then by definition you
won't be able to write to them.

> > I don't have time to work on all of these.  If there's one that
> > particularly interests you, let's dive deep into it and figure out how
> 
> I care about KSM, duplicate NUMA copy (not only for CPU but also
> device) and write protection or exclusive write access. In each case
> you need a list of all the copy (for KSM of the deduplicated page)
> Having a special entry in the page cache does not sound like a good
> option in many code path you would need to re-look the page cache to
> find out if the page is in special state. If you use a bit flag in
> struct page how do you get to the callback or to the copy/alias,
> walk all the page tables ?

Like I said, something that _looks_ like a struct page.  At least looks
enough like a struct page that you can pull a pointer out of the page cache and check the bit.  But since it's not actually a struct page, you can
use the rest of the data structure for pointers to things you want to
track.  Like the real struct page.

> I do not see how i am doing violence to struct page :) The basis of
> my approach is to pass down the mapping. We always have the mapping
> at the top of the stack (either syscall entry point on a file or
> through the vma when working on virtual address).

Yes, you explained all that in Utah.  I wasn't impressed than, and I'm
not impressed now.
Jerome Glisse Oct. 7, 2020, 9:45 p.m. UTC | #6
On Wed, Oct 07, 2020 at 07:33:16PM +0100, Matthew Wilcox wrote:
> On Wed, Oct 07, 2020 at 01:54:19PM -0400, Jerome Glisse wrote:
> > On Wed, Oct 07, 2020 at 06:05:58PM +0100, Matthew Wilcox wrote:
> > > On Wed, Oct 07, 2020 at 10:48:35AM -0400, Jerome Glisse wrote:
> > > > On Wed, Oct 07, 2020 at 04:20:13AM +0100, Matthew Wilcox wrote:
> > > > > On Tue, Oct 06, 2020 at 09:05:49PM -0400, jglisse@redhat.com wrote:
> > > For other things (NUMA distribution), we can point to something which

[...]

> > > isn't a struct page and can be distiguished from a real struct page by a
> > > bit somewhere (I have ideas for at least three bits in struct page that
> > > could be used for this).  Then use a pointer in that data structure to
> > > point to the real page.  Or do NUMA distribution at the inode level.
> > > Have a way to get from (inode, node) to an address_space which contains
> > > just regular pages.
> > 
> > How do you find all the copies ? KSM maintains a list for a reasons.
> > Same would be needed here because if you want to break the write prot
> > you need to find all the copy first. If you intend to walk page table
> > then how do you synchronize to avoid more copy to spawn while you
> > walk reverse mapping, we could lock the struct page i guess. Also how
> > do you walk device page table which are completely hidden from core mm.
> 
> You have the inode and you iterate over each mapping, looking up the page
> that's in each mapping.  Or you use the i_mmap tree to find the pages.

This would slow down for everyone as we would have to walk all mapping
each time we try to write to page. Also we a have mechanism for page
write back to avoid race between thread trying to write and write back.
We would also need something similar. Without mediating this through
struct page i do not see how to keep this reasonable from performance
point of view.


> > > I don't have time to work on all of these.  If there's one that
> > > particularly interests you, let's dive deep into it and figure out how
> > 
> > I care about KSM, duplicate NUMA copy (not only for CPU but also
> > device) and write protection or exclusive write access. In each case
> > you need a list of all the copy (for KSM of the deduplicated page)
> > Having a special entry in the page cache does not sound like a good
> > option in many code path you would need to re-look the page cache to
> > find out if the page is in special state. If you use a bit flag in
> > struct page how do you get to the callback or to the copy/alias,
> > walk all the page tables ?
> 
> Like I said, something that _looks_ like a struct page.  At least looks
> enough like a struct page that you can pull a pointer out of the page
> cache and check the bit.  But since it's not actually a struct page,
> you can use the rest of the data structure for pointers to things you
> want to track.  Like the real struct page.

What i fear is the added cost because it means we need to do this look-
up everytime to check and we also need proper locking to avoid races.
Adding an ancilliary struct and trying to keep everything synchronize
seems harder to me.

> 
> > I do not see how i am doing violence to struct page :) The basis of
> > my approach is to pass down the mapping. We always have the mapping
> > at the top of the stack (either syscall entry point on a file or
> > through the vma when working on virtual address).
> 
> Yes, you explained all that in Utah.  I wasn't impressed than, and I'm
> not impressed now.

Is this more of a taste thing or is there something specific you do not
like ?

Cheers,
Jérôme
Matthew Wilcox (Oracle) Oct. 7, 2020, 10:09 p.m. UTC | #7
On Wed, Oct 07, 2020 at 01:54:19PM -0400, Jerome Glisse wrote:
> > For other things (NUMA distribution), we can point to something which
> > isn't a struct page and can be distiguished from a real struct page by a
> > bit somewhere (I have ideas for at least three bits in struct page that
> > could be used for this).  Then use a pointer in that data structure to
> > point to the real page.  Or do NUMA distribution at the inode level.
> > Have a way to get from (inode, node) to an address_space which contains
> > just regular pages.
> 
> How do you find all the copies ? KSM maintains a list for a reasons.
> Same would be needed here because if you want to break the write prot
> you need to find all the copy first. If you intend to walk page table
> then how do you synchronize to avoid more copy to spawn while you
> walk reverse mapping, we could lock the struct page i guess. Also how
> do you walk device page table which are completely hidden from core mm.

So ... why don't you put a PageKsm page in the page cache?  That way you
can share code with the current KSM implementation.  You'd need
something like this:

+++ b/mm/filemap.c
@@ -1622,6 +1622,9 @@ struct page *find_lock_entry(struct address_space *mapping
, pgoff_t index)
                lock_page(page);
                /* Has the page been truncated? */
                if (unlikely(page->mapping != mapping)) {
+                       if (PageKsm(page)) {
+                               ...
+                       }
                        unlock_page(page);
                        put_page(page);
                        goto repeat;
@@ -1655,6 +1658,7 @@ struct page *find_lock_entry(struct address_space *mapping, pgoff_t index)
  * * %FGP_WRITE - The page will be written
  * * %FGP_NOFS - __GFP_FS will get cleared in gfp mask
  * * %FGP_NOWAIT - Don't get blocked by page lock
+ * * %FGP_KSM - Return KSM pages
  *
  * If %FGP_LOCK or %FGP_CREAT are specified then the function may sleep even
  * if the %GFP flags specified for %FGP_CREAT are atomic.
@@ -1687,6 +1691,11 @@ struct page *pagecache_get_page(struct address_space *mapping, pgoff_t index,
 
                /* Has the page been truncated? */
                if (unlikely(page->mapping != mapping)) {
+                       if (PageKsm(page) {
+                               if (fgp_flags & FGP_KSM)
+                                       return page;
+                               ...
+                       }
                        unlock_page(page);
                        put_page(page);
                        goto repeat;

I don't know what you want to do when you find a KSM page, so I just left
an ellipsis.
Jerome Glisse Oct. 8, 2020, 3:30 p.m. UTC | #8
On Wed, Oct 07, 2020 at 11:09:16PM +0100, Matthew Wilcox wrote:
> On Wed, Oct 07, 2020 at 01:54:19PM -0400, Jerome Glisse wrote:
> > > For other things (NUMA distribution), we can point to something which
> > > isn't a struct page and can be distiguished from a real struct page by a
> > > bit somewhere (I have ideas for at least three bits in struct page that
> > > could be used for this).  Then use a pointer in that data structure to
> > > point to the real page.  Or do NUMA distribution at the inode level.
> > > Have a way to get from (inode, node) to an address_space which contains
> > > just regular pages.
> > 
> > How do you find all the copies ? KSM maintains a list for a reasons.
> > Same would be needed here because if you want to break the write prot
> > you need to find all the copy first. If you intend to walk page table
> > then how do you synchronize to avoid more copy to spawn while you
> > walk reverse mapping, we could lock the struct page i guess. Also how
> > do you walk device page table which are completely hidden from core mm.
> 
> So ... why don't you put a PageKsm page in the page cache?  That way you
> can share code with the current KSM implementation.  You'd need
> something like this:

I do just that but there is no need to change anything in page cache.
So below code is not necessary. What you need is a way to find all
the copies so if you have a write fault (or any write access) then
from that fault you get the mapping and offset and you use that to
lookup the fs specific informations and de-duplicate the page with
new page and the fs specific informations. Hence the filesystem code
do not need to know anything it all happens in generic common code.

So flow is:

  Same as before:
    1 - write fault (address, vma)
    2 - regular write fault handler -> find page in page cache

  New to common page fault code:
    3 - ksm check in write fault common code (same as ksm today
        for anonymous page fault code path).
    4 - break ksm (address, vma) -> (file offset, mapping)
        4.a - use mapping and file offset to lookup the proper
              fs specific information that were save when the
              page was made ksm.
        4.b - allocate new page and initialize it with that
              information (and page content), update page cache
              and mappings ie all the pte who where pointing to
              the ksm for that mapping at that offset to now use
              the new page (like KSM for anonymous page today).

  Resume regular code path:
        mkwrite /|| set pte ...

Roughly the same for write ioctl (other cases goes through GUP
which itself goes through page fault code path). There is no
need to change page cache in anyway. Just common code path that
enable write to file back page.

The fs specific information is page->private, some of the flags
(page->flags) and page->indexi (file offset). Everytime a page
is deduplicated a copy of that information is save in an alias
struct which you can get to from the the share KSM page (page->
mapping is a pointer to ksm root struct which has a pointer to
list of all aliases).

> 
> +++ b/mm/filemap.c
> @@ -1622,6 +1622,9 @@ struct page *find_lock_entry(struct address_space *mapping
> , pgoff_t index)
>                 lock_page(page);
>                 /* Has the page been truncated? */
>                 if (unlikely(page->mapping != mapping)) {
> +                       if (PageKsm(page)) {
> +                               ...
> +                       }
>                         unlock_page(page);
>                         put_page(page);
>                         goto repeat;
> @@ -1655,6 +1658,7 @@ struct page *find_lock_entry(struct address_space *mapping, pgoff_t index)
>   * * %FGP_WRITE - The page will be written
>   * * %FGP_NOFS - __GFP_FS will get cleared in gfp mask
>   * * %FGP_NOWAIT - Don't get blocked by page lock
> + * * %FGP_KSM - Return KSM pages
>   *
>   * If %FGP_LOCK or %FGP_CREAT are specified then the function may sleep even
>   * if the %GFP flags specified for %FGP_CREAT are atomic.
> @@ -1687,6 +1691,11 @@ struct page *pagecache_get_page(struct address_space *mapping, pgoff_t index,
>  
>                 /* Has the page been truncated? */
>                 if (unlikely(page->mapping != mapping)) {
> +                       if (PageKsm(page) {
> +                               if (fgp_flags & FGP_KSM)
> +                                       return page;
> +                               ...
> +                       }
>                         unlock_page(page);
>                         put_page(page);
>                         goto repeat;
> 
> I don't know what you want to do when you find a KSM page, so I just left
> an ellipsis.
>
Matthew Wilcox (Oracle) Oct. 8, 2020, 3:43 p.m. UTC | #9
On Thu, Oct 08, 2020 at 11:30:28AM -0400, Jerome Glisse wrote:
> On Wed, Oct 07, 2020 at 11:09:16PM +0100, Matthew Wilcox wrote:
> > So ... why don't you put a PageKsm page in the page cache?  That way you
> > can share code with the current KSM implementation.  You'd need
> > something like this:
> 
> I do just that but there is no need to change anything in page cache.

That's clearly untrue.  If you just put a PageKsm page in the page
cache today, here's what will happen on a truncate:

void truncate_inode_pages_range(struct address_space *mapping,
                                loff_t lstart, loff_t lend)
{
...
                struct page *page = find_lock_page(mapping, start - 1);

find_lock_page() does this:
        return pagecache_get_page(mapping, offset, FGP_LOCK, 0);

pagecache_get_page():

repeat:
        page = find_get_entry(mapping, index);
...
        if (fgp_flags & FGP_LOCK) {
...
                if (unlikely(compound_head(page)->mapping != mapping)) {
                        unlock_page(page);
                        put_page(page);
                        goto repeat;

so it's just going to spin.  There are plenty of other codepaths that
would need to be checked.  If you haven't found them, that shows you
don't understand the problem deeply enough yet.

I believe we should solve this problem, but I don't think you're going
about it the right way.

> So flow is:
> 
>   Same as before:
>     1 - write fault (address, vma)
>     2 - regular write fault handler -> find page in page cache
> 
>   New to common page fault code:
>     3 - ksm check in write fault common code (same as ksm today
>         for anonymous page fault code path).
>     4 - break ksm (address, vma) -> (file offset, mapping)
>         4.a - use mapping and file offset to lookup the proper
>               fs specific information that were save when the
>               page was made ksm.
>         4.b - allocate new page and initialize it with that
>               information (and page content), update page cache
>               and mappings ie all the pte who where pointing to
>               the ksm for that mapping at that offset to now use
>               the new page (like KSM for anonymous page today).

But by putting that logic in the page fault path, you've missed
the truncate path.  And maybe other places.  Putting the logic
down in pagecache_get_page() means you _don't_ need to find
all the places that call pagecache_get_page().
Jerome Glisse Oct. 8, 2020, 6:48 p.m. UTC | #10
On Thu, Oct 08, 2020 at 04:43:41PM +0100, Matthew Wilcox wrote:
> On Thu, Oct 08, 2020 at 11:30:28AM -0400, Jerome Glisse wrote:
> > On Wed, Oct 07, 2020 at 11:09:16PM +0100, Matthew Wilcox wrote:
> > > So ... why don't you put a PageKsm page in the page cache?  That way you
> > > can share code with the current KSM implementation.  You'd need
> > > something like this:
> > 
> > I do just that but there is no need to change anything in page cache.
> 
> That's clearly untrue.  If you just put a PageKsm page in the page
> cache today, here's what will happen on a truncate:
> 
> void truncate_inode_pages_range(struct address_space *mapping,
>                                 loff_t lstart, loff_t lend)
> {
> ...
>                 struct page *page = find_lock_page(mapping, start - 1);
> 
> find_lock_page() does this:
>         return pagecache_get_page(mapping, offset, FGP_LOCK, 0);
> 
> pagecache_get_page():
> 
> repeat:
>         page = find_get_entry(mapping, index);
> ...
>         if (fgp_flags & FGP_LOCK) {
> ...
>                 if (unlikely(compound_head(page)->mapping != mapping)) {
>                         unlock_page(page);
>                         put_page(page);
>                         goto repeat;
> 
> so it's just going to spin.  There are plenty of other codepaths that
> would need to be checked.  If you haven't found them, that shows you
> don't understand the problem deeply enough yet.

I also change truncate, splice and few other special cases that do
not goes through GUP/page fault/mkwrite (memory debug too but that's
a different beast).


> I believe we should solve this problem, but I don't think you're going
> about it the right way.

I have done much more than what i posted but there is bug that i
need to hammer down before posting everything and i wanted to get
the discussion started. I guess i will finish tracking that one
down and post the whole thing.


> > So flow is:
> > 
> >   Same as before:
> >     1 - write fault (address, vma)
> >     2 - regular write fault handler -> find page in page cache
> > 
> >   New to common page fault code:
> >     3 - ksm check in write fault common code (same as ksm today
> >         for anonymous page fault code path).
> >     4 - break ksm (address, vma) -> (file offset, mapping)
> >         4.a - use mapping and file offset to lookup the proper
> >               fs specific information that were save when the
> >               page was made ksm.
> >         4.b - allocate new page and initialize it with that
> >               information (and page content), update page cache
> >               and mappings ie all the pte who where pointing to
> >               the ksm for that mapping at that offset to now use
> >               the new page (like KSM for anonymous page today).
> 
> But by putting that logic in the page fault path, you've missed
> the truncate path.  And maybe other places.  Putting the logic
> down in pagecache_get_page() means you _don't_ need to find
> all the places that call pagecache_get_page().

They are cases where pagecache is not even in the loop ie you
already have the page and you do not need to look it up (page
fault, some fs common code, anything that goes through GUP,
memory reclaim, ...). Making all those places having to go
through page cache all the times will slow them down and many
are hot code path that i do not believe we want to slow even
if a feature is not use.

Cheers,
Jérôme