md linear: fix a race between linear_add() and linear_congested()
am: 5a1f03f1ee
Change-Id: I3fd7c0c0cbf911b61d8aca203a75e13e698d7840
This commit is contained in:
commit
3ff14c70fe
2 changed files with 35 additions and 5 deletions
|
@ -52,18 +52,26 @@ static inline struct dev_info *which_dev(struct mddev *mddev, sector_t sector)
|
||||||
return conf->disks + lo;
|
return conf->disks + lo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In linear_congested() conf->raid_disks is used as a copy of
|
||||||
|
* mddev->raid_disks to iterate conf->disks[], because conf->raid_disks
|
||||||
|
* and conf->disks[] are created in linear_conf(), they are always
|
||||||
|
* consitent with each other, but mddev->raid_disks does not.
|
||||||
|
*/
|
||||||
static int linear_congested(struct mddev *mddev, int bits)
|
static int linear_congested(struct mddev *mddev, int bits)
|
||||||
{
|
{
|
||||||
struct linear_conf *conf;
|
struct linear_conf *conf;
|
||||||
int i, ret = 0;
|
int i, ret = 0;
|
||||||
|
|
||||||
conf = mddev->private;
|
rcu_read_lock();
|
||||||
|
conf = rcu_dereference(mddev->private);
|
||||||
|
|
||||||
for (i = 0; i < mddev->raid_disks && !ret ; i++) {
|
for (i = 0; i < conf->raid_disks && !ret ; i++) {
|
||||||
struct request_queue *q = bdev_get_queue(conf->disks[i].rdev->bdev);
|
struct request_queue *q = bdev_get_queue(conf->disks[i].rdev->bdev);
|
||||||
ret |= bdi_congested(&q->backing_dev_info, bits);
|
ret |= bdi_congested(&q->backing_dev_info, bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rcu_read_unlock();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,6 +151,19 @@ static struct linear_conf *linear_conf(struct mddev *mddev, int raid_disks)
|
||||||
conf->disks[i-1].end_sector +
|
conf->disks[i-1].end_sector +
|
||||||
conf->disks[i].rdev->sectors;
|
conf->disks[i].rdev->sectors;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* conf->raid_disks is copy of mddev->raid_disks. The reason to
|
||||||
|
* keep a copy of mddev->raid_disks in struct linear_conf is,
|
||||||
|
* mddev->raid_disks may not be consistent with pointers number of
|
||||||
|
* conf->disks[] when it is updated in linear_add() and used to
|
||||||
|
* iterate old conf->disks[] earray in linear_congested().
|
||||||
|
* Here conf->raid_disks is always consitent with number of
|
||||||
|
* pointers in conf->disks[] array, and mddev->private is updated
|
||||||
|
* with rcu_assign_pointer() in linear_addr(), such race can be
|
||||||
|
* avoided.
|
||||||
|
*/
|
||||||
|
conf->raid_disks = raid_disks;
|
||||||
|
|
||||||
return conf;
|
return conf;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
|
@ -195,15 +216,23 @@ static int linear_add(struct mddev *mddev, struct md_rdev *rdev)
|
||||||
if (!newconf)
|
if (!newconf)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* newconf->raid_disks already keeps a copy of * the increased
|
||||||
|
* value of mddev->raid_disks, WARN_ONCE() is just used to make
|
||||||
|
* sure of this. It is possible that oldconf is still referenced
|
||||||
|
* in linear_congested(), therefore kfree_rcu() is used to free
|
||||||
|
* oldconf until no one uses it anymore.
|
||||||
|
*/
|
||||||
mddev_suspend(mddev);
|
mddev_suspend(mddev);
|
||||||
oldconf = mddev->private;
|
oldconf = rcu_dereference(mddev->private);
|
||||||
mddev->raid_disks++;
|
mddev->raid_disks++;
|
||||||
mddev->private = newconf;
|
WARN_ONCE(mddev->raid_disks != newconf->raid_disks,
|
||||||
|
"copied raid_disks doesn't match mddev->raid_disks");
|
||||||
|
rcu_assign_pointer(mddev->private, newconf);
|
||||||
md_set_array_sectors(mddev, linear_size(mddev, 0, 0));
|
md_set_array_sectors(mddev, linear_size(mddev, 0, 0));
|
||||||
set_capacity(mddev->gendisk, mddev->array_sectors);
|
set_capacity(mddev->gendisk, mddev->array_sectors);
|
||||||
mddev_resume(mddev);
|
mddev_resume(mddev);
|
||||||
revalidate_disk(mddev->gendisk);
|
revalidate_disk(mddev->gendisk);
|
||||||
kfree(oldconf);
|
kfree_rcu(oldconf, rcu);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ struct linear_conf
|
||||||
{
|
{
|
||||||
struct rcu_head rcu;
|
struct rcu_head rcu;
|
||||||
sector_t array_sectors;
|
sector_t array_sectors;
|
||||||
|
int raid_disks; /* a copy of mddev->raid_disks */
|
||||||
struct dev_info disks[0];
|
struct dev_info disks[0];
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Reference in a new issue