--- src/sys/vfs/hammer/hammer_blockmap.c 2008/02/23 20:55:23 1.5 +++ src/sys/vfs/hammer/hammer_blockmap.c 2008/03/18 05:19:15 1.6 @@ -39,6 +39,11 @@ */ #include "hammer.h" +static hammer_off_t hammer_find_hole(hammer_mount_t hmp, + hammer_holes_t holes, int bytes); +static void hammer_add_hole(hammer_mount_t hmp, hammer_holes_t holes, + hammer_off_t offset, int bytes); + /* * Allocate bytes from a zone */ @@ -58,6 +63,8 @@ hammer_blockmap_alloc(hammer_mount_t hmp hammer_off_t layer2_offset; hammer_off_t bigblock_offset; int loops = 0; + int skip_amount; + int used_hole; KKASSERT(zone >= HAMMER_ZONE_BTREE_INDEX && zone < HAMMER_MAX_ZONES); root_volume = hammer_get_root_volume(hmp, errorp); @@ -77,18 +84,33 @@ hammer_blockmap_alloc(hammer_mount_t hmp * new blockmap blocks. */ bytes = (bytes + 7) & ~7; - KKASSERT(bytes <= HAMMER_BUFSIZE); + KKASSERT(bytes > 0 && bytes <= HAMMER_BUFSIZE); lockmgr(&hmp->blockmap_lock, LK_EXCLUSIVE|LK_RETRY); - next_offset = rootmap->next_offset; + + /* + * Try to use a known-free hole, otherwise append. + */ + next_offset = hammer_find_hole(hmp, &hmp->holes[zone], bytes); + if (next_offset == 0) { + next_offset = rootmap->next_offset; + used_hole = 0; + } else { + used_hole = 1; + } again: /* - * The allocation request may not cross a buffer boundary + * The allocation request may not cross a buffer boundary. */ - tmp_offset = next_offset + bytes; - if ((next_offset ^ (tmp_offset - 1)) & ~HAMMER_BUFMASK64) - next_offset = (tmp_offset - 1) & ~HAMMER_BUFMASK64; + tmp_offset = next_offset + bytes - 1; + if ((next_offset ^ tmp_offset) & ~HAMMER_BUFMASK64) { + skip_amount = HAMMER_BUFSIZE - + ((int)next_offset & HAMMER_BUFMASK); + hammer_add_hole(hmp, &hmp->holes[zone], + next_offset, skip_amount); + next_offset = tmp_offset & ~HAMMER_BUFMASK64; + } /* * Dive layer 1. If we are starting a new layer 1 entry, @@ -213,12 +235,14 @@ again: * space beyond alloc_offset is uninitialized. alloc_offset must * be big-block aligned. */ - hammer_modify_volume(root_volume, rootmap, sizeof(*rootmap)); - rootmap->next_offset = next_offset + bytes; - if (rootmap->alloc_offset < rootmap->next_offset) { - rootmap->alloc_offset = - (rootmap->next_offset + HAMMER_LARGEBLOCK_MASK) & - ~HAMMER_LARGEBLOCK_MASK64; + if (used_hole == 0) { + hammer_modify_volume(root_volume, rootmap, sizeof(*rootmap)); + rootmap->next_offset = next_offset + bytes; + if (rootmap->alloc_offset < rootmap->next_offset) { + rootmap->alloc_offset = + (rootmap->next_offset + HAMMER_LARGEBLOCK_MASK) & + ~HAMMER_LARGEBLOCK_MASK64; + } } done: if (buffer1) @@ -339,6 +363,82 @@ done: } /* + * Return the number of free bytes in the big-block containing the + * specified blockmap offset. + */ +int +hammer_blockmap_getfree(hammer_mount_t hmp, hammer_off_t bmap_off, + int *curp, int *errorp) +{ + hammer_volume_t root_volume; + hammer_blockmap_t rootmap; + struct hammer_blockmap_layer1 *layer1; + struct hammer_blockmap_layer2 *layer2; + hammer_buffer_t buffer = NULL; + hammer_off_t layer1_offset; + hammer_off_t layer2_offset; + int bytes; + int zone; + + zone = HAMMER_ZONE_DECODE(bmap_off); + KKASSERT(zone >= HAMMER_ZONE_BTREE_INDEX && zone < HAMMER_MAX_ZONES); + root_volume = hammer_get_root_volume(hmp, errorp); + if (*errorp) { + *curp = 0; + return(0); + } + rootmap = &root_volume->ondisk->vol0_blockmap[zone]; + KKASSERT(rootmap->phys_offset != 0); + KKASSERT(HAMMER_ZONE_DECODE(rootmap->phys_offset) == + HAMMER_ZONE_RAW_BUFFER_INDEX); + KKASSERT(HAMMER_ZONE_DECODE(rootmap->alloc_offset) == zone); + + if (bmap_off >= rootmap->alloc_offset) { + panic("hammer_blockmap_lookup: %016llx beyond EOF %016llx", + bmap_off, rootmap->alloc_offset); + bytes = 0; + *curp = 0; + goto done; + } + + /* + * Dive layer 1. + */ + layer1_offset = rootmap->phys_offset + + HAMMER_BLOCKMAP_LAYER1_OFFSET(bmap_off); + layer1 = hammer_bread(hmp, layer1_offset, errorp, &buffer); + KKASSERT(*errorp == 0); + KKASSERT(layer1->phys_offset); + + /* + * Dive layer 2, each entry represents a large-block. + */ + layer2_offset = layer1->phys_offset + + HAMMER_BLOCKMAP_LAYER2_OFFSET(bmap_off); + layer2 = hammer_bread(hmp, layer2_offset, errorp, &buffer); + + KKASSERT(*errorp == 0); + KKASSERT(layer2->u.phys_offset); + + bytes = layer2->bytes_free; + + if ((rootmap->next_offset ^ bmap_off) & ~HAMMER_LARGEBLOCK_MASK64) + *curp = 0; + else + *curp = 1; +done: + if (buffer) + hammer_rel_buffer(buffer, 0); + hammer_rel_volume(root_volume, 0); + if (hammer_debug_general & 0x0800) { + kprintf("hammer_blockmap_getfree: %016llx -> %d\n", + bmap_off, bytes); + } + return(bytes); +} + + +/* * Lookup a blockmap offset. */ hammer_off_t @@ -404,3 +504,82 @@ done: return(result_offset); } +/************************************************************************ + * IN-CORE TRACKING OF ALLOCATION HOLES * + ************************************************************************ + * + * This is a temporary shim in need of a more permanent solution. + * + * As we allocate space holes are created due to having to align to a new + * 16K buffer when an allocation would otherwise cross the buffer boundary. + * These holes are recorded here and used to fullfill smaller requests as + * much as possible. Only a limited number of holes are recorded and these + * functions operate somewhat like a heuristic, where information is allowed + * to be thrown away. + */ + +void +hammer_init_holes(hammer_mount_t hmp, hammer_holes_t holes) +{ + TAILQ_INIT(&holes->list); + holes->count = 0; +} + +void +hammer_free_holes(hammer_mount_t hmp, hammer_holes_t holes) +{ + hammer_hole_t hole; + + while ((hole = TAILQ_FIRST(&holes->list)) != NULL) { + TAILQ_REMOVE(&holes->list, hole, entry); + kfree(hole, M_HAMMER); + } +} + +/* + * Attempt to locate a hole with sufficient free space to accomodate the + * requested allocation. Return the offset or 0 if no hole could be found. + */ +static hammer_off_t +hammer_find_hole(hammer_mount_t hmp, hammer_holes_t holes, int bytes) +{ + hammer_hole_t hole; + hammer_off_t result_off = 0; + + TAILQ_FOREACH(hole, &holes->list, entry) { + if (bytes <= hole->bytes) { + result_off = hole->offset; + hole->offset += bytes; + hole->bytes -= bytes; + break; + } + } + return(result_off); +} + +/* + * If a newly created hole is reasonably sized then record it. We only + * keep track of a limited number of holes. Lost holes are recovered by + * reblocking. + */ +static void +hammer_add_hole(hammer_mount_t hmp, hammer_holes_t holes, + hammer_off_t offset, int bytes) +{ + hammer_hole_t hole; + + if (bytes <= 128) + return; + + if (holes->count < HAMMER_MAX_HOLES) { + hole = kmalloc(sizeof(*hole), M_HAMMER, M_WAITOK); + ++holes->count; + } else { + hole = TAILQ_FIRST(&holes->list); + TAILQ_REMOVE(&holes->list, hole, entry); + } + TAILQ_INSERT_TAIL(&holes->list, hole, entry); + hole->offset = offset; + hole->bytes = bytes; +} +