[PATCH v2] erofs: convert workstn to XArray

Chao Yu yuchao0 at huawei.com
Wed Feb 19 18:46:16 AEDT 2020


On 2020/2/17 11:30, Gao Xiang wrote:
> XArray has friendly apis and it will replace the old radix
> tree in the near future.
> 
> This convert makes use of __xa_cmpxchg when inserting on
> a just inserted item by other thread. In detail, instead
> of totally looking up again as what we did for the old
> radix tree, it will try to legitimize the current in-tree
> item in the XArray therefore more effective.
> 
> In addition, naming is rather a challenge for non-English
> speaker like me. The basic idea of workstn is to provide
> a runtime sparse array with items arranged in the physical
> block number order. Such items (was called workgroup) can be
> used to record compress clusters or for later new features.
> 
> However, both workgroup and workstn seem not good names from
> whatever point of view, so I'd like to rename them as pslot
> and managed_pslots to stand for physical slots. This patch
> handles the second as a part of the radix root convert.
> 
> Cc: Chao Yu <yuchao0 at huawei.com>
> Cc: Matthew Wilcox <willy at infradead.org>
> Signed-off-by: Gao Xiang <gaoxiang25 at huawei.com>
> ---
> changes since v1:
>  - update the comment above "struct xarray managed_pslots";
>  - get rid of "comparison to NULL" style in v1;
>  - stress tested without strange behaviors;
>    https://lore.kernel.org/r/20200206135631.1491-1-hsiangkao@aol.com
> 
>  fs/erofs/internal.h |  8 ++---
>  fs/erofs/super.c    |  2 +-
>  fs/erofs/utils.c    | 85 ++++++++++++++++-----------------------------
>  fs/erofs/zdata.c    | 65 ++++++++++++++++------------------
>  4 files changed, 64 insertions(+), 96 deletions(-)
> 
> diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
> index c4c6dcdc89ad..b70f52c80852 100644
> --- a/fs/erofs/internal.h
> +++ b/fs/erofs/internal.h
> @@ -52,8 +52,8 @@ struct erofs_sb_info {
>  	struct list_head list;
>  	struct mutex umount_mutex;
>  
> -	/* the dedicated workstation for compression */
> -	struct radix_tree_root workstn_tree;
> +	/* managed XArray arranged in physical block number */
> +	struct xarray managed_pslots;
>  
>  	/* threshold for decompression synchronously */
>  	unsigned int max_sync_decompress_pages;
> @@ -402,8 +402,8 @@ static inline void *erofs_get_pcpubuf(unsigned int pagenr)
>  int erofs_workgroup_put(struct erofs_workgroup *grp);
>  struct erofs_workgroup *erofs_find_workgroup(struct super_block *sb,
>  					     pgoff_t index);
> -int erofs_register_workgroup(struct super_block *sb,
> -			     struct erofs_workgroup *grp);
> +struct erofs_workgroup *erofs_insert_workgroup(struct super_block *sb,
> +					       struct erofs_workgroup *grp);
>  void erofs_workgroup_free_rcu(struct erofs_workgroup *grp);
>  void erofs_shrinker_register(struct super_block *sb);
>  void erofs_shrinker_unregister(struct super_block *sb);
> diff --git a/fs/erofs/super.c b/fs/erofs/super.c
> index 057e6d7b5b7f..b514c67e5fc2 100644
> --- a/fs/erofs/super.c
> +++ b/fs/erofs/super.c
> @@ -425,7 +425,7 @@ static int erofs_fill_super(struct super_block *sb, void *data, int silent)
>  		sb->s_flags &= ~SB_POSIXACL;
>  
>  #ifdef CONFIG_EROFS_FS_ZIP
> -	INIT_RADIX_TREE(&sbi->workstn_tree, GFP_ATOMIC);
> +	xa_init(&sbi->managed_pslots);
>  #endif
>  
>  	/* get the root inode */
> diff --git a/fs/erofs/utils.c b/fs/erofs/utils.c
> index fddc5059c930..56ab5b3f73b0 100644
> --- a/fs/erofs/utils.c
> +++ b/fs/erofs/utils.c
> @@ -37,9 +37,6 @@ void *erofs_get_pcpubuf(unsigned int pagenr)
>  /* global shrink count (for all mounted EROFS instances) */
>  static atomic_long_t erofs_global_shrink_cnt;
>  
> -#define __erofs_workgroup_get(grp)	atomic_inc(&(grp)->refcount)
> -#define __erofs_workgroup_put(grp)	atomic_dec(&(grp)->refcount)
> -
>  static int erofs_workgroup_get(struct erofs_workgroup *grp)
>  {
>  	int o;
> @@ -66,7 +63,7 @@ struct erofs_workgroup *erofs_find_workgroup(struct super_block *sb,
>  
>  repeat:
>  	rcu_read_lock();
> -	grp = radix_tree_lookup(&sbi->workstn_tree, index);
> +	grp = xa_load(&sbi->managed_pslots, index);
>  	if (grp) {
>  		if (erofs_workgroup_get(grp)) {
>  			/* prefer to relax rcu read side */
> @@ -80,43 +77,36 @@ struct erofs_workgroup *erofs_find_workgroup(struct super_block *sb,
>  	return grp;
>  }
>  
> -int erofs_register_workgroup(struct super_block *sb,
> -			     struct erofs_workgroup *grp)
> +struct erofs_workgroup *erofs_insert_workgroup(struct super_block *sb,
> +					       struct erofs_workgroup *grp)
>  {
>  	struct erofs_sb_info *sbi;
> -	int err;
> -
> -	/* grp shouldn't be broken or used before */
> -	if (atomic_read(&grp->refcount) != 1) {
> -		DBG_BUGON(1);
> -		return -EINVAL;
> -	}
> -
> -	err = radix_tree_preload(GFP_NOFS);
> -	if (err)
> -		return err;
> -
> -	sbi = EROFS_SB(sb);
> -	xa_lock(&sbi->workstn_tree);
> +	struct erofs_workgroup *pre;
>  
>  	/*
> -	 * Bump up reference count before making this workgroup
> -	 * visible to other users in order to avoid potential UAF
> -	 * without serialized by workstn_lock.
> +	 * Bump up a reference count before making this visible
> +	 * to others for the XArray in order to avoid potential
> +	 * UAF without serialized by xa_lock.
>  	 */
> -	__erofs_workgroup_get(grp);
> +	atomic_inc(&grp->refcount);
>  
> -	err = radix_tree_insert(&sbi->workstn_tree, grp->index, grp);
> -	if (err)
> -		/*
> -		 * it's safe to decrease since the workgroup isn't visible
> -		 * and refcount >= 2 (cannot be freezed).
> -		 */
> -		__erofs_workgroup_put(grp);
> -
> -	xa_unlock(&sbi->workstn_tree);
> -	radix_tree_preload_end();
> -	return err;
> +	sbi = EROFS_SB(sb);
> +repeat:
> +	xa_lock(&sbi->managed_pslots);
> +	pre = __xa_cmpxchg(&sbi->managed_pslots, grp->index,
> +			   NULL, grp, GFP_NOFS);
> +	if (pre) {

It looks __xa_cmpxchg() could return negative value in case of failure, e.g.
no memory case. We'd better handle that case and old valid workgroup separately?

Thanks,

> +		/* try to legitimize the current in-tree one */
> +		if (erofs_workgroup_get(pre)) {
> +			xa_unlock(&sbi->managed_pslots);
> +			cond_resched();
> +			goto repeat;
> +		}
> +		atomic_dec(&grp->refcount);
> +		grp = pre;
> +	}
> +	xa_unlock(&sbi->managed_pslots);
> +	return grp;
>  }
>  
>  static void  __erofs_workgroup_free(struct erofs_workgroup *grp)
> @@ -155,7 +145,7 @@ static bool erofs_try_to_release_workgroup(struct erofs_sb_info *sbi,
>  
>  	/*
>  	 * Note that all cached pages should be unattached
> -	 * before deleted from the radix tree. Otherwise some
> +	 * before deleted from the XArray. Otherwise some
>  	 * cached pages could be still attached to the orphan
>  	 * old workgroup when the new one is available in the tree.
>  	 */
> @@ -169,7 +159,7 @@ static bool erofs_try_to_release_workgroup(struct erofs_sb_info *sbi,
>  	 * however in order to avoid some race conditions, add a
>  	 * DBG_BUGON to observe this in advance.
>  	 */
> -	DBG_BUGON(radix_tree_delete(&sbi->workstn_tree, grp->index) != grp);
> +	DBG_BUGON(xa_erase(&sbi->managed_pslots, grp->index) != grp);
>  
>  	/*
>  	 * If managed cache is on, last refcount should indicate
> @@ -182,22 +172,11 @@ static bool erofs_try_to_release_workgroup(struct erofs_sb_info *sbi,
>  static unsigned long erofs_shrink_workstation(struct erofs_sb_info *sbi,
>  					      unsigned long nr_shrink)
>  {
> -	pgoff_t first_index = 0;
> -	void *batch[PAGEVEC_SIZE];
> +	struct erofs_workgroup *grp;
>  	unsigned int freed = 0;
> +	unsigned long index;
>  
> -	int i, found;
> -repeat:
> -	xa_lock(&sbi->workstn_tree);
> -
> -	found = radix_tree_gang_lookup(&sbi->workstn_tree,
> -				       batch, first_index, PAGEVEC_SIZE);
> -
> -	for (i = 0; i < found; ++i) {
> -		struct erofs_workgroup *grp = batch[i];
> -
> -		first_index = grp->index + 1;
> -
> +	xa_for_each(&sbi->managed_pslots, index, grp) {
>  		/* try to shrink each valid workgroup */
>  		if (!erofs_try_to_release_workgroup(sbi, grp))
>  			continue;
> @@ -206,10 +185,6 @@ static unsigned long erofs_shrink_workstation(struct erofs_sb_info *sbi,
>  		if (!--nr_shrink)
>  			break;
>  	}
> -	xa_unlock(&sbi->workstn_tree);
> -
> -	if (i && nr_shrink)
> -		goto repeat;
>  	return freed;
>  }
>  
> diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c
> index 80e47f07d946..0d77a166068f 100644
> --- a/fs/erofs/zdata.c
> +++ b/fs/erofs/zdata.c
> @@ -67,16 +67,6 @@ static void z_erofs_pcluster_init_once(void *ptr)
>  		pcl->compressed_pages[i] = NULL;
>  }
>  
> -static void z_erofs_pcluster_init_always(struct z_erofs_pcluster *pcl)
> -{
> -	struct z_erofs_collection *cl = z_erofs_primarycollection(pcl);
> -
> -	atomic_set(&pcl->obj.refcount, 1);
> -
> -	DBG_BUGON(cl->nr_pages);
> -	DBG_BUGON(cl->vcnt);
> -}
> -
>  int __init z_erofs_init_zip_subsystem(void)
>  {
>  	pcluster_cachep = kmem_cache_create("erofs_compress",
> @@ -341,26 +331,19 @@ static int z_erofs_lookup_collection(struct z_erofs_collector *clt,
>  				     struct inode *inode,
>  				     struct erofs_map_blocks *map)
>  {
> -	struct erofs_workgroup *grp;
> -	struct z_erofs_pcluster *pcl;
> +	struct z_erofs_pcluster *pcl = clt->pcl;
>  	struct z_erofs_collection *cl;
>  	unsigned int length;
>  
> -	grp = erofs_find_workgroup(inode->i_sb, map->m_pa >> PAGE_SHIFT);
> -	if (!grp)
> -		return -ENOENT;
> -
> -	pcl = container_of(grp, struct z_erofs_pcluster, obj);
> +	/* to avoid unexpected loop formed by corrupted images */
>  	if (clt->owned_head == &pcl->next || pcl == clt->tailpcl) {
>  		DBG_BUGON(1);
> -		erofs_workgroup_put(grp);
>  		return -EFSCORRUPTED;
>  	}
>  
>  	cl = z_erofs_primarycollection(pcl);
>  	if (cl->pageofs != (map->m_la & ~PAGE_MASK)) {
>  		DBG_BUGON(1);
> -		erofs_workgroup_put(grp);
>  		return -EFSCORRUPTED;
>  	}
>  
> @@ -368,7 +351,6 @@ static int z_erofs_lookup_collection(struct z_erofs_collector *clt,
>  	if (length & Z_EROFS_PCLUSTER_FULL_LENGTH) {
>  		if ((map->m_llen << Z_EROFS_PCLUSTER_LENGTH_BIT) > length) {
>  			DBG_BUGON(1);
> -			erofs_workgroup_put(grp);
>  			return -EFSCORRUPTED;
>  		}
>  	} else {
> @@ -391,7 +373,6 @@ static int z_erofs_lookup_collection(struct z_erofs_collector *clt,
>  	/* clean tailpcl if the current owned_head is Z_EROFS_PCLUSTER_TAIL */
>  	if (clt->owned_head == Z_EROFS_PCLUSTER_TAIL)
>  		clt->tailpcl = NULL;
> -	clt->pcl = pcl;
>  	clt->cl = cl;
>  	return 0;
>  }
> @@ -402,14 +383,14 @@ static int z_erofs_register_collection(struct z_erofs_collector *clt,
>  {
>  	struct z_erofs_pcluster *pcl;
>  	struct z_erofs_collection *cl;
> -	int err;
> +	struct erofs_workgroup *grp;
>  
>  	/* no available workgroup, let's allocate one */
>  	pcl = kmem_cache_alloc(pcluster_cachep, GFP_NOFS);
>  	if (!pcl)
>  		return -ENOMEM;
>  
> -	z_erofs_pcluster_init_always(pcl);
> +	atomic_set(&pcl->obj.refcount, 1);
>  	pcl->obj.index = map->m_pa >> PAGE_SHIFT;
>  
>  	pcl->length = (map->m_llen << Z_EROFS_PCLUSTER_LENGTH_BIT) |
> @@ -429,20 +410,27 @@ static int z_erofs_register_collection(struct z_erofs_collector *clt,
>  	clt->mode = COLLECT_PRIMARY_FOLLOWED;
>  
>  	cl = z_erofs_primarycollection(pcl);
> +
> +	/* must be cleaned before freeing to slab */
> +	DBG_BUGON(cl->nr_pages);
> +	DBG_BUGON(cl->vcnt);
> +
>  	cl->pageofs = map->m_la & ~PAGE_MASK;
>  
>  	/*
>  	 * lock all primary followed works before visible to others
>  	 * and mutex_trylock *never* fails for a new pcluster.
>  	 */
> -	mutex_trylock(&cl->lock);
> +	DBG_BUGON(!mutex_trylock(&cl->lock));
>  
> -	err = erofs_register_workgroup(inode->i_sb, &pcl->obj);
> -	if (err) {
> +	grp = erofs_insert_workgroup(inode->i_sb, &pcl->obj);
> +	if (grp != &pcl->obj) {
> +		clt->pcl = container_of(grp, struct z_erofs_pcluster, obj);
>  		mutex_unlock(&cl->lock);
>  		kmem_cache_free(pcluster_cachep, pcl);
> -		return -EAGAIN;
> +		return -EEXIST;
>  	}
> +
>  	/* used to check tail merging loop due to corrupted images */
>  	if (clt->owned_head == Z_EROFS_PCLUSTER_TAIL)
>  		clt->tailpcl = pcl;
> @@ -456,6 +444,7 @@ static int z_erofs_collector_begin(struct z_erofs_collector *clt,
>  				   struct inode *inode,
>  				   struct erofs_map_blocks *map)
>  {
> +	struct erofs_workgroup *grp;
>  	int ret;
>  
>  	DBG_BUGON(clt->cl);
> @@ -469,21 +458,25 @@ static int z_erofs_collector_begin(struct z_erofs_collector *clt,
>  		return -EINVAL;
>  	}
>  
> -repeat:
> -	ret = z_erofs_lookup_collection(clt, inode, map);
> -	if (ret == -ENOENT) {
> +	grp = erofs_find_workgroup(inode->i_sb, map->m_pa >> PAGE_SHIFT);
> +	if (grp) {
> +		clt->pcl = container_of(grp, struct z_erofs_pcluster, obj);
> +	} else {
>  		ret = z_erofs_register_collection(clt, inode, map);
>  
> -		/* someone registered at the same time, give another try */
> -		if (ret == -EAGAIN) {
> -			cond_resched();
> -			goto repeat;
> -		}
> +		if (!ret)
> +			goto out;
> +		if (ret != -EEXIST)
> +			return ret;
>  	}
>  
> -	if (ret)
> +	ret = z_erofs_lookup_collection(clt, inode, map);
> +	if (ret) {
> +		erofs_workgroup_put(&clt->pcl->obj);
>  		return ret;
> +	}
>  
> +out:
>  	z_erofs_pagevec_ctor_init(&clt->vector, Z_EROFS_NR_INLINE_PAGEVECS,
>  				  clt->cl->pagevec, clt->cl->vcnt);
>  
> 


More information about the Linux-erofs mailing list