mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	Btrfs: using rcu lock in the reader side of devices list
fs_devices->devices is only updated on remove and add device paths, so we can use rcu to protect it in the reader side Signed-off-by: Xiao Guangrong <xiaoguangrong@cn.fujitsu.com> Signed-off-by: Chris Mason <chris.mason@oracle.com>
This commit is contained in:
		
							parent
							
								
									4622470565
								
							
						
					
					
						commit
						1f78160ce1
					
				
					 4 changed files with 72 additions and 36 deletions
				
			
		| 
						 | 
					@ -1410,8 +1410,8 @@ static int btrfs_congested_fn(void *congested_data, int bdi_bits)
 | 
				
			||||||
	struct btrfs_device *device;
 | 
						struct btrfs_device *device;
 | 
				
			||||||
	struct backing_dev_info *bdi;
 | 
						struct backing_dev_info *bdi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mutex_lock(&info->fs_devices->device_list_mutex);
 | 
						rcu_read_lock();
 | 
				
			||||||
	list_for_each_entry(device, &info->fs_devices->devices, dev_list) {
 | 
						list_for_each_entry_rcu(device, &info->fs_devices->devices, dev_list) {
 | 
				
			||||||
		if (!device->bdev)
 | 
							if (!device->bdev)
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		bdi = blk_get_backing_dev_info(device->bdev);
 | 
							bdi = blk_get_backing_dev_info(device->bdev);
 | 
				
			||||||
| 
						 | 
					@ -1420,7 +1420,7 @@ static int btrfs_congested_fn(void *congested_data, int bdi_bits)
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	mutex_unlock(&info->fs_devices->device_list_mutex);
 | 
						rcu_read_unlock();
 | 
				
			||||||
	return ret;
 | 
						return ret;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2332,9 +2332,9 @@ int write_all_supers(struct btrfs_root *root, int max_mirrors)
 | 
				
			||||||
	sb = &root->fs_info->super_for_commit;
 | 
						sb = &root->fs_info->super_for_commit;
 | 
				
			||||||
	dev_item = &sb->dev_item;
 | 
						dev_item = &sb->dev_item;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mutex_lock(&root->fs_info->fs_devices->device_list_mutex);
 | 
						rcu_read_lock();
 | 
				
			||||||
	head = &root->fs_info->fs_devices->devices;
 | 
						head = &root->fs_info->fs_devices->devices;
 | 
				
			||||||
	list_for_each_entry(dev, head, dev_list) {
 | 
						list_for_each_entry_rcu(dev, head, dev_list) {
 | 
				
			||||||
		if (!dev->bdev) {
 | 
							if (!dev->bdev) {
 | 
				
			||||||
			total_errors++;
 | 
								total_errors++;
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
| 
						 | 
					@ -2367,7 +2367,7 @@ int write_all_supers(struct btrfs_root *root, int max_mirrors)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	total_errors = 0;
 | 
						total_errors = 0;
 | 
				
			||||||
	list_for_each_entry(dev, head, dev_list) {
 | 
						list_for_each_entry_rcu(dev, head, dev_list) {
 | 
				
			||||||
		if (!dev->bdev)
 | 
							if (!dev->bdev)
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		if (!dev->in_fs_metadata || !dev->writeable)
 | 
							if (!dev->in_fs_metadata || !dev->writeable)
 | 
				
			||||||
| 
						 | 
					@ -2377,7 +2377,7 @@ int write_all_supers(struct btrfs_root *root, int max_mirrors)
 | 
				
			||||||
		if (ret)
 | 
							if (ret)
 | 
				
			||||||
			total_errors++;
 | 
								total_errors++;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
 | 
						rcu_read_unlock();
 | 
				
			||||||
	if (total_errors > max_errors) {
 | 
						if (total_errors > max_errors) {
 | 
				
			||||||
		printk(KERN_ERR "btrfs: %d errors while writing supers\n",
 | 
							printk(KERN_ERR "btrfs: %d errors while writing supers\n",
 | 
				
			||||||
		       total_errors);
 | 
							       total_errors);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -281,8 +281,9 @@ static noinline int btrfs_ioctl_fitrim(struct file *file, void __user *arg)
 | 
				
			||||||
	if (!capable(CAP_SYS_ADMIN))
 | 
						if (!capable(CAP_SYS_ADMIN))
 | 
				
			||||||
		return -EPERM;
 | 
							return -EPERM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mutex_lock(&fs_info->fs_devices->device_list_mutex);
 | 
						rcu_read_lock();
 | 
				
			||||||
	list_for_each_entry(device, &fs_info->fs_devices->devices, dev_list) {
 | 
						list_for_each_entry_rcu(device, &fs_info->fs_devices->devices,
 | 
				
			||||||
 | 
									dev_list) {
 | 
				
			||||||
		if (!device->bdev)
 | 
							if (!device->bdev)
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		q = bdev_get_queue(device->bdev);
 | 
							q = bdev_get_queue(device->bdev);
 | 
				
			||||||
| 
						 | 
					@ -292,7 +293,7 @@ static noinline int btrfs_ioctl_fitrim(struct file *file, void __user *arg)
 | 
				
			||||||
				     minlen);
 | 
									     minlen);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	mutex_unlock(&fs_info->fs_devices->device_list_mutex);
 | 
						rcu_read_unlock();
 | 
				
			||||||
	if (!num_devices)
 | 
						if (!num_devices)
 | 
				
			||||||
		return -EOPNOTSUPP;
 | 
							return -EOPNOTSUPP;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -363,7 +363,7 @@ static noinline int device_list_add(const char *path,
 | 
				
			||||||
		INIT_LIST_HEAD(&device->dev_alloc_list);
 | 
							INIT_LIST_HEAD(&device->dev_alloc_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		mutex_lock(&fs_devices->device_list_mutex);
 | 
							mutex_lock(&fs_devices->device_list_mutex);
 | 
				
			||||||
		list_add(&device->dev_list, &fs_devices->devices);
 | 
							list_add_rcu(&device->dev_list, &fs_devices->devices);
 | 
				
			||||||
		mutex_unlock(&fs_devices->device_list_mutex);
 | 
							mutex_unlock(&fs_devices->device_list_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		device->fs_devices = fs_devices;
 | 
							device->fs_devices = fs_devices;
 | 
				
			||||||
| 
						 | 
					@ -471,6 +471,29 @@ int btrfs_close_extra_devices(struct btrfs_fs_devices *fs_devices)
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void __free_device(struct work_struct *work)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct btrfs_device *device;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						device = container_of(work, struct btrfs_device, rcu_work);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (device->bdev)
 | 
				
			||||||
 | 
							blkdev_put(device->bdev, device->mode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kfree(device->name);
 | 
				
			||||||
 | 
						kfree(device);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void free_device(struct rcu_head *head)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct btrfs_device *device;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						device = container_of(head, struct btrfs_device, rcu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						INIT_WORK(&device->rcu_work, __free_device);
 | 
				
			||||||
 | 
						schedule_work(&device->rcu_work);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int __btrfs_close_devices(struct btrfs_fs_devices *fs_devices)
 | 
					static int __btrfs_close_devices(struct btrfs_fs_devices *fs_devices)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct btrfs_device *device;
 | 
						struct btrfs_device *device;
 | 
				
			||||||
| 
						 | 
					@ -480,18 +503,27 @@ static int __btrfs_close_devices(struct btrfs_fs_devices *fs_devices)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mutex_lock(&fs_devices->device_list_mutex);
 | 
						mutex_lock(&fs_devices->device_list_mutex);
 | 
				
			||||||
	list_for_each_entry(device, &fs_devices->devices, dev_list) {
 | 
						list_for_each_entry(device, &fs_devices->devices, dev_list) {
 | 
				
			||||||
		if (device->bdev) {
 | 
							struct btrfs_device *new_device;
 | 
				
			||||||
			blkdev_put(device->bdev, device->mode);
 | 
					
 | 
				
			||||||
 | 
							if (device->bdev)
 | 
				
			||||||
			fs_devices->open_devices--;
 | 
								fs_devices->open_devices--;
 | 
				
			||||||
		}
 | 
					
 | 
				
			||||||
		if (device->writeable) {
 | 
							if (device->writeable) {
 | 
				
			||||||
			list_del_init(&device->dev_alloc_list);
 | 
								list_del_init(&device->dev_alloc_list);
 | 
				
			||||||
			fs_devices->rw_devices--;
 | 
								fs_devices->rw_devices--;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		device->bdev = NULL;
 | 
							new_device = kmalloc(sizeof(*new_device), GFP_NOFS);
 | 
				
			||||||
		device->writeable = 0;
 | 
							BUG_ON(!new_device);
 | 
				
			||||||
		device->in_fs_metadata = 0;
 | 
							memcpy(new_device, device, sizeof(*new_device));
 | 
				
			||||||
 | 
							new_device->name = kstrdup(device->name, GFP_NOFS);
 | 
				
			||||||
 | 
							BUG_ON(!new_device->name);
 | 
				
			||||||
 | 
							new_device->bdev = NULL;
 | 
				
			||||||
 | 
							new_device->writeable = 0;
 | 
				
			||||||
 | 
							new_device->in_fs_metadata = 0;
 | 
				
			||||||
 | 
							list_replace_rcu(&device->dev_list, &new_device->dev_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							call_rcu(&device->rcu, free_device);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	mutex_unlock(&fs_devices->device_list_mutex);
 | 
						mutex_unlock(&fs_devices->device_list_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1204,11 +1236,13 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)
 | 
				
			||||||
	struct block_device *bdev;
 | 
						struct block_device *bdev;
 | 
				
			||||||
	struct buffer_head *bh = NULL;
 | 
						struct buffer_head *bh = NULL;
 | 
				
			||||||
	struct btrfs_super_block *disk_super;
 | 
						struct btrfs_super_block *disk_super;
 | 
				
			||||||
 | 
						struct btrfs_fs_devices *cur_devices;
 | 
				
			||||||
	u64 all_avail;
 | 
						u64 all_avail;
 | 
				
			||||||
	u64 devid;
 | 
						u64 devid;
 | 
				
			||||||
	u64 num_devices;
 | 
						u64 num_devices;
 | 
				
			||||||
	u8 *dev_uuid;
 | 
						u8 *dev_uuid;
 | 
				
			||||||
	int ret = 0;
 | 
						int ret = 0;
 | 
				
			||||||
 | 
						bool clear_super = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mutex_lock(&uuid_mutex);
 | 
						mutex_lock(&uuid_mutex);
 | 
				
			||||||
	mutex_lock(&root->fs_info->volume_mutex);
 | 
						mutex_lock(&root->fs_info->volume_mutex);
 | 
				
			||||||
| 
						 | 
					@ -1294,6 +1328,7 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)
 | 
				
			||||||
		list_del_init(&device->dev_alloc_list);
 | 
							list_del_init(&device->dev_alloc_list);
 | 
				
			||||||
		unlock_chunks(root);
 | 
							unlock_chunks(root);
 | 
				
			||||||
		root->fs_info->fs_devices->rw_devices--;
 | 
							root->fs_info->fs_devices->rw_devices--;
 | 
				
			||||||
 | 
							clear_super = true;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret = btrfs_shrink_device(device, 0);
 | 
						ret = btrfs_shrink_device(device, 0);
 | 
				
			||||||
| 
						 | 
					@ -1304,16 +1339,15 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)
 | 
				
			||||||
	if (ret)
 | 
						if (ret)
 | 
				
			||||||
		goto error_undo;
 | 
							goto error_undo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	device->in_fs_metadata = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
	 * the device list mutex makes sure that we don't change
 | 
						 * the device list mutex makes sure that we don't change
 | 
				
			||||||
	 * the device list while someone else is writing out all
 | 
						 * the device list while someone else is writing out all
 | 
				
			||||||
	 * the device supers.
 | 
						 * the device supers.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cur_devices = device->fs_devices;
 | 
				
			||||||
	mutex_lock(&root->fs_info->fs_devices->device_list_mutex);
 | 
						mutex_lock(&root->fs_info->fs_devices->device_list_mutex);
 | 
				
			||||||
	list_del_init(&device->dev_list);
 | 
						list_del_rcu(&device->dev_list);
 | 
				
			||||||
	mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	device->fs_devices->num_devices--;
 | 
						device->fs_devices->num_devices--;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1327,36 +1361,36 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)
 | 
				
			||||||
	if (device->bdev == root->fs_info->fs_devices->latest_bdev)
 | 
						if (device->bdev == root->fs_info->fs_devices->latest_bdev)
 | 
				
			||||||
		root->fs_info->fs_devices->latest_bdev = next_device->bdev;
 | 
							root->fs_info->fs_devices->latest_bdev = next_device->bdev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (device->bdev) {
 | 
						if (device->bdev)
 | 
				
			||||||
		blkdev_put(device->bdev, device->mode);
 | 
					 | 
				
			||||||
		device->bdev = NULL;
 | 
					 | 
				
			||||||
		device->fs_devices->open_devices--;
 | 
							device->fs_devices->open_devices--;
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
 | 
						call_rcu(&device->rcu, free_device);
 | 
				
			||||||
 | 
						mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	num_devices = btrfs_super_num_devices(&root->fs_info->super_copy) - 1;
 | 
						num_devices = btrfs_super_num_devices(&root->fs_info->super_copy) - 1;
 | 
				
			||||||
	btrfs_set_super_num_devices(&root->fs_info->super_copy, num_devices);
 | 
						btrfs_set_super_num_devices(&root->fs_info->super_copy, num_devices);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (device->fs_devices->open_devices == 0) {
 | 
						if (cur_devices->open_devices == 0) {
 | 
				
			||||||
		struct btrfs_fs_devices *fs_devices;
 | 
							struct btrfs_fs_devices *fs_devices;
 | 
				
			||||||
		fs_devices = root->fs_info->fs_devices;
 | 
							fs_devices = root->fs_info->fs_devices;
 | 
				
			||||||
		while (fs_devices) {
 | 
							while (fs_devices) {
 | 
				
			||||||
			if (fs_devices->seed == device->fs_devices)
 | 
								if (fs_devices->seed == cur_devices)
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			fs_devices = fs_devices->seed;
 | 
								fs_devices = fs_devices->seed;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		fs_devices->seed = device->fs_devices->seed;
 | 
							fs_devices->seed = cur_devices->seed;
 | 
				
			||||||
		device->fs_devices->seed = NULL;
 | 
							cur_devices->seed = NULL;
 | 
				
			||||||
		lock_chunks(root);
 | 
							lock_chunks(root);
 | 
				
			||||||
		__btrfs_close_devices(device->fs_devices);
 | 
							__btrfs_close_devices(cur_devices);
 | 
				
			||||||
		unlock_chunks(root);
 | 
							unlock_chunks(root);
 | 
				
			||||||
		free_fs_devices(device->fs_devices);
 | 
							free_fs_devices(cur_devices);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
	 * at this point, the device is zero sized.  We want to
 | 
						 * at this point, the device is zero sized.  We want to
 | 
				
			||||||
	 * remove it from the devices list and zero out the old super
 | 
						 * remove it from the devices list and zero out the old super
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	if (device->writeable) {
 | 
						if (clear_super) {
 | 
				
			||||||
		/* make sure this device isn't detected as part of
 | 
							/* make sure this device isn't detected as part of
 | 
				
			||||||
		 * the FS anymore
 | 
							 * the FS anymore
 | 
				
			||||||
		 */
 | 
							 */
 | 
				
			||||||
| 
						 | 
					@ -1365,8 +1399,6 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)
 | 
				
			||||||
		sync_dirty_buffer(bh);
 | 
							sync_dirty_buffer(bh);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	kfree(device->name);
 | 
					 | 
				
			||||||
	kfree(device);
 | 
					 | 
				
			||||||
	ret = 0;
 | 
						ret = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
error_brelse:
 | 
					error_brelse:
 | 
				
			||||||
| 
						 | 
					@ -1425,7 +1457,8 @@ static int btrfs_prepare_sprout(struct btrfs_trans_handle *trans,
 | 
				
			||||||
	mutex_init(&seed_devices->device_list_mutex);
 | 
						mutex_init(&seed_devices->device_list_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mutex_lock(&root->fs_info->fs_devices->device_list_mutex);
 | 
						mutex_lock(&root->fs_info->fs_devices->device_list_mutex);
 | 
				
			||||||
	list_splice_init(&fs_devices->devices, &seed_devices->devices);
 | 
						list_splice_init_rcu(&fs_devices->devices, &seed_devices->devices,
 | 
				
			||||||
 | 
								      synchronize_rcu);
 | 
				
			||||||
	mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
 | 
						mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	list_splice_init(&fs_devices->alloc_list, &seed_devices->alloc_list);
 | 
						list_splice_init(&fs_devices->alloc_list, &seed_devices->alloc_list);
 | 
				
			||||||
| 
						 | 
					@ -1624,7 +1657,7 @@ int btrfs_init_new_device(struct btrfs_root *root, char *device_path)
 | 
				
			||||||
	 * half setup
 | 
						 * half setup
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	mutex_lock(&root->fs_info->fs_devices->device_list_mutex);
 | 
						mutex_lock(&root->fs_info->fs_devices->device_list_mutex);
 | 
				
			||||||
	list_add(&device->dev_list, &root->fs_info->fs_devices->devices);
 | 
						list_add_rcu(&device->dev_list, &root->fs_info->fs_devices->devices);
 | 
				
			||||||
	list_add(&device->dev_alloc_list,
 | 
						list_add(&device->dev_alloc_list,
 | 
				
			||||||
		 &root->fs_info->fs_devices->alloc_list);
 | 
							 &root->fs_info->fs_devices->alloc_list);
 | 
				
			||||||
	root->fs_info->fs_devices->num_devices++;
 | 
						root->fs_info->fs_devices->num_devices++;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -86,6 +86,8 @@ struct btrfs_device {
 | 
				
			||||||
	u8 uuid[BTRFS_UUID_SIZE];
 | 
						u8 uuid[BTRFS_UUID_SIZE];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct btrfs_work work;
 | 
						struct btrfs_work work;
 | 
				
			||||||
 | 
						struct rcu_head rcu;
 | 
				
			||||||
 | 
						struct work_struct rcu_work;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct btrfs_fs_devices {
 | 
					struct btrfs_fs_devices {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue