[dm-devel] [PATCH 10/10] dm snapshot: Add snapshot merging code

Mark McLoughlin markmc at redhat.com
Tue Apr 1 07:32:57 UTC 2008


Add the code to actually perform the incremental merging of chunks
from the COW device to the origin device.

Chunks are merged one at a time by ksnapd. While a merge is in
progress the merging mutex is held, allowing us to disable merging
while the target is supended.

We take care not to initiate a copy from the COW device while there
are writes pending on the chunk in question. Likewise we take care to
delay any writes to a chunk while the chunk is being merged.

The merging_work pointer added to the pending_exception structure
allows us to indicate that ksnapd should be woken up when the I/O on
that chunk has completed or when all other associated snapshots have
finished copying the chunk to their COW device. This pointer is
protected by the pe_lock spinlock.

The old_chunk, new_chunk and delayed_bios members of the dm_merged
structure are all protected by the read/write mutex in the dm_snapshot
structure.

Signed-off-by: Mark McLoughlin <markmc at redhat.com>
---
 drivers/md/dm-snap.c |  233 +++++++++++++++++++++++++++++++++++++++++++++++---
 drivers/md/dm-snap.h |   16 ++++
 2 files changed, 235 insertions(+), 14 deletions(-)

diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c
index bc13f35..b2b9762 100644
--- a/drivers/md/dm-snap.c
+++ b/drivers/md/dm-snap.c
@@ -76,6 +76,14 @@ struct dm_snap_pending_exception {
 	 */
 	atomic_t ref_count;
 
+	/*
+	 * If set, merging of a chunk is blocked until all snapshots
+	 * have finished copying the chunk from the origin device
+	 * and until all writes to the chunk on the COW device have
+	 * completed.
+	 */
+	struct work_struct *merging_work;
+
 	/* Pointer back to snapshot context */
 	struct dm_snapshot *snap;
 
@@ -752,6 +760,8 @@ static struct bio *put_pending_exception(struct dm_snap_pending_exception *pe)
 	 */
 	if ((!primary_pe || primary_pe != pe) &&
 	    atomic_dec_and_test(&pe->ref_count)) {
+		if (pe->merging_work)
+			queue_work(ksnapd, pe->merging_work);
 		remove_exception(&pe->e);
 		free_pending_exception(pe);
 	}
@@ -760,6 +770,8 @@ static struct bio *put_pending_exception(struct dm_snap_pending_exception *pe)
 	 * Free the primary pe if nothing references it.
 	 */
 	if (primary_pe && !atomic_read(&primary_pe->ref_count)) {
+		if (primary_pe->merging_work)
+			queue_work(ksnapd, primary_pe->merging_work);
 		remove_exception(&primary_pe->e);
 		free_pending_exception(primary_pe);
 	}
@@ -927,6 +939,7 @@ __find_pending_exception(struct dm_snapshot *s, chunk_t chunk, int prepare)
 	bio_list_init(&pe->snapshot_bios);
 	pe->primary_pe = NULL;
 	atomic_set(&pe->ref_count, 0);
+	pe->merging_work = NULL;
 	pe->snap = s;
 	pe->started = 0;
 
@@ -1104,7 +1117,7 @@ static int snapshot_status(struct dm_target *ti, status_type_t type,
  * Origin methods
  *---------------------------------------------------------------*/
 static int __origin_write(struct list_head *snapshots, sector_t sector,
-			  struct bio *bio)
+			  struct bio *bio, struct work_struct *merging_work)
 {
 	int r = DM_MAPIO_REMAPPED;
 	struct dm_snapshot *snap;
@@ -1169,6 +1182,8 @@ static int __origin_write(struct list_head *snapshots, sector_t sector,
 
 			if (bio)
 				bio_list_add(&primary_pe->origin_bios, bio);
+			if (merging_work)
+				primary_pe->merging_work = merging_work;
 
 			r = DM_MAPIO_SUBMITTED;
 		}
@@ -1215,7 +1230,7 @@ static int __origin_write(struct list_head *snapshots, sector_t sector,
  * Called on a write from the origin driver.
  */
 static int do_origin(struct dm_dev *origin, sector_t sector,
-		     struct bio *bio)
+		     struct bio *bio, struct work_struct *merging_work)
 {
 	struct origin *o;
 	int r = DM_MAPIO_REMAPPED;
@@ -1223,7 +1238,7 @@ static int do_origin(struct dm_dev *origin, sector_t sector,
 	down_read(&_origins_lock);
 	o = __lookup_origin(origin->bdev);
 	if (o)
-		r = __origin_write(&o->snapshots, sector, bio);
+		r = __origin_write(&o->snapshots, sector, bio, merging_work);
 	up_read(&_origins_lock);
 
 	return r;
@@ -1276,7 +1291,7 @@ static int origin_map(struct dm_target *ti, struct bio *bio,
 	if (bio_rw(bio) != WRITE)
 		return DM_MAPIO_REMAPPED;
 
-	return do_origin(dev, bio->bi_sector, bio);
+	return do_origin(dev, bio->bi_sector, bio, NULL);
 }
 
 #define min_not_zero(l, r) (l == 0) ? r : ((r == 0) ? l : min(l, r))
@@ -1326,6 +1341,167 @@ static int origin_status(struct dm_target *ti, status_type_t type, char *result,
 	return 0;
 }
 
+#define MERGE_COMPLETE_BIT 0
+#define MERGE_ERROR_BIT    1
+
+static inline void set_merge_complete(struct dm_merged *merged)
+{
+	set_bit(MERGE_COMPLETE_BIT, &merged->status);
+}
+
+static inline void set_merge_error(struct dm_merged *merged)
+{
+	set_bit(MERGE_ERROR_BIT, &merged->status);
+}
+
+static inline int get_merge_complete(struct dm_merged *merged)
+{
+	return test_and_clear_bit(MERGE_COMPLETE_BIT, &merged->status);
+}
+
+static inline int get_merge_error(struct dm_merged *merged)
+{
+	return test_and_clear_bit(MERGE_ERROR_BIT, &merged->status);
+}
+
+static void merge_callback(int read_err, unsigned int write_err, void *context)
+{
+	struct dm_merged *merged = (struct dm_merged *) context;
+
+	if (read_err || write_err)
+		set_merge_error(merged);
+
+	set_merge_complete(merged);
+
+	queue_work(ksnapd, &merged->merging_work);
+}
+
+static int start_merge(struct dm_merged *merged)
+{
+	struct dm_snapshot *s = &merged->snap;
+	struct io_region src, dest;
+	sector_t dev_size, old_sector;
+	struct dm_snap_exception e;
+	struct dm_snap_pending_exception *pe;
+	unsigned long flags;
+	int empty;
+	int r;
+
+	down_write(&s->lock);
+
+	if (merged->new_chunk == 0) {
+		r = s->store.prepare_merge(&s->store, &e, &empty);
+		if (r || empty) {
+			if (empty)
+				dm_table_event(s->table);
+			up_write(&s->lock);
+			return 0;
+		}
+
+		merged->old_chunk = e.old_chunk;
+		merged->new_chunk = e.new_chunk;
+	}
+
+	spin_lock_irqsave(&s->pe_lock, flags);
+
+	pe = (struct dm_snap_pending_exception *)
+		lookup_exception(&s->pending, merged->old_chunk);
+	if (pe) {
+		pe->merging_work = &merged->merging_work;
+		spin_unlock_irqrestore(&s->pe_lock, flags);
+		up_write(&s->lock);
+		return 0; /* punt the copy until pending I/O complete */
+	}
+
+	spin_unlock_irqrestore(&s->pe_lock, flags);
+
+	up_write(&s->lock);
+
+	old_sector = chunk_to_sector(s, merged->old_chunk);
+
+	r = do_origin(s->origin, old_sector, NULL, &merged->merging_work);
+	if (r <= 0)
+		return 0; /* punt until other snapshots finish copying */
+
+	dev_size = get_dev_size(s->origin->bdev);
+
+	dest.bdev = s->origin->bdev;
+	dest.sector = old_sector;
+	dest.count = min(s->chunk_size, dev_size - dest.sector);
+
+	src.bdev = s->cow->bdev;
+	src.sector = chunk_to_sector(s, merged->new_chunk);
+	src.count = dest.count;
+
+	r = kcopyd_copy(s->kcopyd_client, &src, 1, &dest, 0,
+			merge_callback, merged);
+	return r == 0;
+}
+
+static int end_merge(struct dm_merged *merged, int *error)
+{
+	struct dm_snapshot *s = &merged->snap;
+	struct dm_snap_exception *e;
+	struct bio *bio;
+	int r;
+
+	if (!get_merge_complete(merged))
+		return 0;
+
+	*error = get_merge_error(merged);
+
+	down_write(&s->lock);
+
+	e = lookup_exception(&s->complete, merged->old_chunk);
+	BUG_ON(!e);
+
+	merged->old_chunk = 0;
+	merged->new_chunk = 0;
+
+	if (*error) {
+		up_write(&s->lock);
+		return 1;
+	}
+
+	while ((bio = bio_list_pop(&merged->delayed_bios))) {
+		r = do_origin(s->origin, bio->bi_sector, bio, NULL);
+		if (r > 0)
+			generic_make_request(bio);
+		else if (r < 0) {
+			/* error the io and bail out */
+			bio_endio(bio, r);
+			bio_put(bio);
+		}
+	}
+
+	remove_exception(e);
+	free_exception(e);
+	s->store.commit_merge(&s->store);
+
+	up_write(&s->lock);
+
+	return 1;
+}
+
+static void do_merging(struct work_struct *work)
+{
+	struct dm_merged *merged =
+		container_of(work, struct dm_merged, merging_work);
+	int error;
+
+	if (end_merge(merged, &error)) {
+		up(&merged->merging);
+		if (error)
+			return;
+	}
+
+	if (down_trylock(&merged->merging))
+		return;
+
+	if (!start_merge(merged))
+		up(&merged->merging);
+}
+
 /*
  * Construct a merged snapshot: <origin_dev> <COW-dev>
  */
@@ -1360,6 +1536,17 @@ static int merged_ctr(struct dm_target *ti, unsigned int argc, char **argv)
 		return r;
 	}
 
+	sema_init(&merged->merging, 0);
+
+	INIT_WORK(&merged->merging_work, do_merging);
+
+	merged->status = 0;
+
+	merged->old_chunk = 0;
+	merged->new_chunk = 0;
+
+	bio_list_init(&merged->delayed_bios);
+
 	ti->private = merged;
 
 	return 0;
@@ -1396,6 +1583,13 @@ static int merged_map(struct dm_target *ti, struct bio *bio,
 		return -EIO;
 	}
 
+	if (bio_rw(bio) == WRITE && chunk == merged->old_chunk) {
+		bio->bi_bdev = s->origin->bdev;
+		bio_list_add(&merged->delayed_bios, bio);
+		up_write(&s->lock);
+		return 0;
+	}
+
 	e = lookup_exception(&s->complete, chunk);
 	if (e) {
 		if (bio_rw(bio) == WRITE) {
@@ -1413,7 +1607,7 @@ static int merged_map(struct dm_target *ti, struct bio *bio,
 		bio->bi_bdev = s->origin->bdev;
 
 		if (bio_rw(bio) == WRITE)
-			r = do_origin(s->origin, bio->bi_sector, bio);
+			r = do_origin(s->origin, bio->bi_sector, bio, NULL);
 	}
 
 out_unlock:
@@ -1440,6 +1634,16 @@ static void merged_resume(struct dm_target *ti)
 	struct dm_merged *merged = ti->private;
 
 	ti->split_io = min_chunk_size(merged->snap.origin);
+
+	up(&merged->merging);
+	queue_work(ksnapd, &merged->merging_work);
+}
+
+static void merged_postsuspend(struct dm_target *ti)
+{
+	struct dm_merged *merged = (struct dm_merged *) ti->private;
+
+	down(&merged->merging);
 }
 
 static int merged_status(struct dm_target *ti, status_type_t type, char *result,
@@ -1496,15 +1700,16 @@ static struct target_type snapshot_target = {
 };
 
 static struct target_type merged_target = {
-	.name    = "snapshot-merged",
-	.version = {1, 1, 1},
-	.module  = THIS_MODULE,
-	.ctr     = merged_ctr,
-	.dtr     = merged_dtr,
-	.map     = merged_map,
-	.end_io  = merged_end_io,
-	.resume  = merged_resume,
-	.status  = merged_status,
+	.name        = "snapshot-merged",
+	.version     = {1, 1, 1},
+	.module      = THIS_MODULE,
+	.ctr         = merged_ctr,
+	.dtr         = merged_dtr,
+	.map         = merged_map,
+	.end_io      = merged_end_io,
+	.resume      = merged_resume,
+	.postsuspend = merged_postsuspend,
+	.status      = merged_status,
 };
 
 static int __init dm_snapshot_init(void)
diff --git a/drivers/md/dm-snap.h b/drivers/md/dm-snap.h
index 8600477..048274c 100644
--- a/drivers/md/dm-snap.h
+++ b/drivers/md/dm-snap.h
@@ -190,6 +190,22 @@ struct dm_snapshot {
 
 struct dm_merged {
 	struct dm_snapshot snap;
+
+	/* held while there is a merge in progress */
+	struct semaphore merging;
+
+	/* merging work for ksnapd */
+	struct work_struct merging_work;
+
+	/* status of current merge - bitfield of MERGE_*_BIT */
+	unsigned long status;
+
+	/* current merge - could be delayed or in progress */
+	chunk_t old_chunk;
+	chunk_t new_chunk;
+
+	/* I/O waiting for current merge to complete */
+	struct bio_list delayed_bios;
 };
 
 /*
-- 
1.5.4.1




More information about the dm-devel mailing list