@@ -633,29 +633,45 @@ void validate_mm(struct mm_struct *mm)
*
* On success, returns the merged VMA. Otherwise returns NULL.
*/
-static struct vm_area_struct *commit_merge(struct vma_merge_struct *vmg,
- long adj_start)
+static struct vm_area_struct *commit_merge(struct vma_merge_struct *vmg)
{
- struct vma_prepare vp;
struct vm_area_struct *remove = NULL;
struct vm_area_struct *remove2 = NULL;
+ unsigned long flags = vmg->merge_flags;
+ struct vma_prepare vp;
struct vm_area_struct *adjust = NULL;
+ long adj_start;
+ bool merge_target;
+
/*
- * In all cases but that of merge right, shrink next, we write
- * vmg->target to the maple tree and return this as the merged VMA.
+ * If modifying an existing VMA and we don't remove vmg->middle, then we
+ * shrink the adjacent VMA.
*/
- bool merge_target = adj_start >= 0;
+ if (flags & __VMG_FLAG_ADJUST_MIDDLE_START) {
+ adjust = vmg->middle;
+ /* The POSITIVE value by which we offset vmg->middle->vm_start. */
+ adj_start = vmg->end - vmg->middle->vm_start;
+ merge_target = true;
+ } else if (flags & __VMG_FLAG_ADJUST_NEXT_START) {
+ adjust = vmg->next;
+ /* The NEGATIVE value by which we offset vmg->next->vm_start. */
+ adj_start = -(vmg->middle->vm_end - vmg->end);
+ /*
+ * In all cases but this - merge right, shrink next - we write
+ * vmg->target to the maple tree and return this as the merged VMA.
+ */
+ merge_target = false;
+ } else {
+ adjust = NULL;
+ adj_start = 0;
+ merge_target = true;
+ }
- if (vmg->merge_flags & __VMG_FLAG_REMOVE_MIDDLE)
+ if (flags & __VMG_FLAG_REMOVE_MIDDLE)
remove = vmg->middle;
if (vmg->merge_flags & __VMG_FLAG_REMOVE_NEXT)
remove2 = vmg->next;
- if (adj_start > 0)
- adjust = vmg->middle;
- else if (adj_start < 0)
- adjust = vmg->next;
-
init_multi_vma_prep(&vp, vmg->target, adjust, remove, remove2);
VM_WARN_ON(vp.anon_vma && adjust && adjust->anon_vma &&
@@ -739,7 +755,6 @@ static __must_check struct vm_area_struct *vma_merge_existing_range(
bool left_side = middle && start == middle->vm_start;
bool right_side = middle && end == middle->vm_end;
int err = 0;
- long adj_start = 0;
bool merge_will_delete_middle, merge_will_delete_next;
bool merge_left, merge_right, merge_both;
@@ -860,11 +875,8 @@ static __must_check struct vm_area_struct *vma_merge_existing_range(
vmg->start = prev->vm_start;
vmg->pgoff = prev->vm_pgoff;
- /*
- * We both expand prev and shrink middle.
- */
if (!merge_will_delete_middle)
- adj_start = vmg->end - middle->vm_start;
+ vmg->merge_flags |= __VMG_FLAG_ADJUST_MIDDLE_START;
err = dup_anon_vma(prev, middle, &anon_dup);
} else { /* merge_right */
@@ -893,12 +905,11 @@ static __must_check struct vm_area_struct *vma_merge_existing_range(
* IMPORTANT: This is the ONLY case where the final
* merged VMA is NOT vmg->target, but rather vmg->next.
*/
+ vmg->merge_flags |= __VMG_FLAG_ADJUST_NEXT_START;
vmg->target = middle;
vmg->start = middle->vm_start;
vmg->end = start;
vmg->pgoff = middle->vm_pgoff;
-
- adj_start = -(middle->vm_end - start);
}
err = dup_anon_vma(next, middle, &anon_dup);
@@ -912,7 +923,7 @@ static __must_check struct vm_area_struct *vma_merge_existing_range(
if (merge_will_delete_next)
vmg->merge_flags |= __VMG_FLAG_REMOVE_NEXT;
- res = commit_merge(vmg, adj_start);
+ res = commit_merge(vmg);
if (!res) {
if (anon_dup)
unlink_anon_vmas(anon_dup);
@@ -1087,7 +1098,7 @@ int vma_expand(struct vma_merge_struct *vmg)
if (remove_next)
vmg->merge_flags |= __VMG_FLAG_REMOVE_NEXT;
- if (!commit_merge(vmg, 0))
+ if (!commit_merge(vmg))
goto nomem;
return 0;
@@ -67,16 +67,26 @@ enum vma_merge_flags {
* at the gap.
*/
VMG_FLAG_JUST_EXPAND = 1 << 0,
+ /*
+ * Internal flag indicating the merge increases vmg->middle->vm_start
+ * (and thereby, vmg->prev->vm_end).
+ */
+ __VMG_FLAG_ADJUST_MIDDLE_START = 1 << 1,
+ /*
+ * Internal flag indicating the merge decreases vmg->next->vm_start
+ * (and thereby, vmg->middle->vm_end).
+ */
+ __VMG_FLAG_ADJUST_NEXT_START = 1 << 2,
/*
* Internal flag used during the merge operation to indicate we will
* remove vmg->middle.
*/
- __VMG_FLAG_REMOVE_MIDDLE = 1 << 1,
+ __VMG_FLAG_REMOVE_MIDDLE = 1 << 3,
/*
* Internal flag used during the merge operationr to indicate we will
* remove vmg->next.
*/
- __VMG_FLAG_REMOVE_NEXT = 1 << 2,
+ __VMG_FLAG_REMOVE_NEXT = 1 << 4,
};
/*
Introduce internal __VMG_FLAG_ADJUST_MIDDLE_START and __VMG_FLAG_ADJUST_NEXT_START merge flags, enabling us to indicate to commit_merge() that we are performing a merge which either spans only part of vmg->middle, or part of vmg->next respectively. In the former instance, we change the start of vmg->middle to match the attributes of vmg->prev, without spanning all of vmg->middle. This implies that vmg->prev->vm_end and vmg->middle->vm_start are both increased to form the new merged VMA (vmg->prev) and the new subsequent VMA (vmg->middle). In the latter case, we change the end of vmg->middle to match the attributes of vmg->next, without spanning all of vmg->next. This implies that vmg->middle->vm_end and vmg->next->vm_start are both decreased to form the new merged VMA (vmg->next) and the new prior VMA (vmg->middle). Since we now have a stable set of prev, middle, next VMAs threaded through vmg and with these flags set know what is happening, we can perform the calculation in commit_merge() instead. This allows us to drop the confusing adj_start parameter and instead pass semantic information to commit_merge(). In the latter case the -(middle->vm_end - start) calculation becomes -(middle->vm-end - vmg->end), however this is correct as vmg->end is set to the start parameter. This is because in this case (rather confusingly), we manipulate vmg->middle, but ultimately return vmg->next, whose range will be correctly specified. At this point vmg->start, end is the new range for the prior VMA rather than the merged one. This patch has no change in functional behaviour. Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com> --- mm/vma.c | 53 ++++++++++++++++++++++++++++++++--------------------- mm/vma.h | 14 ++++++++++++-- 2 files changed, 44 insertions(+), 23 deletions(-)