Re: [PATCH 02/16] huge tmpfs: fix split_huge_page() after FALLOC_FL_KEEP_SIZE

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Jul 30, 2021 at 12:28 AM Hugh Dickins <hughd@xxxxxxxxxx> wrote:
>
> A successful shmem_fallocate() guarantees that the extent has been
> reserved, even beyond i_size when the FALLOC_FL_KEEP_SIZE flag was used.
> But that guarantee is broken by shmem_unused_huge_shrink()'s attempts to
> split huge pages and free their excess beyond i_size; and by other uses
> of split_huge_page() near i_size.
>
> It's sad to add a shmem inode field just for this, but I did not find a
> better way to keep the guarantee.  A flag to say KEEP_SIZE has been used
> would be cheaper, but I'm averse to unclearable flags.  The fallocend
> field is not perfect either (many disjoint ranges might be fallocated),
> but good enough; and gains another use later on.
>
> Fixes: 779750d20b93 ("shmem: split huge pages beyond i_size under memory pressure")
> Signed-off-by: Hugh Dickins <hughd@xxxxxxxxxx>

Reviewed-by: Yang Shi <shy828301@xxxxxxxxx>

> ---
>  include/linux/shmem_fs.h | 13 +++++++++++++
>  mm/huge_memory.c         |  6 ++++--
>  mm/shmem.c               | 15 ++++++++++++++-
>  3 files changed, 31 insertions(+), 3 deletions(-)
>
> diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
> index 8e775ce517bb..9b7f7ac52351 100644
> --- a/include/linux/shmem_fs.h
> +++ b/include/linux/shmem_fs.h
> @@ -18,6 +18,7 @@ struct shmem_inode_info {
>         unsigned long           flags;
>         unsigned long           alloced;        /* data pages alloced to file */
>         unsigned long           swapped;        /* subtotal assigned to swap */
> +       pgoff_t                 fallocend;      /* highest fallocate endindex */
>         struct list_head        shrinklist;     /* shrinkable hpage inodes */
>         struct list_head        swaplist;       /* chain of maybes on swap */
>         struct shared_policy    policy;         /* NUMA memory alloc policy */
> @@ -119,6 +120,18 @@ static inline bool shmem_file(struct file *file)
>         return shmem_mapping(file->f_mapping);
>  }
>
> +/*
> + * If fallocate(FALLOC_FL_KEEP_SIZE) has been used, there may be pages
> + * beyond i_size's notion of EOF, which fallocate has committed to reserving:
> + * which split_huge_page() must therefore not delete.  This use of a single
> + * "fallocend" per inode errs on the side of not deleting a reservation when
> + * in doubt: there are plenty of cases when it preserves unreserved pages.
> + */
> +static inline pgoff_t shmem_fallocend(struct inode *inode, pgoff_t eof)
> +{
> +       return max(eof, SHMEM_I(inode)->fallocend);
> +}
> +
>  extern bool shmem_charge(struct inode *inode, long pages);
>  extern void shmem_uncharge(struct inode *inode, long pages);
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index afff3ac87067..890fb73ac89b 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -2454,11 +2454,11 @@ static void __split_huge_page(struct page *page, struct list_head *list,
>
>         for (i = nr - 1; i >= 1; i--) {
>                 __split_huge_page_tail(head, i, lruvec, list);
> -               /* Some pages can be beyond i_size: drop them from page cache */
> +               /* Some pages can be beyond EOF: drop them from page cache */
>                 if (head[i].index >= end) {
>                         ClearPageDirty(head + i);
>                         __delete_from_page_cache(head + i, NULL);
> -                       if (IS_ENABLED(CONFIG_SHMEM) && PageSwapBacked(head))
> +                       if (shmem_mapping(head->mapping))
>                                 shmem_uncharge(head->mapping->host, 1);
>                         put_page(head + i);
>                 } else if (!PageAnon(page)) {
> @@ -2686,6 +2686,8 @@ int split_huge_page_to_list(struct page *page, struct list_head *list)
>                  * head page lock is good enough to serialize the trimming.
>                  */
>                 end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
> +               if (shmem_mapping(mapping))
> +                       end = shmem_fallocend(mapping->host, end);
>         }
>
>         /*
> diff --git a/mm/shmem.c b/mm/shmem.c
> index 0cd5c9156457..24c9da6b41c2 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
> @@ -905,6 +905,9 @@ static void shmem_undo_range(struct inode *inode, loff_t lstart, loff_t lend,
>         if (lend == -1)
>                 end = -1;       /* unsigned, so actually very big */
>
> +       if (info->fallocend > start && info->fallocend <= end && !unfalloc)
> +               info->fallocend = start;
> +
>         pagevec_init(&pvec);
>         index = start;
>         while (index < end && find_lock_entries(mapping, index, end - 1,
> @@ -2667,7 +2670,7 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset,
>         struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
>         struct shmem_inode_info *info = SHMEM_I(inode);
>         struct shmem_falloc shmem_falloc;
> -       pgoff_t start, index, end;
> +       pgoff_t start, index, end, undo_fallocend;
>         int error;
>
>         if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE))
> @@ -2736,6 +2739,15 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset,
>         inode->i_private = &shmem_falloc;
>         spin_unlock(&inode->i_lock);
>
> +       /*
> +        * info->fallocend is only relevant when huge pages might be
> +        * involved: to prevent split_huge_page() freeing fallocated
> +        * pages when FALLOC_FL_KEEP_SIZE committed beyond i_size.
> +        */
> +       undo_fallocend = info->fallocend;
> +       if (info->fallocend < end)
> +               info->fallocend = end;
> +
>         for (index = start; index < end; ) {
>                 struct page *page;
>
> @@ -2750,6 +2762,7 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset,
>                 else
>                         error = shmem_getpage(inode, index, &page, SGP_FALLOC);
>                 if (error) {
> +                       info->fallocend = undo_fallocend;
>                         /* Remove the !PageUptodate pages we added */
>                         if (index > start) {
>                                 shmem_undo_range(inode,
> --
> 2.26.2
>



[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux