From patchwork Mon Jul 18 08:52:20 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter Lieven X-Patchwork-Id: 9234211 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id B182A6075D for ; Mon, 18 Jul 2016 08:53:28 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A190C205AF for ; Mon, 18 Jul 2016 08:53:28 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 95D7026A4D; Mon, 18 Jul 2016 08:53:28 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 98544205AF for ; Mon, 18 Jul 2016 08:53:27 +0000 (UTC) Received: from localhost ([::1]:45203 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bP4Io-0004Mx-Od for patchwork-qemu-devel@patchwork.kernel.org; Mon, 18 Jul 2016 04:53:26 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:57784) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bP4IA-0003gp-JV for qemu-devel@nongnu.org; Mon, 18 Jul 2016 04:52:48 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1bP4I7-0008Bn-Dz for qemu-devel@nongnu.org; Mon, 18 Jul 2016 04:52:46 -0400 Received: from mx-v6.kamp.de ([2a02:248:0:51::16]:37957 helo=mx01.kamp.de) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1bP4I7-0008B5-3k for qemu-devel@nongnu.org; Mon, 18 Jul 2016 04:52:43 -0400 Received: (qmail 5107 invoked by uid 89); 18 Jul 2016 08:52:42 -0000 Received: from [195.62.97.28] by client-16-kamp (envelope-from , uid 89) with qmail-scanner-2010/03/19-MF (clamdscan: 0.99.2/21924. hbedv: 8.3.40.44/7.12.99.34. avast: 1.2.2/16071700. spamassassin: 3.4.1. Clear:RC:1(195.62.97.28):. Processed in 0.063299 secs); 18 Jul 2016 08:52:42 -0000 Received: from smtp.kamp.de (HELO submission.kamp.de) ([195.62.97.28]) by mx01.kamp.de with ESMTPS (DHE-RSA-AES256-GCM-SHA384 encrypted); 18 Jul 2016 08:52:41 -0000 X-GL_Whitelist: yes Received: (qmail 28435 invoked from network); 18 Jul 2016 08:52:36 -0000 Received: from lieven-pc.kamp-intra.net (HELO lieven-pc) (relay@kamp.de@::ffff:172.21.12.60) by submission.kamp.de with ESMTPS (DHE-RSA-AES256-GCM-SHA384 encrypted) ESMTPA; 18 Jul 2016 08:52:36 -0000 Received: by lieven-pc (Postfix, from userid 1000) id 0FAFB20A61; Mon, 18 Jul 2016 10:52:28 +0200 (CEST) From: Peter Lieven To: qemu-block@nongnu.org Date: Mon, 18 Jul 2016 10:52:20 +0200 Message-Id: <1468831940-15556-3-git-send-email-pl@kamp.de> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1468831940-15556-1-git-send-email-pl@kamp.de> References: <1468831940-15556-1-git-send-email-pl@kamp.de> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2a02:248:0:51::16 Subject: [Qemu-devel] [PATCH V5 2/2] block/iscsi: allow caching of the allocation map X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kwolf@redhat.com, famz@redhat.com, Peter Lieven , qemu-devel@nongnu.org, mreitz@redhat.com, ronniesahlberg@gmail.com, pbonzini@redhat.com Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP until now the allocation map was used only as a hint if a cluster is allocated or not. If a block was not allocated (or Qemu had no info about the allocation status) a get_block_status call was issued to check the allocation status and possibly avoid a subsequent read of unallocated sectors. If a block known to be allocated the get_block_status call was omitted. In the other case a get_block_status call was issued before every read to avoid the necessity for a consistent allocation map. To avoid the potential overhead of calling get_block_status for each and every read request this took only place for the bigger requests. This patch enhances this mechanism to cache the allocation status and avoid calling get_block_status for blocks where the allocation status has been queried before. This allows for bypassing the read request even for smaller requests and additionally omits calling get_block_status for known to be unallocated blocks. Signed-off-by: Peter Lieven --- block/iscsi.c | 250 ++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 192 insertions(+), 58 deletions(-) diff --git a/block/iscsi.c b/block/iscsi.c index 22330e1..21f1b2f 100644 --- a/block/iscsi.c +++ b/block/iscsi.c @@ -2,7 +2,7 @@ * QEMU Block driver for iSCSI images * * Copyright (c) 2010-2011 Ronnie Sahlberg - * Copyright (c) 2012-2015 Peter Lieven + * Copyright (c) 2012-2016 Peter Lieven * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -61,7 +61,23 @@ typedef struct IscsiLun { struct scsi_inquiry_logical_block_provisioning lbp; struct scsi_inquiry_block_limits bl; unsigned char *zeroblock; - unsigned long *allocationmap; + /* The allocmap tracks which clusters (pages) on the iSCSI target are + * allocated and which are not. In case a target returns zeros for + * unallocated pages (iscsilun->lprz) we can directly return zeros instead + * of reading zeros over the wire if a read request falls within an + * unallocated block. As there are 3 possible states we need 2 bitmaps to + * track. allocmap_valid keeps track if QEMU's information about a page is + * valid. allocmap tracks if a page is allocated or not. In case QEMU has no + * valid information about a page the corresponding allocmap entry should be + * switched to unallocated as well to force a new lookup of the allocation + * status as lookups are generally skipped if a page is suspect to be + * allocated. If a iSCSI target is opened with cache.direct = on the + * allocmap_valid does not exist turning all cached information invalid so + * that a fresh lookup is made for any page even if allocmap entry returns + * it's unallocated. */ + unsigned long *allocmap; + unsigned long *allocmap_valid; + long allocmap_size; int cluster_sectors; bool use_16_for_rw; bool write_protected; @@ -422,39 +438,135 @@ static bool is_sector_request_lun_aligned(int64_t sector_num, int nb_sectors, iscsilun); } -static unsigned long *iscsi_allocationmap_init(IscsiLun *iscsilun) +static void iscsi_allocmap_free(IscsiLun *iscsilun) { - return bitmap_try_new(DIV_ROUND_UP(sector_lun2qemu(iscsilun->num_blocks, - iscsilun), - iscsilun->cluster_sectors)); + g_free(iscsilun->allocmap); + g_free(iscsilun->allocmap_valid); + iscsilun->allocmap = NULL; + iscsilun->allocmap_valid = NULL; } -static void iscsi_allocationmap_set(IscsiLun *iscsilun, int64_t sector_num, - int nb_sectors) + +static int iscsi_allocmap_init(IscsiLun *iscsilun, int open_flags) { - int64_t cluster_num, nb_clusters; - if (iscsilun->allocationmap == NULL) { - return; + iscsi_allocmap_free(iscsilun); + + iscsilun->allocmap_size = + DIV_ROUND_UP(sector_lun2qemu(iscsilun->num_blocks, iscsilun), + iscsilun->cluster_sectors); + + iscsilun->allocmap = bitmap_try_new(iscsilun->allocmap_size); + if (!iscsilun->allocmap) { + return -ENOMEM; } - cluster_num = sector_num / iscsilun->cluster_sectors; - nb_clusters = DIV_ROUND_UP(sector_num + nb_sectors, - iscsilun->cluster_sectors) - cluster_num; - bitmap_set(iscsilun->allocationmap, cluster_num, nb_clusters); + + if (open_flags & BDRV_O_NOCACHE) { + /* in case that cache.direct = on all allocmap entries are + * treated as invalid to force a relookup of the block + * status on every read request */ + return 0; + } + + iscsilun->allocmap_valid = bitmap_try_new(iscsilun->allocmap_size); + if (!iscsilun->allocmap_valid) { + /* if we are under memory pressure free the allocmap as well */ + iscsi_allocmap_free(iscsilun); + return -ENOMEM; + } + + return 0; } -static void iscsi_allocationmap_clear(IscsiLun *iscsilun, int64_t sector_num, - int nb_sectors) +static void +iscsi_allocmap_update(IscsiLun *iscsilun, int64_t sector_num, + int nb_sectors, bool allocated, bool valid) { - int64_t cluster_num, nb_clusters; - if (iscsilun->allocationmap == NULL) { + int64_t cl_num_expanded, nb_cls_expanded, cl_num_shrinked, nb_cls_shrinked; + + if (iscsilun->allocmap == NULL) { return; } - cluster_num = DIV_ROUND_UP(sector_num, iscsilun->cluster_sectors); - nb_clusters = (sector_num + nb_sectors) / iscsilun->cluster_sectors - - cluster_num; - if (nb_clusters > 0) { - bitmap_clear(iscsilun->allocationmap, cluster_num, nb_clusters); + /* expand to entirely contain all affected clusters */ + cl_num_expanded = sector_num / iscsilun->cluster_sectors; + nb_cls_expanded = DIV_ROUND_UP(sector_num + nb_sectors, + iscsilun->cluster_sectors) - cl_num_expanded; + /* shrink to touch only completely contained clusters */ + cl_num_shrinked = DIV_ROUND_UP(sector_num, iscsilun->cluster_sectors); + nb_cls_shrinked = (sector_num + nb_sectors) / iscsilun->cluster_sectors + - cl_num_shrinked; + if (allocated) { + bitmap_set(iscsilun->allocmap, cl_num_expanded, nb_cls_expanded); + } else { + bitmap_clear(iscsilun->allocmap, cl_num_shrinked, nb_cls_shrinked); + } + + if (iscsilun->allocmap_valid == NULL) { + return; + } + if (valid) { + bitmap_set(iscsilun->allocmap_valid, cl_num_shrinked, nb_cls_shrinked); + } else { + bitmap_clear(iscsilun->allocmap_valid, cl_num_expanded, + nb_cls_expanded); + } +} + +static void +iscsi_allocmap_set_allocated(IscsiLun *iscsilun, int64_t sector_num, + int nb_sectors) +{ + iscsi_allocmap_update(iscsilun, sector_num, nb_sectors, true, true); +} + +static void +iscsi_allocmap_set_unallocated(IscsiLun *iscsilun, int64_t sector_num, + int nb_sectors) +{ + /* Note: if cache.direct=on the fifth argument to iscsi_allocmap_update + * is ignored, so this will in effect be an iscsi_allocmap_set_invalid. + */ + iscsi_allocmap_update(iscsilun, sector_num, nb_sectors, false, true); +} + +static void iscsi_allocmap_set_invalid(IscsiLun *iscsilun, int64_t sector_num, + int nb_sectors) +{ + iscsi_allocmap_update(iscsilun, sector_num, nb_sectors, false, false); +} + +static void iscsi_allocmap_invalidate(IscsiLun *iscsilun) +{ + if (iscsilun->allocmap) { + bitmap_zero(iscsilun->allocmap, iscsilun->allocmap_size); + } + if (iscsilun->allocmap_valid) { + bitmap_zero(iscsilun->allocmap_valid, iscsilun->allocmap_size); + } +} + +static inline bool +iscsi_allocmap_is_allocated(IscsiLun *iscsilun, int64_t sector_num, + int nb_sectors) +{ + unsigned long size; + if (iscsilun->allocmap == NULL) { + return true; } + size = DIV_ROUND_UP(sector_num + nb_sectors, iscsilun->cluster_sectors); + return !(find_next_bit(iscsilun->allocmap, size, + sector_num / iscsilun->cluster_sectors) == size); +} + +static inline bool iscsi_allocmap_is_valid(IscsiLun *iscsilun, + int64_t sector_num, int nb_sectors) +{ + unsigned long size; + if (iscsilun->allocmap_valid == NULL) { + return false; + } + size = DIV_ROUND_UP(sector_num + nb_sectors, iscsilun->cluster_sectors); + return (find_next_zero_bit(iscsilun->allocmap_valid, size, + sector_num / iscsilun->cluster_sectors) == size); } static int coroutine_fn @@ -517,26 +629,16 @@ retry: } if (iTask.status != SCSI_STATUS_GOOD) { + iscsi_allocmap_set_invalid(iscsilun, sector_num, nb_sectors); return iTask.err_code; } - iscsi_allocationmap_set(iscsilun, sector_num, nb_sectors); + iscsi_allocmap_set_allocated(iscsilun, sector_num, nb_sectors); return 0; } -static bool iscsi_allocationmap_is_allocated(IscsiLun *iscsilun, - int64_t sector_num, int nb_sectors) -{ - unsigned long size; - if (iscsilun->allocationmap == NULL) { - return true; - } - size = DIV_ROUND_UP(sector_num + nb_sectors, iscsilun->cluster_sectors); - return !(find_next_bit(iscsilun->allocationmap, size, - sector_num / iscsilun->cluster_sectors) == size); -} static int64_t coroutine_fn iscsi_co_get_block_status(BlockDriverState *bs, int64_t sector_num, @@ -621,9 +723,9 @@ retry: } if (ret & BDRV_BLOCK_ZERO) { - iscsi_allocationmap_clear(iscsilun, sector_num, *pnum); + iscsi_allocmap_set_unallocated(iscsilun, sector_num, *pnum); } else { - iscsi_allocationmap_set(iscsilun, sector_num, *pnum); + iscsi_allocmap_set_allocated(iscsilun, sector_num, *pnum); } if (*pnum > nb_sectors) { @@ -659,17 +761,32 @@ static int coroutine_fn iscsi_co_readv(BlockDriverState *bs, return -EINVAL; } - if (iscsilun->lbprz && nb_sectors >= ISCSI_CHECKALLOC_THRES && - !iscsi_allocationmap_is_allocated(iscsilun, sector_num, nb_sectors)) { - int64_t ret; + /* if cache.direct is off and we have a valid entry in our allocation map + * we can skip checking the block status and directly return zeroes if + * the request falls within an unallocated area */ + if (iscsi_allocmap_is_valid(iscsilun, sector_num, nb_sectors) && + !iscsi_allocmap_is_allocated(iscsilun, sector_num, nb_sectors)) { + qemu_iovec_memset(iov, 0, 0x00, iov->size); + return 0; + } + + if (nb_sectors >= ISCSI_CHECKALLOC_THRES && + !iscsi_allocmap_is_valid(iscsilun, sector_num, nb_sectors) && + !iscsi_allocmap_is_allocated(iscsilun, sector_num, nb_sectors)) { int pnum; BlockDriverState *file; - ret = iscsi_co_get_block_status(bs, sector_num, - BDRV_REQUEST_MAX_SECTORS, &pnum, &file); + /* check the block status from the beginning of the cluster + * containing the start sector */ + int64_t ret = iscsi_co_get_block_status(bs, + sector_num - sector_num % iscsilun->cluster_sectors, + BDRV_REQUEST_MAX_SECTORS, &pnum, &file); if (ret < 0) { return ret; } - if (ret & BDRV_BLOCK_ZERO && pnum >= nb_sectors) { + /* if the whole request falls into an unallocated area we can avoid + * to read and directly return zeroes instead */ + if (ret & BDRV_BLOCK_ZERO && + pnum >= nb_sectors + sector_num % iscsilun->cluster_sectors) { qemu_iovec_memset(iov, 0, 0x00, iov->size); return 0; } @@ -983,7 +1100,7 @@ retry: return iTask.err_code; } - iscsi_allocationmap_clear(iscsilun, sector_num, nb_sectors); + iscsi_allocmap_set_invalid(iscsilun, sector_num, nb_sectors); return 0; } @@ -1073,15 +1190,17 @@ retry: } if (iTask.status != SCSI_STATUS_GOOD) { + iscsi_allocmap_set_invalid(iscsilun, offset >> BDRV_SECTOR_BITS, + count >> BDRV_SECTOR_BITS); return iTask.err_code; } if (flags & BDRV_REQ_MAY_UNMAP) { - iscsi_allocationmap_clear(iscsilun, offset >> BDRV_SECTOR_BITS, - count >> BDRV_SECTOR_BITS); + iscsi_allocmap_set_invalid(iscsilun, offset >> BDRV_SECTOR_BITS, + count >> BDRV_SECTOR_BITS); } else { - iscsi_allocationmap_set(iscsilun, offset >> BDRV_SECTOR_BITS, - count >> BDRV_SECTOR_BITS); + iscsi_allocmap_set_allocated(iscsilun, offset >> BDRV_SECTOR_BITS, + count >> BDRV_SECTOR_BITS); } return 0; @@ -1654,10 +1773,7 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags, iscsilun->cluster_sectors = (iscsilun->bl.opt_unmap_gran * iscsilun->block_size) >> BDRV_SECTOR_BITS; if (iscsilun->lbprz) { - iscsilun->allocationmap = iscsi_allocationmap_init(iscsilun); - if (iscsilun->allocationmap == NULL) { - ret = -ENOMEM; - } + ret = iscsi_allocmap_init(iscsilun, bs->open_flags); } } @@ -1694,7 +1810,7 @@ static void iscsi_close(BlockDriverState *bs) } iscsi_destroy_context(iscsi); g_free(iscsilun->zeroblock); - g_free(iscsilun->allocationmap); + iscsi_allocmap_free(iscsilun); memset(iscsilun, 0, sizeof(IscsiLun)); } @@ -1758,6 +1874,16 @@ static int iscsi_reopen_prepare(BDRVReopenState *state, return 0; } +static void iscsi_reopen_commit(BDRVReopenState *reopen_state) +{ + IscsiLun *iscsilun = reopen_state->bs->opaque; + + /* the cache.direct status might have changed */ + if (iscsilun->allocmap != NULL) { + iscsi_allocmap_init(iscsilun, reopen_state->flags); + } +} + static int iscsi_truncate(BlockDriverState *bs, int64_t offset) { IscsiLun *iscsilun = bs->opaque; @@ -1777,9 +1903,8 @@ static int iscsi_truncate(BlockDriverState *bs, int64_t offset) return -EINVAL; } - if (iscsilun->allocationmap != NULL) { - g_free(iscsilun->allocationmap); - iscsilun->allocationmap = iscsi_allocationmap_init(iscsilun); + if (iscsilun->allocmap != NULL) { + iscsi_allocmap_init(iscsilun, bs->open_flags); } return 0; @@ -1839,6 +1964,13 @@ static int iscsi_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) return 0; } +static void iscsi_invalidate_cache(BlockDriverState *bs, + Error **errp) +{ + IscsiLun *iscsilun = bs->opaque; + iscsi_allocmap_invalidate(iscsilun); +} + static QemuOptsList iscsi_create_opts = { .name = "iscsi-create-opts", .head = QTAILQ_HEAD_INITIALIZER(iscsi_create_opts.head), @@ -1862,7 +1994,9 @@ static BlockDriver bdrv_iscsi = { .bdrv_close = iscsi_close, .bdrv_create = iscsi_create, .create_opts = &iscsi_create_opts, - .bdrv_reopen_prepare = iscsi_reopen_prepare, + .bdrv_reopen_prepare = iscsi_reopen_prepare, + .bdrv_reopen_commit = iscsi_reopen_commit, + .bdrv_invalidate_cache = iscsi_invalidate_cache, .bdrv_getlength = iscsi_getlength, .bdrv_get_info = iscsi_get_info,