ANDROID: 16K: Handle pad VMA splits and merges

In some cases a VMA with padding representation may be split, and
therefore the padding flags must be updated accordingly.

There are 3 cases to handle:

Given:
    | DDDDPPPP |

where:
    - D represents 1 page of data;
    - P represents 1 page of padding;
    - | represents the boundaries (start/end) of the VMA

1) Split exactly at the padding boundary

    | DDDDPPPP | --> | DDDD | PPPP |

    - Remove padding flags from the first VMA.
    - The second VMA is all padding

2) Split within the padding area

    | DDDDPPPP | --> | DDDDPP | PP |

    - Subtract the length of the second VMA from the first VMA's
      padding.
    - The second VMA is all padding, adjust its padding length (flags)

3) Split within the data area

    | DDDDPPPP | --> | DD | DDPPPP |

    - Remove padding flags from the first VMA.
    - The second VMA is has the same padding as from before the split.

To simplify the semantics merging of padding VMAs is not allowed.

If a split produces a VMA that is entirely padding, show_[s]maps()
only outputs the padding VMA entry (as the data entry is of length 0).

Bug: 330117029
Bug: 327600007
Bug: 330767927
Bug: 328266487
Bug: 329803029
Change-Id: Ie2628ced5512e2c7f8af25fabae1f38730c8bb1a
Signed-off-by: Kalesh Singh <kaleshsingh@google.com>
This commit is contained in:
Kalesh Singh 2024-04-22 14:24:59 -07:00
parent b86b5cb22d
commit 72a9c0a205
6 changed files with 115 additions and 4 deletions

View File

@ -352,7 +352,8 @@ static int show_map(struct seq_file *m, void *v)
struct vm_area_struct *pad_vma = get_pad_vma(v);
struct vm_area_struct *vma = get_data_vma(v);
show_map_vma(m, vma);
if (vma_pages(vma))
show_map_vma(m, vma);
show_map_pad_vma(vma, pad_vma, m, show_map_vma);
@ -871,6 +872,9 @@ static int show_smap(struct seq_file *m, void *v)
memset(&mss, 0, sizeof(mss));
if (!vma_pages(vma))
goto show_pad;
smap_gather_stats(vma, &mss, 0);
show_map_vma(m, vma);
@ -889,6 +893,7 @@ static int show_smap(struct seq_file *m, void *v)
seq_printf(m, "ProtectionKey: %8u\n", vma_pkey(vma));
show_smap_vma_flags(m, vma);
show_pad:
show_map_pad_vma(vma, pad_vma, m, (show_pad_vma_fn)((void*)show_smap));
return 0;

View File

@ -61,6 +61,9 @@ extern struct vm_area_struct *get_data_vma(struct vm_area_struct *vma);
extern void show_map_pad_vma(struct vm_area_struct *vma,
struct vm_area_struct *pad,
struct seq_file *m, show_pad_vma_fn func);
extern void split_pad_vma(struct vm_area_struct *vma, struct vm_area_struct *new,
unsigned long addr, int new_below);
#else /* PAGE_SIZE != SZ_4K || !defined(CONFIG_64BIT) */
static inline void vma_set_pad_pages(struct vm_area_struct *vma,
unsigned long nr_pages)
@ -92,10 +95,41 @@ static inline void show_map_pad_vma(struct vm_area_struct *vma,
struct seq_file *m, show_pad_vma_fn func)
{
}
static inline void split_pad_vma(struct vm_area_struct *vma, struct vm_area_struct *new,
unsigned long addr, int new_below)
{
}
#endif /* PAGE_SIZE == SZ_4K && defined(CONFIG_64BIT) */
static inline unsigned long vma_data_pages(struct vm_area_struct *vma)
{
return vma_pages(vma) - vma_pad_pages(vma);
}
/*
* Sets the correct padding bits / flags for a VMA split.
*/
static inline unsigned long vma_pad_fixup_flags(struct vm_area_struct *vma,
unsigned long newflags)
{
if (newflags & VM_PAD_MASK)
return (newflags & ~VM_PAD_MASK) | (vma->vm_flags & VM_PAD_MASK);
else
return newflags;
}
/*
* Merging of padding VMAs is uncommon, as padding is only allowed
* from the linker context.
*
* To simplify the semantics, adjacent VMAs with padding are not
* allowed to merge.
*/
static inline bool is_mergable_pad_vma(struct vm_area_struct *vma,
unsigned long vm_flags)
{
/* Padding VMAs cannot be merged with other padding or real VMAs */
return !((vma->vm_flags | vm_flags) & VM_PAD_MASK);
}
#endif /* _LINUX_PAGE_SIZE_MIGRATION_H */

View File

