+#ifdef _KERNEL
+/*
+ * Determine the amount of memory eligible for eviction contained in the
+ * ARC. All clean data reported by the ghost lists can always be safely
+ * evicted. Due to arc_c_min, the same does not hold for all clean data
+ * contained by the regular mru and mfu lists.
+ *
+ * In the case of the regular mru and mfu lists, we need to report as
+ * much clean data as possible, such that evicting that same reported
+ * data will not bring arc_size below arc_c_min. Thus, in certain
+ * circumstances, the total amount of clean data in the mru and mfu
+ * lists might not actually be evictable.
+ *
+ * The following two distinct cases are accounted for:
+ *
+ * 1. The sum of the amount of dirty data contained by both the mru and
+ * mfu lists, plus the ARC's other accounting (e.g. the anon list),
+ * is greater than or equal to arc_c_min.
+ * (i.e. amount of dirty data >= arc_c_min)
+ *
+ * This is the easy case; all clean data contained by the mru and mfu
+ * lists is evictable. Evicting all clean data can only drop arc_size
+ * to the amount of dirty data, which is greater than arc_c_min.
+ *
+ * 2. The sum of the amount of dirty data contained by both the mru and
+ * mfu lists, plus the ARC's other accounting (e.g. the anon list),
+ * is less than arc_c_min.
+ * (i.e. arc_c_min > amount of dirty data)
+ *
+ * 2.1. arc_size is greater than or equal arc_c_min.
+ * (i.e. arc_size >= arc_c_min > amount of dirty data)
+ *
+ * In this case, not all clean data from the regular mru and mfu
+ * lists is actually evictable; we must leave enough clean data
+ * to keep arc_size above arc_c_min. Thus, the maximum amount of
+ * evictable data from the two lists combined, is exactly the
+ * difference between arc_size and arc_c_min.
+ *
+ * 2.2. arc_size is less than arc_c_min
+ * (i.e. arc_c_min > arc_size > amount of dirty data)
+ *
+ * In this case, none of the data contained in the mru and mfu
+ * lists is evictable, even if it's clean. Since arc_size is
+ * already below arc_c_min, evicting any more would only
+ * increase this negative difference.
+ */
+static uint64_t
+arc_evictable_memory(void) {
+ uint64_t arc_clean =
+ arc_mru->arcs_lsize[ARC_BUFC_DATA] +
+ arc_mru->arcs_lsize[ARC_BUFC_METADATA] +
+ arc_mfu->arcs_lsize[ARC_BUFC_DATA] +
+ arc_mfu->arcs_lsize[ARC_BUFC_METADATA];
+ uint64_t ghost_clean =
+ arc_mru_ghost->arcs_lsize[ARC_BUFC_DATA] +
+ arc_mru_ghost->arcs_lsize[ARC_BUFC_METADATA] +
+ arc_mfu_ghost->arcs_lsize[ARC_BUFC_DATA] +
+ arc_mfu_ghost->arcs_lsize[ARC_BUFC_METADATA];
+ uint64_t arc_dirty = MAX((int64_t)arc_size - (int64_t)arc_clean, 0);
+
+ if (arc_dirty >= arc_c_min)
+ return (ghost_clean + arc_clean);
+
+ return (ghost_clean + MAX((int64_t)arc_size - (int64_t)arc_c_min, 0));
+}
+
+static int
+__arc_shrinker_func(struct shrinker *shrink, struct shrink_control *sc)
+{
+ uint64_t pages;
+
+ /* The arc is considered warm once reclaim has occurred */
+ if (unlikely(arc_warm == B_FALSE))
+ arc_warm = B_TRUE;
+
+ /* Return the potential number of reclaimable pages */
+ pages = btop(arc_evictable_memory());
+ if (sc->nr_to_scan == 0)
+ return (pages);
+
+ /* Not allowed to perform filesystem reclaim */
+ if (!(sc->gfp_mask & __GFP_FS))
+ return (-1);
+
+ /* Reclaim in progress */
+ if (mutex_tryenter(&arc_reclaim_thr_lock) == 0)
+ return (-1);
+
+ /*
+ * Evict the requested number of pages by shrinking arc_c the
+ * requested amount. If there is nothing left to evict just
+ * reap whatever we can from the various arc slabs.
+ */
+ if (pages > 0) {
+ arc_kmem_reap_now(ARC_RECLAIM_AGGR, ptob(sc->nr_to_scan));
+ pages = btop(arc_evictable_memory());
+ } else {
+ arc_kmem_reap_now(ARC_RECLAIM_CONS, ptob(sc->nr_to_scan));
+ pages = -1;
+ }
+
+ /*
+ * When direct reclaim is observed it usually indicates a rapid
+ * increase in memory pressure. This occurs because the kswapd
+ * threads were unable to asynchronously keep enough free memory
+ * available. In this case set arc_no_grow to briefly pause arc
+ * growth to avoid compounding the memory pressure.
+ */
+ if (current_is_kswapd()) {
+ ARCSTAT_BUMP(arcstat_memory_indirect_count);
+ } else {
+ arc_no_grow = B_TRUE;
+ arc_grow_time = ddi_get_lbolt() + (arc_grow_retry * hz);
+ ARCSTAT_BUMP(arcstat_memory_direct_count);
+ }
+
+ mutex_exit(&arc_reclaim_thr_lock);
+
+ return (pages);
+}
+SPL_SHRINKER_CALLBACK_WRAPPER(arc_shrinker_func);
+
+SPL_SHRINKER_DECLARE(arc_shrinker, arc_shrinker_func, DEFAULT_SEEKS);
+#endif /* _KERNEL */
+