git: 249939298daf - stable/15 - vm_object_coalesce(): return swap reservation back if overcharged

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Sat, 24 Jan 2026 00:32:21 UTC
The branch stable/15 has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=249939298daf62f003cf75cb3bd265c6a7b20c08

commit 249939298daf62f003cf75cb3bd265c6a7b20c08
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2025-12-20 16:03:40 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-01-24 00:26:44 +0000

    vm_object_coalesce(): return swap reservation back if overcharged
    
    (cherry picked from commit 7685aaea8850f5b6995a17740a016019e0956c70)
---
 sys/vm/vm_map.c    | 44 ++++++++++++++++++++++++++++++--------------
 sys/vm/vm_object.c | 41 +++++++++++++++++++++++++++++++----------
 sys/vm/vm_object.h |  8 +++++++-
 3 files changed, 68 insertions(+), 25 deletions(-)

diff --git a/sys/vm/vm_map.c b/sys/vm/vm_map.c
index 6b09552c5fee..68dcadd2b2f1 100644
--- a/sys/vm/vm_map.c
+++ b/sys/vm/vm_map.c
@@ -1620,6 +1620,7 @@ vm_map_insert1(vm_map_t map, vm_object_t object, vm_ooffset_t offset,
 	vm_inherit_t inheritance;
 	u_long bdry;
 	u_int bidx;
+	int cflags;
 
 	VM_MAP_ASSERT_LOCKED(map);
 	KASSERT(object != kernel_object ||
@@ -1696,20 +1697,36 @@ vm_map_insert1(vm_map_t map, vm_object_t object, vm_ooffset_t offset,
 	}
 
 	cred = NULL;
-	if ((cow & (MAP_ACC_NO_CHARGE | MAP_NOFAULT | MAP_CREATE_GUARD)) != 0)
-		goto charged;
-	if ((cow & MAP_ACC_CHARGED) || ((prot & VM_PROT_WRITE) &&
-	    ((protoeflags & MAP_ENTRY_NEEDS_COPY) || object == NULL))) {
-		if (!(cow & MAP_ACC_CHARGED) && !swap_reserve(end - start))
-			return (KERN_RESOURCE_SHORTAGE);
-		KASSERT(object == NULL ||
-		    (protoeflags & MAP_ENTRY_NEEDS_COPY) != 0 ||
-		    object->cred == NULL,
-		    ("overcommit: vm_map_insert o %p", object));
-		cred = curthread->td_ucred;
+	if ((cow & (MAP_ACC_NO_CHARGE | MAP_NOFAULT | MAP_CREATE_GUARD)) != 0) {
+		cflags = OBJCO_NO_CHARGE;
+	} else {
+		cflags = 0;
+		if ((cow & MAP_ACC_CHARGED) != 0 ||
+		    ((prot & VM_PROT_WRITE) != 0 &&
+		    ((protoeflags & MAP_ENTRY_NEEDS_COPY) != 0 ||
+		    object == NULL))) {
+			if ((cow & MAP_ACC_CHARGED) == 0) {
+				if (!swap_reserve(end - start))
+					return (KERN_RESOURCE_SHORTAGE);
+
+				/*
+				 * Only inform vm_object_coalesce()
+				 * that the object was charged if
+				 * there is no need for CoW, so the
+				 * swap amount reserved is applicable
+				 * to the prev_entry->object.
+				 */
+				if ((protoeflags & MAP_ENTRY_NEEDS_COPY) == 0)
+					cflags |= OBJCO_CHARGED;
+			}
+			KASSERT(object == NULL ||
+			    (protoeflags & MAP_ENTRY_NEEDS_COPY) != 0 ||
+			    object->cred == NULL,
+			    ("overcommit: vm_map_insert o %p", object));
+			cred = curthread->td_ucred;
+		}
 	}
 
-charged:
 	/* Expand the kernel pmap, if necessary. */
 	if (map == kernel_map && end > kernel_vm_end) {
 		int rv;
@@ -1741,8 +1758,7 @@ charged:
 	    vm_object_coalesce(prev_entry->object.vm_object,
 	    prev_entry->offset,
 	    (vm_size_t)(prev_entry->end - prev_entry->start),
-	    (vm_size_t)(end - prev_entry->end), cred != NULL &&
-	    (protoeflags & MAP_ENTRY_NEEDS_COPY) == 0)) {
+	    (vm_size_t)(end - prev_entry->end), cflags)) {
 		/*
 		 * We were able to extend the object.  Determine if we
 		 * can extend the previous map entry to include the
diff --git a/sys/vm/vm_object.c b/sys/vm/vm_object.c
index c216fdc01af1..f4c54ba91742 100644
--- a/sys/vm/vm_object.c
+++ b/sys/vm/vm_object.c
@@ -2161,7 +2161,7 @@ vm_object_populate(vm_object_t object, vm_pindex_t start, vm_pindex_t end)
  */
 boolean_t
 vm_object_coalesce(vm_object_t prev_object, vm_ooffset_t prev_offset,
-    vm_size_t prev_size, vm_size_t next_size, boolean_t reserved)
+    vm_size_t prev_size, vm_size_t next_size, int cflags)
 {
 	vm_pindex_t next_end, next_pindex;
 
@@ -2202,8 +2202,7 @@ vm_object_coalesce(vm_object_t prev_object, vm_ooffset_t prev_offset,
 	/*
 	 * Account for the charge.
 	 */
-	if (prev_object->cred != NULL &&
-	    next_pindex + next_size > prev_object->size) {
+	if (prev_object->cred != NULL && (cflags & OBJCO_NO_CHARGE) == 0) {
 		/*
 		 * If prev_object was charged, then this mapping,
 		 * although not charged now, may become writable
@@ -2214,14 +2213,36 @@ vm_object_coalesce(vm_object_t prev_object, vm_ooffset_t prev_offset,
 		 * entry, and swap reservation for this entry is
 		 * managed in appropriate time.
 		 */
-		vm_size_t charge = ptoa(next_pindex + next_size -
-		    prev_object->size);
-		if (!reserved &&
-		    !swap_reserve_by_cred(charge, prev_object->cred)) {
-			VM_OBJECT_WUNLOCK(prev_object);
-			return (FALSE);
+		if (next_end > prev_object->size) {
+			vm_size_t charge = ptoa(next_end - prev_object->size);
+
+			if ((cflags & OBJCO_CHARGED) == 0) {
+				if (!swap_reserve_by_cred(charge,
+				    prev_object->cred)) {
+					VM_OBJECT_WUNLOCK(prev_object);
+					return (FALSE);
+				}
+			} else if (prev_object->size > next_pindex) {
+				/*
+				 * The caller charged, but:
+				 * - the object has already accounted for the
+				 *   space,
+				 * - and the object end is between previous
+				 *   mapping end and next_end.
+				 */
+				swap_release_by_cred(ptoa(prev_object->size -
+				    next_pindex), prev_object->cred);
+			}
+			prev_object->charge += charge;
+		} else if ((cflags & OBJCO_CHARGED) != 0) {
+			/*
+			 * The caller charged, but the object has
+			 * already accounted for the space.  Whole new
+			 * mapping charge should be released,
+			 */
+			swap_release_by_cred(ptoa(next_size),
+			    prev_object->cred);
 		}
-		prev_object->charge += charge;
 	}
 
 	/*
diff --git a/sys/vm/vm_object.h b/sys/vm/vm_object.h
index a7f2e9a2b785..fdc0bcfc9f28 100644
--- a/sys/vm/vm_object.h
+++ b/sys/vm/vm_object.h
@@ -228,6 +228,12 @@ struct vm_object {
 #define	OBJPR_NOTMAPPED	0x2		/* Don't unmap pages. */
 #define	OBJPR_VALIDONLY	0x4		/* Ignore invalid pages. */
 
+/*
+ * Options for vm_object_coalesce().
+ */
+#define	OBJCO_CHARGED	0x1		/* The next_size was charged already */
+#define	OBJCO_NO_CHARGE	0x2		/* Do not do swap accounting at all */
+
 TAILQ_HEAD(object_q, vm_object);
 
 extern struct object_q vm_object_list;	/* list of allocated objects */
@@ -356,7 +362,7 @@ vm_object_t vm_object_allocate_anon(vm_pindex_t, vm_object_t, struct ucred *,
    vm_size_t);
 vm_object_t vm_object_allocate_dyn(objtype_t, vm_pindex_t, u_short);
 boolean_t vm_object_coalesce(vm_object_t, vm_ooffset_t, vm_size_t, vm_size_t,
-   boolean_t);
+   int);
 void vm_object_collapse (vm_object_t);
 void vm_object_deallocate (vm_object_t);
 void vm_object_destroy (vm_object_t);