@ -13,6 +13,7 @@
#include <linux/swap.h>
#include <linux/swapops.h>
#include <linux/pagemap.h>
#include <linux/pgsize_migration.h>
#include <linux/pagevec.h>
#include <linux/pagewalk.h>
#include <linux/mempolicy.h>
@ -458,7 +459,7 @@ static int mlock_fixup(struct vm_area_struct *vma, struct vm_area_struct **prev,
if ((newflags & VM_LOCKED) && (oldflags & VM_LOCKED)) {
/* No work to do, and mlocking twice would be wrong */
vma_start_write(vma);
vm_flags_reset(vma, newflags);
vm_flags_reset(vma, vma_pad_fixup_flags(vma, newflags));
} else {
mlock_vma_pages_range(vma, start, end, newflags);
}

View File

@ -23,6 +23,7 @@
#include <linux/init.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/pgsize_migration.h>
#include <linux/personality.h>
#include <linux/security.h>
#include <linux/hugetlb.h>
@ -940,6 +941,8 @@ static inline int is_mergeable_vma(struct vm_area_struct *vma,
return 0;
if (!anon_vma_name_eq(anon_vma_name(vma), anon_name))
return 0;
if (!is_mergable_pad_vma(vma, vm_flags))
return 0;
return 1;
}
@ -2455,8 +2458,10 @@ int __split_vma(struct mm_struct *mm, struct vm_area_struct *vma,
err = vma_adjust(vma, vma->vm_start, addr, vma->vm_pgoff, new);
/* Success. */
if (!err)
if (!err) {
split_pad_vma(vma, new, addr, new_below);
return 0;
}
/* Avoid vm accounting in close() operation */
new->vm_start = new->vm_end;

View File

@ -17,6 +17,7 @@
#include <linux/highmem.h>
#include <linux/security.h>
#include <linux/mempolicy.h>
#include <linux/pgsize_migration.h>
#include <linux/personality.h>
#include <linux/syscalls.h>
#include <linux/swap.h>
@ -632,7 +633,7 @@ mprotect_fixup(struct mmu_gather *tlb, struct vm_area_struct *vma,
* held in write mode.
*/
vma_start_write(vma);
vm_flags_reset(vma, newflags);
vm_flags_reset(vma, vma_pad_fixup_flags(vma, newflags));
/*
* We want to check manually if we can change individual PTEs writable
* if we can't do that automatically for all PTEs in a mapping. For

View File

@ -113,6 +113,7 @@ void vma_set_pad_pages(struct vm_area_struct *vma,
if (!is_pgsize_migration_enabled())
return;
vm_flags_clear(vma, VM_PAD_MASK);
vm_flags_set(vma, nr_pages << VM_PAD_SHIFT);
}
@ -324,5 +325,69 @@ void show_map_pad_vma(struct vm_area_struct *vma, struct vm_area_struct *pad,
kfree(pad);
kfree(vma);
}
/*
* When splitting a padding VMA there are a couple of cases to handle.
*
* Given:
*
* | DDDDPPPP |
*
* where:
* - D represents 1 page of data;
* - P represents 1 page of padding;
* - | represents the boundaries (start/end) of the VMA
*
*
* 1) Split exactly at the padding boundary
*
* | DDDDPPPP | --> | DDDD | PPPP |
*
* - Remove padding flags from the first VMA.
* - The second VMA is all padding
*
* 2) Split within the padding area
*
* | DDDDPPPP | --> | DDDDPP | PP |
*
* - Subtract the length of the second VMA from the first VMA's padding.
* - The second VMA is all padding, adjust its padding length (flags)
*
* 3) Split within the data area
*
* | DDDDPPPP | --> | DD | DDPPPP |
*
* - Remove padding flags from the first VMA.
* - The second VMA is has the same padding as from before the split.
*/
void split_pad_vma(struct vm_area_struct *vma, struct vm_area_struct *new,
unsigned long addr, int new_below)
{
unsigned long nr_pad_pages = vma_pad_pages(vma);
unsigned long nr_vma2_pages;
struct vm_area_struct *first;
struct vm_area_struct *second;
if (!nr_pad_pages)
return;
if (new_below) {
first = new;
second = vma;
} else {
first = vma;
second = new;
}
nr_vma2_pages = vma_pages(second);
if (nr_vma2_pages >= nr_pad_pages) { /* Case 1 & 3*/
vm_flags_clear(first, VM_PAD_MASK);
vma_set_pad_pages(second, nr_pad_pages);
} else { /* Case 2 */
vma_set_pad_pages(first, nr_pad_pages - nr_vma2_pages);
vma_set_pad_pages(second, nr_vma2_pages);
}
}
#endif /* PAGE_SIZE == SZ_4K */
#endif /* CONFIG_64BIT */