[PATCH v4 3/5] mm/page_counter: introduce page_counter_try_charge_stock()
From: Joshua Hahn
Date: Tue Jun 23 2026 - 14:02:15 EST
Add a stock-aware variant of page_counter_try_charge.
As before with try_charge_memcg, it first tries to satisfy the charge by
consuming the per-cpu stock (and skipping the hierarchical charge). On a
miss, it tries to greedily overcharge up to counter->batch pages to
refill the stock. Finally, if this fails, it tries to charge exactly the
requested number of pages.
The number of pages that were charged to the page_counter is reported
back to the caller, so that stock hits don't trigger memory limit
checks.
The variant is unused for now; memcg is converted in a later patch.
Signed-off-by: Joshua Hahn <joshua.hahnjy@xxxxxxxxx>
---
include/linux/page_counter.h | 4 +++
mm/page_counter.c | 48 ++++++++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+)
diff --git a/include/linux/page_counter.h b/include/linux/page_counter.h
index 4abc7fe7c3494..b97b5491447e4 100644
--- a/include/linux/page_counter.h
+++ b/include/linux/page_counter.h
@@ -84,6 +84,10 @@ void page_counter_charge(struct page_counter *counter, unsigned long nr_pages);
bool page_counter_try_charge(struct page_counter *counter,
unsigned long nr_pages,
struct page_counter **fail);
+bool page_counter_try_charge_stock(struct page_counter *counter,
+ unsigned long nr_pages,
+ struct page_counter **fail,
+ unsigned long *nr_charged);
void page_counter_uncharge(struct page_counter *counter, unsigned long nr_pages);
void page_counter_set_min(struct page_counter *counter, unsigned long nr_pages);
void page_counter_set_low(struct page_counter *counter, unsigned long nr_pages);
diff --git a/mm/page_counter.c b/mm/page_counter.c
index 6bb48a913a90d..cce3af3f19e03 100644
--- a/mm/page_counter.c
+++ b/mm/page_counter.c
@@ -172,6 +172,54 @@ bool page_counter_try_charge(struct page_counter *counter,
return false;
}
+bool page_counter_try_charge_stock(struct page_counter *counter,
+ unsigned long nr_pages,
+ struct page_counter **fail,
+ unsigned long *nr_charged)
+{
+ struct page_counter_stock *stock;
+ unsigned long charge = 0;
+ int old;
+
+ if (!counter->stock)
+ goto charge_exact;
+
+ preempt_disable();
+ stock = this_cpu_ptr(counter->stock);
+ old = atomic_read(&stock->nr_pages);
+ while ((unsigned long)old >= nr_pages) {
+ if (atomic_try_cmpxchg(&stock->nr_pages, &old,
+ old - (int)nr_pages)) {
+ preempt_enable();
+ goto out_success;
+ }
+ }
+ preempt_enable();
+
+ charge = max_t(unsigned long, READ_ONCE(counter->batch), nr_pages);
+ if (charge <= nr_pages)
+ goto charge_exact;
+
+ if (page_counter_try_charge(counter, charge, fail)) {
+ preempt_disable();
+ stock = this_cpu_ptr(counter->stock);
+ atomic_add((int)(charge - nr_pages), &stock->nr_pages);
+ preempt_enable();
+ goto out_success;
+ }
+
+charge_exact:
+ /* stock is not enabled, no need for surplus, or greedy charge failed */
+ charge = nr_pages;
+ if (!page_counter_try_charge(counter, charge, fail))
+ return false;
+
+out_success:
+ if (nr_charged)
+ *nr_charged = charge;
+ return true;
+}
+
/**
* page_counter_uncharge - hierarchically uncharge pages
* @counter: counter
--
2.53.0-Meta