diff options
author | Anssi Hannula <anssi.hannula@iki.fi> | 2014-08-01 11:55:47 -0400 |
---|---|---|
committer | Mike Snitzer <snitzer@redhat.com> | 2014-08-01 12:25:22 -0400 |
commit | 44fa816bb778edbab6b6ddaaf24908dd6295937e (patch) | |
tree | 2b67384ea2670780a7215c593b08e2ec92bd27e6 | |
parent | d8c712ea471ce7a4fd1734ad2211adf8469ddddc (diff) |
dm cache: fix race affecting dirty block count
nr_dirty is updated without locking, causing it to drift so that it is
non-zero (either a small positive integer, or a very large one when an
underflow occurs) even when there are no actual dirty blocks. This was
due to a race between the workqueue and map function accessing nr_dirty
in parallel without proper protection.
People were seeing under runs due to a race on increment/decrement of
nr_dirty, see: https://lkml.org/lkml/2014/6/3/648
Fix this by using an atomic_t for nr_dirty.
Reported-by: roma1390@gmail.com
Signed-off-by: Anssi Hannula <anssi.hannula@iki.fi>
Signed-off-by: Joe Thornber <ejt@redhat.com>
Signed-off-by: Mike Snitzer <snitzer@redhat.com>
Cc: stable@vger.kernel.org
-rw-r--r-- | drivers/md/dm-cache-target.c | 13 |
1 files changed, 6 insertions, 7 deletions
diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c index 5f054c44b485..2c63326638b6 100644 --- a/drivers/md/dm-cache-target.c +++ b/drivers/md/dm-cache-target.c | |||
@@ -231,7 +231,7 @@ struct cache { | |||
231 | /* | 231 | /* |
232 | * cache_size entries, dirty if set | 232 | * cache_size entries, dirty if set |
233 | */ | 233 | */ |
234 | dm_cblock_t nr_dirty; | 234 | atomic_t nr_dirty; |
235 | unsigned long *dirty_bitset; | 235 | unsigned long *dirty_bitset; |
236 | 236 | ||
237 | /* | 237 | /* |
@@ -492,7 +492,7 @@ static bool is_dirty(struct cache *cache, dm_cblock_t b) | |||
492 | static void set_dirty(struct cache *cache, dm_oblock_t oblock, dm_cblock_t cblock) | 492 | static void set_dirty(struct cache *cache, dm_oblock_t oblock, dm_cblock_t cblock) |
493 | { | 493 | { |
494 | if (!test_and_set_bit(from_cblock(cblock), cache->dirty_bitset)) { | 494 | if (!test_and_set_bit(from_cblock(cblock), cache->dirty_bitset)) { |
495 | cache->nr_dirty = to_cblock(from_cblock(cache->nr_dirty) + 1); | 495 | atomic_inc(&cache->nr_dirty); |
496 | policy_set_dirty(cache->policy, oblock); | 496 | policy_set_dirty(cache->policy, oblock); |
497 | } | 497 | } |
498 | } | 498 | } |
@@ -501,8 +501,7 @@ static void clear_dirty(struct cache *cache, dm_oblock_t oblock, dm_cblock_t cbl | |||
501 | { | 501 | { |
502 | if (test_and_clear_bit(from_cblock(cblock), cache->dirty_bitset)) { | 502 | if (test_and_clear_bit(from_cblock(cblock), cache->dirty_bitset)) { |
503 | policy_clear_dirty(cache->policy, oblock); | 503 | policy_clear_dirty(cache->policy, oblock); |
504 | cache->nr_dirty = to_cblock(from_cblock(cache->nr_dirty) - 1); | 504 | if (atomic_dec_return(&cache->nr_dirty) == 0) |
505 | if (!from_cblock(cache->nr_dirty)) | ||
506 | dm_table_event(cache->ti->table); | 505 | dm_table_event(cache->ti->table); |
507 | } | 506 | } |
508 | } | 507 | } |
@@ -2269,7 +2268,7 @@ static int cache_create(struct cache_args *ca, struct cache **result) | |||
2269 | atomic_set(&cache->quiescing_ack, 0); | 2268 | atomic_set(&cache->quiescing_ack, 0); |
2270 | 2269 | ||
2271 | r = -ENOMEM; | 2270 | r = -ENOMEM; |
2272 | cache->nr_dirty = 0; | 2271 | atomic_set(&cache->nr_dirty, 0); |
2273 | cache->dirty_bitset = alloc_bitset(from_cblock(cache->cache_size)); | 2272 | cache->dirty_bitset = alloc_bitset(from_cblock(cache->cache_size)); |
2274 | if (!cache->dirty_bitset) { | 2273 | if (!cache->dirty_bitset) { |
2275 | *error = "could not allocate dirty bitset"; | 2274 | *error = "could not allocate dirty bitset"; |
@@ -2808,7 +2807,7 @@ static void cache_status(struct dm_target *ti, status_type_t type, | |||
2808 | 2807 | ||
2809 | residency = policy_residency(cache->policy); | 2808 | residency = policy_residency(cache->policy); |
2810 | 2809 | ||
2811 | DMEMIT("%u %llu/%llu %u %llu/%llu %u %u %u %u %u %u %llu ", | 2810 | DMEMIT("%u %llu/%llu %u %llu/%llu %u %u %u %u %u %u %lu ", |
2812 | (unsigned)(DM_CACHE_METADATA_BLOCK_SIZE >> SECTOR_SHIFT), | 2811 | (unsigned)(DM_CACHE_METADATA_BLOCK_SIZE >> SECTOR_SHIFT), |
2813 | (unsigned long long)(nr_blocks_metadata - nr_free_blocks_metadata), | 2812 | (unsigned long long)(nr_blocks_metadata - nr_free_blocks_metadata), |
2814 | (unsigned long long)nr_blocks_metadata, | 2813 | (unsigned long long)nr_blocks_metadata, |
@@ -2821,7 +2820,7 @@ static void cache_status(struct dm_target *ti, status_type_t type, | |||
2821 | (unsigned) atomic_read(&cache->stats.write_miss), | 2820 | (unsigned) atomic_read(&cache->stats.write_miss), |
2822 | (unsigned) atomic_read(&cache->stats.demotion), | 2821 | (unsigned) atomic_read(&cache->stats.demotion), |
2823 | (unsigned) atomic_read(&cache->stats.promotion), | 2822 | (unsigned) atomic_read(&cache->stats.promotion), |
2824 | (unsigned long long) from_cblock(cache->nr_dirty)); | 2823 | (unsigned long) atomic_read(&cache->nr_dirty)); |
2825 | 2824 | ||
2826 | if (writethrough_mode(&cache->features)) | 2825 | if (writethrough_mode(&cache->features)) |
2827 | DMEMIT("1 writethrough "); | 2826 | DMEMIT("1 writethrough "); |