Re: [PATCH] mm: memcontrol: move memsw charge callbacks to v1

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri 24-01-25 00:41:32, Johannes Weiner wrote:
> The interweaving of two entirely different swap accounting strategies
> has been one of the more confusing parts of the memcg code. Split out
> the v1 code to clarify the implementation and a handful of callsites,
> and to avoid building the v1 bits when !CONFIG_MEMCG_V1.
> 
>    text	  data	   bss	   dec	   hex	filename
>   39253	  6446	  4160	 49859	  c2c3	mm/memcontrol.o.old
>   38877	  6382	  4160	 49419	  c10b	mm/memcontrol.o
> 
> Signed-off-by: Johannes Weiner <hannes@xxxxxxxxxxx>

Acked-by: Michal Hocko <mhocko@xxxxxxxx>
Thanks!

> ---
>  include/linux/memcontrol.h |  17 +++--
>  include/linux/swap.h       |   5 --
>  mm/huge_memory.c           |   2 +-
>  mm/memcontrol-v1.c         |  89 ++++++++++++++++++++++++-
>  mm/memcontrol-v1.h         |   6 +-
>  mm/memcontrol.c            | 129 ++++++-------------------------------
>  mm/memory.c                |   2 +-
>  mm/shmem.c                 |   2 +-
>  mm/swap_state.c            |   2 +-
>  mm/vmscan.c                |   2 +-
>  10 files changed, 126 insertions(+), 130 deletions(-)
> 
> diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> index 6e74b8254d9b..57664e2a8fb7 100644
> --- a/include/linux/memcontrol.h
> +++ b/include/linux/memcontrol.h
> @@ -649,8 +649,6 @@ int mem_cgroup_charge_hugetlb(struct folio* folio, gfp_t gfp);
>  int mem_cgroup_swapin_charge_folio(struct folio *folio, struct mm_struct *mm,
>  				  gfp_t gfp, swp_entry_t entry);
>  
> -void mem_cgroup_swapin_uncharge_swap(swp_entry_t entry, unsigned int nr_pages);
> -
>  void __mem_cgroup_uncharge(struct folio *folio);
>  
>  /**
> @@ -1165,10 +1163,6 @@ static inline int mem_cgroup_swapin_charge_folio(struct folio *folio,
>  	return 0;
>  }
>  
> -static inline void mem_cgroup_swapin_uncharge_swap(swp_entry_t entry, unsigned int nr)
> -{
> -}
> -
>  static inline void mem_cgroup_uncharge(struct folio *folio)
>  {
>  }
> @@ -1848,6 +1842,9 @@ static inline void mem_cgroup_exit_user_fault(void)
>  	current->in_user_fault = 0;
>  }
>  
> +void memcg1_swapout(struct folio *folio, swp_entry_t entry);
> +void memcg1_swapin(swp_entry_t entry, unsigned int nr_pages);
> +
>  #else /* CONFIG_MEMCG_V1 */
>  static inline
>  unsigned long memcg1_soft_limit_reclaim(pg_data_t *pgdat, int order,
> @@ -1875,6 +1872,14 @@ static inline void mem_cgroup_exit_user_fault(void)
>  {
>  }
>  
> +static inline void memcg1_swapout(struct folio *folio, swp_entry_t entry)
> +{
> +}
> +
> +static inline void memcg1_swapin(swp_entry_t entry, unsigned int nr_pages)
> +{
> +}
> +
>  #endif /* CONFIG_MEMCG_V1 */
>  
>  #endif /* _LINUX_MEMCONTROL_H */
> diff --git a/include/linux/swap.h b/include/linux/swap.h
> index b13b72645db3..91b30701274e 100644
> --- a/include/linux/swap.h
> +++ b/include/linux/swap.h
> @@ -659,7 +659,6 @@ static inline void folio_throttle_swaprate(struct folio *folio, gfp_t gfp)
>  #endif
>  
>  #if defined(CONFIG_MEMCG) && defined(CONFIG_SWAP)
> -void mem_cgroup_swapout(struct folio *folio, swp_entry_t entry);
>  int __mem_cgroup_try_charge_swap(struct folio *folio, swp_entry_t entry);
>  static inline int mem_cgroup_try_charge_swap(struct folio *folio,
>  		swp_entry_t entry)
> @@ -680,10 +679,6 @@ static inline void mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_p
>  extern long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg);
>  extern bool mem_cgroup_swap_full(struct folio *folio);
>  #else
> -static inline void mem_cgroup_swapout(struct folio *folio, swp_entry_t entry)
> -{
> -}
> -
>  static inline int mem_cgroup_try_charge_swap(struct folio *folio,
>  					     swp_entry_t entry)
>  {
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 3d3ebdc002d5..c40b42a1015a 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -3740,7 +3740,7 @@ void deferred_split_folio(struct folio *folio, bool partially_mapped)
>  
>  	/*
>  	 * Exclude swapcache: originally to avoid a corrupt deferred split
> -	 * queue. Nowadays that is fully prevented by mem_cgroup_swapout();
> +	 * queue. Nowadays that is fully prevented by memcg1_swapout();
>  	 * but if page reclaim is already handling the same folio, it is
>  	 * unnecessary to handle it again in the shrinker, so excluding
>  	 * swapcache here may still be a useful optimization.
> diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c
> index 6d184fae0ad1..1d16a99fb964 100644
> --- a/mm/memcontrol-v1.c
> +++ b/mm/memcontrol-v1.c
> @@ -581,8 +581,59 @@ void memcg1_commit_charge(struct folio *folio, struct mem_cgroup *memcg)
>  	local_irq_restore(flags);
>  }
>  
> -void memcg1_swapout(struct folio *folio, struct mem_cgroup *memcg)
> +/**
> + * memcg1_swapout - transfer a memsw charge to swap
> + * @folio: folio whose memsw charge to transfer
> + * @entry: swap entry to move the charge to
> + *
> + * Transfer the memsw charge of @folio to @entry.
> + */
> +void memcg1_swapout(struct folio *folio, swp_entry_t entry)
>  {
> +	struct mem_cgroup *memcg, *swap_memcg;
> +	unsigned int nr_entries;
> +
> +	VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
> +	VM_BUG_ON_FOLIO(folio_ref_count(folio), folio);
> +
> +	if (mem_cgroup_disabled())
> +		return;
> +
> +	if (!do_memsw_account())
> +		return;
> +
> +	memcg = folio_memcg(folio);
> +
> +	VM_WARN_ON_ONCE_FOLIO(!memcg, folio);
> +	if (!memcg)
> +		return;
> +
> +	/*
> +	 * In case the memcg owning these pages has been offlined and doesn't
> +	 * have an ID allocated to it anymore, charge the closest online
> +	 * ancestor for the swap instead and transfer the memory+swap charge.
> +	 */
> +	swap_memcg = mem_cgroup_id_get_online(memcg);
> +	nr_entries = folio_nr_pages(folio);
> +	/* Get references for the tail pages, too */
> +	if (nr_entries > 1)
> +		mem_cgroup_id_get_many(swap_memcg, nr_entries - 1);
> +	mod_memcg_state(swap_memcg, MEMCG_SWAP, nr_entries);
> +
> +	swap_cgroup_record(folio, entry);
> +
> +	folio_unqueue_deferred_split(folio);
> +	folio->memcg_data = 0;
> +
> +	if (!mem_cgroup_is_root(memcg))
> +		page_counter_uncharge(&memcg->memory, nr_entries);
> +
> +	if (memcg != swap_memcg) {
> +		if (!mem_cgroup_is_root(swap_memcg))
> +			page_counter_charge(&swap_memcg->memsw, nr_entries);
> +		page_counter_uncharge(&memcg->memsw, nr_entries);
> +	}
> +
>  	/*
>  	 * Interrupts should be disabled here because the caller holds the
>  	 * i_pages lock which is taken with interrupts-off. It is
> @@ -594,6 +645,42 @@ void memcg1_swapout(struct folio *folio, struct mem_cgroup *memcg)
>  	memcg1_charge_statistics(memcg, -folio_nr_pages(folio));
>  	preempt_enable_nested();
>  	memcg1_check_events(memcg, folio_nid(folio));
> +
> +	css_put(&memcg->css);
> +}
> +
> +/*
> + * memcg1_swapin - uncharge swap slot
> + * @entry: the first swap entry for which the pages are charged
> + * @nr_pages: number of pages which will be uncharged
> + *
> + * Call this function after successfully adding the charged page to swapcache.
> + *
> + * Note: This function assumes the page for which swap slot is being uncharged
> + * is order 0 page.
> + */
> +void memcg1_swapin(swp_entry_t entry, unsigned int nr_pages)
> +{
> +	/*
> +	 * Cgroup1's unified memory+swap counter has been charged with the
> +	 * new swapcache page, finish the transfer by uncharging the swap
> +	 * slot. The swap slot would also get uncharged when it dies, but
> +	 * it can stick around indefinitely and we'd count the page twice
> +	 * the entire time.
> +	 *
> +	 * Cgroup2 has separate resource counters for memory and swap,
> +	 * so this is a non-issue here. Memory and swap charge lifetimes
> +	 * correspond 1:1 to page and swap slot lifetimes: we charge the
> +	 * page to memory here, and uncharge swap when the slot is freed.
> +	 */
> +	if (do_memsw_account()) {
> +		/*
> +		 * The swap entry might not get freed for a long time,
> +		 * let's not wait for it.  The page already received a
> +		 * memory+swap charge, drop the swap entry duplicate.
> +		 */
> +		mem_cgroup_uncharge_swap(entry, nr_pages);
> +	}
>  }
>  
>  void memcg1_uncharge_batch(struct mem_cgroup *memcg, unsigned long pgpgout,
> diff --git a/mm/memcontrol-v1.h b/mm/memcontrol-v1.h
> index 4c8f36430fe9..1dc759e65471 100644
> --- a/mm/memcontrol-v1.h
> +++ b/mm/memcontrol-v1.h
> @@ -39,6 +39,9 @@ unsigned long memcg_page_state_output(struct mem_cgroup *memcg, int item);
>  unsigned long memcg_page_state_local_output(struct mem_cgroup *memcg, int item);
>  int memory_stat_show(struct seq_file *m, void *v);
>  
> +void mem_cgroup_id_get_many(struct mem_cgroup *memcg, unsigned int n);
> +struct mem_cgroup *mem_cgroup_id_get_online(struct mem_cgroup *memcg);
> +
>  /* Cgroup v1-specific declarations */
>  #ifdef CONFIG_MEMCG_V1
>  
> @@ -69,7 +72,6 @@ void memcg1_oom_finish(struct mem_cgroup *memcg, bool locked);
>  void memcg1_oom_recover(struct mem_cgroup *memcg);
>  
>  void memcg1_commit_charge(struct folio *folio, struct mem_cgroup *memcg);
> -void memcg1_swapout(struct folio *folio, struct mem_cgroup *memcg);
>  void memcg1_uncharge_batch(struct mem_cgroup *memcg, unsigned long pgpgout,
>  			   unsigned long nr_memory, int nid);
>  
> @@ -107,8 +109,6 @@ static inline void memcg1_oom_recover(struct mem_cgroup *memcg) {}
>  static inline void memcg1_commit_charge(struct folio *folio,
>  					struct mem_cgroup *memcg) {}
>  
> -static inline void memcg1_swapout(struct folio *folio, struct mem_cgroup *memcg) {}
> -
>  static inline void memcg1_uncharge_batch(struct mem_cgroup *memcg,
>  					 unsigned long pgpgout,
>  					 unsigned long nr_memory, int nid) {}
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index 818143b81760..a95cb3fbb2c8 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -3377,7 +3377,7 @@ static void mem_cgroup_id_remove(struct mem_cgroup *memcg)
>  	}
>  }
>  
> -static void __maybe_unused mem_cgroup_id_get_many(struct mem_cgroup *memcg,
> +void __maybe_unused mem_cgroup_id_get_many(struct mem_cgroup *memcg,
>  					   unsigned int n)
>  {
>  	refcount_add(n, &memcg->id.ref);
> @@ -3398,6 +3398,24 @@ static inline void mem_cgroup_id_put(struct mem_cgroup *memcg)
>  	mem_cgroup_id_put_many(memcg, 1);
>  }
>  
> +struct mem_cgroup *mem_cgroup_id_get_online(struct mem_cgroup *memcg)
> +{
> +	while (!refcount_inc_not_zero(&memcg->id.ref)) {
> +		/*
> +		 * The root cgroup cannot be destroyed, so it's refcount must
> +		 * always be >= 1.
> +		 */
> +		if (WARN_ON_ONCE(mem_cgroup_is_root(memcg))) {
> +			VM_BUG_ON(1);
> +			break;
> +		}
> +		memcg = parent_mem_cgroup(memcg);
> +		if (!memcg)
> +			memcg = root_mem_cgroup;
> +	}
> +	return memcg;
> +}
> +
>  /**
>   * mem_cgroup_from_id - look up a memcg from a memcg id
>   * @id: the memcg id to look up
> @@ -4585,40 +4603,6 @@ int mem_cgroup_swapin_charge_folio(struct folio *folio, struct mm_struct *mm,
>  	return ret;
>  }
>  
> -/*
> - * mem_cgroup_swapin_uncharge_swap - uncharge swap slot
> - * @entry: the first swap entry for which the pages are charged
> - * @nr_pages: number of pages which will be uncharged
> - *
> - * Call this function after successfully adding the charged page to swapcache.
> - *
> - * Note: This function assumes the page for which swap slot is being uncharged
> - * is order 0 page.
> - */
> -void mem_cgroup_swapin_uncharge_swap(swp_entry_t entry, unsigned int nr_pages)
> -{
> -	/*
> -	 * Cgroup1's unified memory+swap counter has been charged with the
> -	 * new swapcache page, finish the transfer by uncharging the swap
> -	 * slot. The swap slot would also get uncharged when it dies, but
> -	 * it can stick around indefinitely and we'd count the page twice
> -	 * the entire time.
> -	 *
> -	 * Cgroup2 has separate resource counters for memory and swap,
> -	 * so this is a non-issue here. Memory and swap charge lifetimes
> -	 * correspond 1:1 to page and swap slot lifetimes: we charge the
> -	 * page to memory here, and uncharge swap when the slot is freed.
> -	 */
> -	if (do_memsw_account()) {
> -		/*
> -		 * The swap entry might not get freed for a long time,
> -		 * let's not wait for it.  The page already received a
> -		 * memory+swap charge, drop the swap entry duplicate.
> -		 */
> -		mem_cgroup_uncharge_swap(entry, nr_pages);
> -	}
> -}
> -
>  struct uncharge_gather {
>  	struct mem_cgroup *memcg;
>  	unsigned long nr_memory;
> @@ -4944,81 +4928,6 @@ static int __init mem_cgroup_init(void)
>  subsys_initcall(mem_cgroup_init);
>  
>  #ifdef CONFIG_SWAP
> -static struct mem_cgroup *mem_cgroup_id_get_online(struct mem_cgroup *memcg)
> -{
> -	while (!refcount_inc_not_zero(&memcg->id.ref)) {
> -		/*
> -		 * The root cgroup cannot be destroyed, so it's refcount must
> -		 * always be >= 1.
> -		 */
> -		if (WARN_ON_ONCE(mem_cgroup_is_root(memcg))) {
> -			VM_BUG_ON(1);
> -			break;
> -		}
> -		memcg = parent_mem_cgroup(memcg);
> -		if (!memcg)
> -			memcg = root_mem_cgroup;
> -	}
> -	return memcg;
> -}
> -
> -/**
> - * mem_cgroup_swapout - transfer a memsw charge to swap
> - * @folio: folio whose memsw charge to transfer
> - * @entry: swap entry to move the charge to
> - *
> - * Transfer the memsw charge of @folio to @entry.
> - */
> -void mem_cgroup_swapout(struct folio *folio, swp_entry_t entry)
> -{
> -	struct mem_cgroup *memcg, *swap_memcg;
> -	unsigned int nr_entries;
> -
> -	VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
> -	VM_BUG_ON_FOLIO(folio_ref_count(folio), folio);
> -
> -	if (mem_cgroup_disabled())
> -		return;
> -
> -	if (!do_memsw_account())
> -		return;
> -
> -	memcg = folio_memcg(folio);
> -
> -	VM_WARN_ON_ONCE_FOLIO(!memcg, folio);
> -	if (!memcg)
> -		return;
> -
> -	/*
> -	 * In case the memcg owning these pages has been offlined and doesn't
> -	 * have an ID allocated to it anymore, charge the closest online
> -	 * ancestor for the swap instead and transfer the memory+swap charge.
> -	 */
> -	swap_memcg = mem_cgroup_id_get_online(memcg);
> -	nr_entries = folio_nr_pages(folio);
> -	/* Get references for the tail pages, too */
> -	if (nr_entries > 1)
> -		mem_cgroup_id_get_many(swap_memcg, nr_entries - 1);
> -	mod_memcg_state(swap_memcg, MEMCG_SWAP, nr_entries);
> -
> -	swap_cgroup_record(folio, entry);
> -
> -	folio_unqueue_deferred_split(folio);
> -	folio->memcg_data = 0;
> -
> -	if (!mem_cgroup_is_root(memcg))
> -		page_counter_uncharge(&memcg->memory, nr_entries);
> -
> -	if (memcg != swap_memcg) {
> -		if (!mem_cgroup_is_root(swap_memcg))
> -			page_counter_charge(&swap_memcg->memsw, nr_entries);
> -		page_counter_uncharge(&memcg->memsw, nr_entries);
> -	}
> -
> -	memcg1_swapout(folio, memcg);
> -	css_put(&memcg->css);
> -}
> -
>  /**
>   * __mem_cgroup_try_charge_swap - try charging swap space for a folio
>   * @folio: folio being added to swap
> diff --git a/mm/memory.c b/mm/memory.c
> index 2a20e3810534..708ae27673b1 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -4393,7 +4393,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
>  				}
>  				need_clear_cache = true;
>  
> -				mem_cgroup_swapin_uncharge_swap(entry, nr_pages);
> +				memcg1_swapin(entry, nr_pages);
>  
>  				shadow = get_shadow_from_swap_cache(entry);
>  				if (shadow)
> diff --git a/mm/shmem.c b/mm/shmem.c
> index 44379bee5b96..d885ecb6fe1e 100644
> --- a/mm/shmem.c
> +++ b/mm/shmem.c
> @@ -2017,7 +2017,7 @@ static struct folio *shmem_swap_alloc_folio(struct inode *inode,
>  	__folio_set_swapbacked(new);
>  	new->swap = entry;
>  
> -	mem_cgroup_swapin_uncharge_swap(entry, nr_pages);
> +	memcg1_swapin(entry, nr_pages);
>  	shadow = get_shadow_from_swap_cache(entry);
>  	if (shadow)
>  		workingset_refault(new, shadow);
> diff --git a/mm/swap_state.c b/mm/swap_state.c
> index ca42b2be64d9..2e1acb210e57 100644
> --- a/mm/swap_state.c
> +++ b/mm/swap_state.c
> @@ -521,7 +521,7 @@ struct folio *__read_swap_cache_async(swp_entry_t entry, gfp_t gfp_mask,
>  	if (add_to_swap_cache(new_folio, entry, gfp_mask & GFP_RECLAIM_MASK, &shadow))
>  		goto fail_unlock;
>  
> -	mem_cgroup_swapin_uncharge_swap(entry, 1);
> +	memcg1_swapin(entry, 1);
>  
>  	if (shadow)
>  		workingset_refault(new_folio, shadow);
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 3bbe917b6a34..b2b2f27b10a0 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -769,7 +769,7 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
>  		if (reclaimed && !mapping_exiting(mapping))
>  			shadow = workingset_eviction(folio, target_memcg);
>  		__delete_from_swap_cache(folio, swap, shadow);
> -		mem_cgroup_swapout(folio, swap);
> +		memcg1_swapout(folio, swap);
>  		xa_unlock_irq(&mapping->i_pages);
>  		put_swap_folio(folio, swap);
>  	} else {
> -- 
> 2.48.1

-- 
Michal Hocko
SUSE Labs




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux OMAP]     [Linux MIPS]     [eCos]     [Asterisk Internet PBX]     [Linux API]     [Monitors]

  Powered by Linux