Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1f78160c authored by Xiao Guangrong's avatar Xiao Guangrong Committed by Chris Mason
Browse files

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: default avatarXiao Guangrong <xiaoguangrong@cn.fujitsu.com>
Signed-off-by: default avatarChris Mason <chris.mason@oracle.com>
parent 46224705
Loading
Loading
Loading
Loading
+7 −7
Original line number Diff line number Diff line
@@ -1410,8 +1410,8 @@ static int btrfs_congested_fn(void *congested_data, int bdi_bits)
	struct btrfs_device *device;
	struct backing_dev_info *bdi;

	mutex_lock(&info->fs_devices->device_list_mutex);
	list_for_each_entry(device, &info->fs_devices->devices, dev_list) {
	rcu_read_lock();
	list_for_each_entry_rcu(device, &info->fs_devices->devices, dev_list) {
		if (!device->bdev)
			continue;
		bdi = blk_get_backing_dev_info(device->bdev);
@@ -1420,7 +1420,7 @@ static int btrfs_congested_fn(void *congested_data, int bdi_bits)
			break;
		}
	}
	mutex_unlock(&info->fs_devices->device_list_mutex);
	rcu_read_unlock();
	return ret;
}

@@ -2332,9 +2332,9 @@ int write_all_supers(struct btrfs_root *root, int max_mirrors)
	sb = &root->fs_info->super_for_commit;
	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;
	list_for_each_entry(dev, head, dev_list) {
	list_for_each_entry_rcu(dev, head, dev_list) {
		if (!dev->bdev) {
			total_errors++;
			continue;
@@ -2367,7 +2367,7 @@ int write_all_supers(struct btrfs_root *root, int max_mirrors)
	}

	total_errors = 0;
	list_for_each_entry(dev, head, dev_list) {
	list_for_each_entry_rcu(dev, head, dev_list) {
		if (!dev->bdev)
			continue;
		if (!dev->in_fs_metadata || !dev->writeable)
@@ -2377,7 +2377,7 @@ int write_all_supers(struct btrfs_root *root, int max_mirrors)
		if (ret)
			total_errors++;
	}
	mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
	rcu_read_unlock();
	if (total_errors > max_errors) {
		printk(KERN_ERR "btrfs: %d errors while writing supers\n",
		       total_errors);
+4 −3
Original line number Diff line number Diff line
@@ -281,8 +281,9 @@ static noinline int btrfs_ioctl_fitrim(struct file *file, void __user *arg)
	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	mutex_lock(&fs_info->fs_devices->device_list_mutex);
	list_for_each_entry(device, &fs_info->fs_devices->devices, dev_list) {
	rcu_read_lock();
	list_for_each_entry_rcu(device, &fs_info->fs_devices->devices,
				dev_list) {
		if (!device->bdev)
			continue;
		q = bdev_get_queue(device->bdev);
@@ -292,7 +293,7 @@ static noinline int btrfs_ioctl_fitrim(struct file *file, void __user *arg)
				     minlen);
		}
	}
	mutex_unlock(&fs_info->fs_devices->device_list_mutex);
	rcu_read_unlock();
	if (!num_devices)
		return -EOPNOTSUPP;

+59 −26
Original line number Diff line number Diff line
@@ -363,7 +363,7 @@ static noinline int device_list_add(const char *path,
		INIT_LIST_HEAD(&device->dev_alloc_list);

		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);

		device->fs_devices = fs_devices;
@@ -471,6 +471,29 @@ again:
	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)
{
	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);
	list_for_each_entry(device, &fs_devices->devices, dev_list) {
		if (device->bdev) {
			blkdev_put(device->bdev, device->mode);
		struct btrfs_device *new_device;

		if (device->bdev)
			fs_devices->open_devices--;
		}

		if (device->writeable) {
			list_del_init(&device->dev_alloc_list);
			fs_devices->rw_devices--;
		}

		device->bdev = NULL;
		device->writeable = 0;
		device->in_fs_metadata = 0;
		new_device = kmalloc(sizeof(*new_device), GFP_NOFS);
		BUG_ON(!new_device);
		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);

@@ -1204,11 +1236,13 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)
	struct block_device *bdev;
	struct buffer_head *bh = NULL;
	struct btrfs_super_block *disk_super;
	struct btrfs_fs_devices *cur_devices;
	u64 all_avail;
	u64 devid;
	u64 num_devices;
	u8 *dev_uuid;
	int ret = 0;
	bool clear_super = false;

	mutex_lock(&uuid_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);
		unlock_chunks(root);
		root->fs_info->fs_devices->rw_devices--;
		clear_super = true;
	}

	ret = btrfs_shrink_device(device, 0);
@@ -1304,16 +1339,15 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)
	if (ret)
		goto error_undo;

	device->in_fs_metadata = 0;

	/*
	 * the device list mutex makes sure that we don't change
	 * the device list while someone else is writing out all
	 * the device supers.
	 */

	cur_devices = device->fs_devices;
	mutex_lock(&root->fs_info->fs_devices->device_list_mutex);
	list_del_init(&device->dev_list);
	mutex_unlock(&root->fs_info->fs_devices->device_list_mutex);
	list_del_rcu(&device->dev_list);

	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)
		root->fs_info->fs_devices->latest_bdev = next_device->bdev;

	if (device->bdev) {
		blkdev_put(device->bdev, device->mode);
		device->bdev = NULL;
	if (device->bdev)
		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;
	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;
		fs_devices = root->fs_info->fs_devices;
		while (fs_devices) {
			if (fs_devices->seed == device->fs_devices)
			if (fs_devices->seed == cur_devices)
				break;
			fs_devices = fs_devices->seed;
		}
		fs_devices->seed = device->fs_devices->seed;
		device->fs_devices->seed = NULL;
		fs_devices->seed = cur_devices->seed;
		cur_devices->seed = NULL;
		lock_chunks(root);
		__btrfs_close_devices(device->fs_devices);
		__btrfs_close_devices(cur_devices);
		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
	 * 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
		 * the FS anymore
		 */
@@ -1365,8 +1399,6 @@ int btrfs_rm_device(struct btrfs_root *root, char *device_path)
		sync_dirty_buffer(bh);
	}

	kfree(device->name);
	kfree(device);
	ret = 0;

error_brelse:
@@ -1425,7 +1457,8 @@ static int btrfs_prepare_sprout(struct btrfs_trans_handle *trans,
	mutex_init(&seed_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);

	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
	 */
	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,
		 &root->fs_info->fs_devices->alloc_list);
	root->fs_info->fs_devices->num_devices++;
+2 −0
Original line number Diff line number Diff line
@@ -86,6 +86,8 @@ struct btrfs_device {
	u8 uuid[BTRFS_UUID_SIZE];

	struct btrfs_work work;
	struct rcu_head rcu;
	struct work_struct rcu_work;
};

struct btrfs_fs_devices {