1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2009-2010 The FreeBSD Foundation 5 * All rights reserved. 6 * 7 * This software was developed by Pawel Jakub Dawidek under sponsorship from 8 * the FreeBSD Foundation. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 __FBSDID("$FreeBSD$"); 34 35 #include <sys/param.h> /* powerof2() */ 36 #include <sys/queue.h> 37 38 #include <bitstring.h> 39 #include <errno.h> 40 #include <stdint.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 45 #include <pjdlog.h> 46 47 #include "activemap.h" 48 49 #ifndef PJDLOG_ASSERT 50 #include <assert.h> 51 #define PJDLOG_ASSERT(...) assert(__VA_ARGS__) 52 #endif 53 54 #define ACTIVEMAP_MAGIC 0xac71e4 55 struct activemap { 56 int am_magic; /* Magic value. */ 57 off_t am_mediasize; /* Media size in bytes. */ 58 uint32_t am_extentsize; /* Extent size in bytes, 59 must be power of 2. */ 60 uint8_t am_extentshift;/* 2 ^ extentbits == extentsize */ 61 int am_nextents; /* Number of extents. */ 62 size_t am_mapsize; /* Bitmap size in bytes. */ 63 uint16_t *am_memtab; /* An array that holds number of pending 64 writes per extent. */ 65 bitstr_t *am_diskmap; /* On-disk bitmap of dirty extents. */ 66 bitstr_t *am_memmap; /* In-memory bitmap of dirty extents. */ 67 size_t am_diskmapsize; /* Map size rounded up to sector size. */ 68 uint64_t am_ndirty; /* Number of dirty regions. */ 69 bitstr_t *am_syncmap; /* Bitmap of extents to sync. */ 70 off_t am_syncoff; /* Next synchronization offset. */ 71 TAILQ_HEAD(skeepdirty, keepdirty) am_keepdirty; /* List of extents that 72 we keep dirty to reduce bitmap 73 updates. */ 74 int am_nkeepdirty; /* Number of am_keepdirty elements. */ 75 int am_nkeepdirty_limit; /* Maximum number of am_keepdirty 76 elements. */ 77 }; 78 79 struct keepdirty { 80 int kd_extent; 81 TAILQ_ENTRY(keepdirty) kd_next; 82 }; 83 84 /* 85 * Helper function taken from sys/systm.h to calculate extentshift. 86 */ 87 static uint32_t 88 bitcount32(uint32_t x) 89 { 90 91 x = (x & 0x55555555) + ((x & 0xaaaaaaaa) >> 1); 92 x = (x & 0x33333333) + ((x & 0xcccccccc) >> 2); 93 x = (x + (x >> 4)) & 0x0f0f0f0f; 94 x = (x + (x >> 8)); 95 x = (x + (x >> 16)) & 0x000000ff; 96 return (x); 97 } 98 99 static __inline int 100 off2ext(const struct activemap *amp, off_t offset) 101 { 102 int extent; 103 104 PJDLOG_ASSERT(offset >= 0 && offset < amp->am_mediasize); 105 extent = (offset >> amp->am_extentshift); 106 PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents); 107 return (extent); 108 } 109 110 static __inline off_t 111 ext2off(const struct activemap *amp, int extent) 112 { 113 off_t offset; 114 115 PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents); 116 offset = ((off_t)extent << amp->am_extentshift); 117 PJDLOG_ASSERT(offset >= 0 && offset < amp->am_mediasize); 118 return (offset); 119 } 120 121 /* 122 * Function calculates number of requests needed to synchronize the given 123 * extent. 124 */ 125 static __inline int 126 ext2reqs(const struct activemap *amp, int ext) 127 { 128 off_t left; 129 130 if (ext < amp->am_nextents - 1) 131 return (((amp->am_extentsize - 1) / MAXPHYS) + 1); 132 133 PJDLOG_ASSERT(ext == amp->am_nextents - 1); 134 left = amp->am_mediasize % amp->am_extentsize; 135 if (left == 0) 136 left = amp->am_extentsize; 137 return (((left - 1) / MAXPHYS) + 1); 138 } 139 140 /* 141 * Initialize activemap structure and allocate memory for internal needs. 142 * Function returns 0 on success and -1 if any of the allocations failed. 143 */ 144 int 145 activemap_init(struct activemap **ampp, uint64_t mediasize, uint32_t extentsize, 146 uint32_t sectorsize, uint32_t keepdirty) 147 { 148 struct activemap *amp; 149 150 PJDLOG_ASSERT(ampp != NULL); 151 PJDLOG_ASSERT(mediasize > 0); 152 PJDLOG_ASSERT(extentsize > 0); 153 PJDLOG_ASSERT(powerof2(extentsize)); 154 PJDLOG_ASSERT(sectorsize > 0); 155 PJDLOG_ASSERT(powerof2(sectorsize)); 156 PJDLOG_ASSERT(keepdirty > 0); 157 158 amp = malloc(sizeof(*amp)); 159 if (amp == NULL) 160 return (-1); 161 162 amp->am_mediasize = mediasize; 163 amp->am_nkeepdirty_limit = keepdirty; 164 amp->am_extentsize = extentsize; 165 amp->am_extentshift = bitcount32(extentsize - 1); 166 amp->am_nextents = ((mediasize - 1) / extentsize) + 1; 167 amp->am_mapsize = bitstr_size(amp->am_nextents); 168 amp->am_diskmapsize = roundup2(amp->am_mapsize, sectorsize); 169 amp->am_ndirty = 0; 170 amp->am_syncoff = -2; 171 TAILQ_INIT(&->am_keepdirty); 172 amp->am_nkeepdirty = 0; 173 174 amp->am_memtab = calloc(amp->am_nextents, sizeof(amp->am_memtab[0])); 175 amp->am_diskmap = calloc(1, amp->am_diskmapsize); 176 amp->am_memmap = bit_alloc(amp->am_nextents); 177 amp->am_syncmap = bit_alloc(amp->am_nextents); 178 179 /* 180 * Check to see if any of the allocations above failed. 181 */ 182 if (amp->am_memtab == NULL || amp->am_diskmap == NULL || 183 amp->am_memmap == NULL || amp->am_syncmap == NULL) { 184 if (amp->am_memtab != NULL) 185 free(amp->am_memtab); 186 if (amp->am_diskmap != NULL) 187 free(amp->am_diskmap); 188 if (amp->am_memmap != NULL) 189 free(amp->am_memmap); 190 if (amp->am_syncmap != NULL) 191 free(amp->am_syncmap); 192 amp->am_magic = 0; 193 free(amp); 194 errno = ENOMEM; 195 return (-1); 196 } 197 198 amp->am_magic = ACTIVEMAP_MAGIC; 199 *ampp = amp; 200 201 return (0); 202 } 203 204 static struct keepdirty * 205 keepdirty_find(struct activemap *amp, int extent) 206 { 207 struct keepdirty *kd; 208 209 TAILQ_FOREACH(kd, &->am_keepdirty, kd_next) { 210 if (kd->kd_extent == extent) 211 break; 212 } 213 return (kd); 214 } 215 216 static bool 217 keepdirty_add(struct activemap *amp, int extent) 218 { 219 struct keepdirty *kd; 220 221 kd = keepdirty_find(amp, extent); 222 if (kd != NULL) { 223 /* 224 * Only move element at the beginning. 225 */ 226 TAILQ_REMOVE(&->am_keepdirty, kd, kd_next); 227 TAILQ_INSERT_HEAD(&->am_keepdirty, kd, kd_next); 228 return (false); 229 } 230 /* 231 * Add new element, but first remove the most unused one if 232 * we have too many. 233 */ 234 if (amp->am_nkeepdirty >= amp->am_nkeepdirty_limit) { 235 kd = TAILQ_LAST(&->am_keepdirty, skeepdirty); 236 PJDLOG_ASSERT(kd != NULL); 237 TAILQ_REMOVE(&->am_keepdirty, kd, kd_next); 238 amp->am_nkeepdirty--; 239 PJDLOG_ASSERT(amp->am_nkeepdirty > 0); 240 } 241 if (kd == NULL) 242 kd = malloc(sizeof(*kd)); 243 /* We can ignore allocation failure. */ 244 if (kd != NULL) { 245 kd->kd_extent = extent; 246 amp->am_nkeepdirty++; 247 TAILQ_INSERT_HEAD(&->am_keepdirty, kd, kd_next); 248 } 249 250 return (true); 251 } 252 253 static void 254 keepdirty_fill(struct activemap *amp) 255 { 256 struct keepdirty *kd; 257 258 TAILQ_FOREACH(kd, &->am_keepdirty, kd_next) 259 bit_set(amp->am_diskmap, kd->kd_extent); 260 } 261 262 static void 263 keepdirty_free(struct activemap *amp) 264 { 265 struct keepdirty *kd; 266 267 while ((kd = TAILQ_FIRST(&->am_keepdirty)) != NULL) { 268 TAILQ_REMOVE(&->am_keepdirty, kd, kd_next); 269 amp->am_nkeepdirty--; 270 free(kd); 271 } 272 PJDLOG_ASSERT(amp->am_nkeepdirty == 0); 273 } 274 275 /* 276 * Function frees resources allocated by activemap_init() function. 277 */ 278 void 279 activemap_free(struct activemap *amp) 280 { 281 282 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 283 284 amp->am_magic = 0; 285 286 keepdirty_free(amp); 287 free(amp->am_memtab); 288 free(amp->am_diskmap); 289 free(amp->am_memmap); 290 free(amp->am_syncmap); 291 } 292 293 /* 294 * Function should be called before we handle write requests. It updates 295 * internal structures and returns true if on-disk metadata should be updated. 296 */ 297 bool 298 activemap_write_start(struct activemap *amp, off_t offset, off_t length) 299 { 300 bool modified; 301 off_t end; 302 int ext; 303 304 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 305 PJDLOG_ASSERT(length > 0); 306 307 modified = false; 308 end = offset + length - 1; 309 310 for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) { 311 /* 312 * If the number of pending writes is increased from 0, 313 * we have to mark the extent as dirty also in on-disk bitmap. 314 * By returning true we inform the caller that on-disk bitmap 315 * was modified and has to be flushed to disk. 316 */ 317 if (amp->am_memtab[ext]++ == 0) { 318 PJDLOG_ASSERT(!bit_test(amp->am_memmap, ext)); 319 bit_set(amp->am_memmap, ext); 320 amp->am_ndirty++; 321 } 322 if (keepdirty_add(amp, ext)) 323 modified = true; 324 } 325 326 return (modified); 327 } 328 329 /* 330 * Function should be called after receiving write confirmation. It updates 331 * internal structures and returns true if on-disk metadata should be updated. 332 */ 333 bool 334 activemap_write_complete(struct activemap *amp, off_t offset, off_t length) 335 { 336 bool modified; 337 off_t end; 338 int ext; 339 340 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 341 PJDLOG_ASSERT(length > 0); 342 343 modified = false; 344 end = offset + length - 1; 345 346 for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) { 347 /* 348 * If the number of pending writes goes down to 0, we have to 349 * mark the extent as clean also in on-disk bitmap. 350 * By returning true we inform the caller that on-disk bitmap 351 * was modified and has to be flushed to disk. 352 */ 353 PJDLOG_ASSERT(amp->am_memtab[ext] > 0); 354 PJDLOG_ASSERT(bit_test(amp->am_memmap, ext)); 355 if (--amp->am_memtab[ext] == 0) { 356 bit_clear(amp->am_memmap, ext); 357 amp->am_ndirty--; 358 if (keepdirty_find(amp, ext) == NULL) 359 modified = true; 360 } 361 } 362 363 return (modified); 364 } 365 366 /* 367 * Function should be called after finishing synchronization of one extent. 368 * It returns true if on-disk metadata should be updated. 369 */ 370 bool 371 activemap_extent_complete(struct activemap *amp, int extent) 372 { 373 bool modified; 374 int reqs; 375 376 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 377 PJDLOG_ASSERT(extent >= 0 && extent < amp->am_nextents); 378 379 modified = false; 380 381 reqs = ext2reqs(amp, extent); 382 PJDLOG_ASSERT(amp->am_memtab[extent] >= reqs); 383 amp->am_memtab[extent] -= reqs; 384 PJDLOG_ASSERT(bit_test(amp->am_memmap, extent)); 385 if (amp->am_memtab[extent] == 0) { 386 bit_clear(amp->am_memmap, extent); 387 amp->am_ndirty--; 388 modified = true; 389 } 390 391 return (modified); 392 } 393 394 /* 395 * Function returns number of dirty regions. 396 */ 397 uint64_t 398 activemap_ndirty(const struct activemap *amp) 399 { 400 401 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 402 403 return (amp->am_ndirty); 404 } 405 406 /* 407 * Function compare on-disk bitmap and in-memory bitmap and returns true if 408 * they differ and should be flushed to the disk. 409 */ 410 bool 411 activemap_differ(const struct activemap *amp) 412 { 413 414 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 415 416 return (memcmp(amp->am_diskmap, amp->am_memmap, 417 amp->am_mapsize) != 0); 418 } 419 420 /* 421 * Function returns number of bytes used by bitmap. 422 */ 423 size_t 424 activemap_size(const struct activemap *amp) 425 { 426 427 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 428 429 return (amp->am_mapsize); 430 } 431 432 /* 433 * Function returns number of bytes needed for storing on-disk bitmap. 434 * This is the same as activemap_size(), but rounded up to sector size. 435 */ 436 size_t 437 activemap_ondisk_size(const struct activemap *amp) 438 { 439 440 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 441 442 return (amp->am_diskmapsize); 443 } 444 445 /* 446 * Function copies the given buffer read from disk to the internal bitmap. 447 */ 448 void 449 activemap_copyin(struct activemap *amp, const unsigned char *buf, size_t size) 450 { 451 int ext; 452 453 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 454 PJDLOG_ASSERT(size >= amp->am_mapsize); 455 456 memcpy(amp->am_diskmap, buf, amp->am_mapsize); 457 memcpy(amp->am_memmap, buf, amp->am_mapsize); 458 memcpy(amp->am_syncmap, buf, amp->am_mapsize); 459 460 bit_ffs(amp->am_memmap, amp->am_nextents, &ext); 461 if (ext == -1) { 462 /* There are no dirty extents, so we can leave now. */ 463 return; 464 } 465 /* 466 * Set synchronization offset to the first dirty extent. 467 */ 468 activemap_sync_rewind(amp); 469 /* 470 * We have dirty extents and we want them to stay that way until 471 * we synchronize, so we set number of pending writes to number 472 * of requests needed to synchronize one extent. 473 */ 474 amp->am_ndirty = 0; 475 for (; ext < amp->am_nextents; ext++) { 476 if (bit_test(amp->am_memmap, ext)) { 477 amp->am_memtab[ext] = ext2reqs(amp, ext); 478 amp->am_ndirty++; 479 } 480 } 481 } 482 483 /* 484 * Function merges the given bitmap with existing one. 485 */ 486 void 487 activemap_merge(struct activemap *amp, const unsigned char *buf, size_t size) 488 { 489 bitstr_t *remmap = __DECONST(bitstr_t *, buf); 490 int ext; 491 492 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 493 PJDLOG_ASSERT(size >= amp->am_mapsize); 494 495 bit_ffs(remmap, amp->am_nextents, &ext); 496 if (ext == -1) { 497 /* There are no dirty extents, so we can leave now. */ 498 return; 499 } 500 /* 501 * We have dirty extents and we want them to stay that way until 502 * we synchronize, so we set number of pending writes to number 503 * of requests needed to synchronize one extent. 504 */ 505 for (; ext < amp->am_nextents; ext++) { 506 /* Local extent already dirty. */ 507 if (bit_test(amp->am_syncmap, ext)) 508 continue; 509 /* Remote extent isn't dirty. */ 510 if (!bit_test(remmap, ext)) 511 continue; 512 bit_set(amp->am_syncmap, ext); 513 bit_set(amp->am_memmap, ext); 514 bit_set(amp->am_diskmap, ext); 515 if (amp->am_memtab[ext] == 0) 516 amp->am_ndirty++; 517 amp->am_memtab[ext] = ext2reqs(amp, ext); 518 } 519 /* 520 * Set synchronization offset to the first dirty extent. 521 */ 522 activemap_sync_rewind(amp); 523 } 524 525 /* 526 * Function returns pointer to internal bitmap that should be written to disk. 527 */ 528 const unsigned char * 529 activemap_bitmap(struct activemap *amp, size_t *sizep) 530 { 531 532 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 533 534 if (sizep != NULL) 535 *sizep = amp->am_diskmapsize; 536 memcpy(amp->am_diskmap, amp->am_memmap, amp->am_mapsize); 537 keepdirty_fill(amp); 538 return ((const unsigned char *)amp->am_diskmap); 539 } 540 541 /* 542 * Function calculates size needed to store bitmap on disk. 543 */ 544 size_t 545 activemap_calc_ondisk_size(uint64_t mediasize, uint32_t extentsize, 546 uint32_t sectorsize) 547 { 548 uint64_t nextents, mapsize; 549 550 PJDLOG_ASSERT(mediasize > 0); 551 PJDLOG_ASSERT(extentsize > 0); 552 PJDLOG_ASSERT(powerof2(extentsize)); 553 PJDLOG_ASSERT(sectorsize > 0); 554 PJDLOG_ASSERT(powerof2(sectorsize)); 555 556 nextents = ((mediasize - 1) / extentsize) + 1; 557 mapsize = bitstr_size(nextents); 558 return (roundup2(mapsize, sectorsize)); 559 } 560 561 /* 562 * Set synchronization offset to the first dirty extent. 563 */ 564 void 565 activemap_sync_rewind(struct activemap *amp) 566 { 567 int ext; 568 569 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 570 571 bit_ffs(amp->am_syncmap, amp->am_nextents, &ext); 572 if (ext == -1) { 573 /* There are no extents to synchronize. */ 574 amp->am_syncoff = -2; 575 return; 576 } 577 /* 578 * Mark that we want to start synchronization from the beginning. 579 */ 580 amp->am_syncoff = -1; 581 } 582 583 /* 584 * Return next offset of where we should synchronize. 585 */ 586 off_t 587 activemap_sync_offset(struct activemap *amp, off_t *lengthp, int *syncextp) 588 { 589 off_t syncoff, left; 590 int ext; 591 592 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 593 PJDLOG_ASSERT(lengthp != NULL); 594 PJDLOG_ASSERT(syncextp != NULL); 595 596 *syncextp = -1; 597 598 if (amp->am_syncoff == -2) 599 return (-1); 600 601 if (amp->am_syncoff >= 0 && 602 (amp->am_syncoff + MAXPHYS >= amp->am_mediasize || 603 off2ext(amp, amp->am_syncoff) != 604 off2ext(amp, amp->am_syncoff + MAXPHYS))) { 605 /* 606 * We are about to change extent, so mark previous one as clean. 607 */ 608 ext = off2ext(amp, amp->am_syncoff); 609 bit_clear(amp->am_syncmap, ext); 610 *syncextp = ext; 611 amp->am_syncoff = -1; 612 } 613 614 if (amp->am_syncoff == -1) { 615 /* 616 * Let's find first extent to synchronize. 617 */ 618 bit_ffs(amp->am_syncmap, amp->am_nextents, &ext); 619 if (ext == -1) { 620 amp->am_syncoff = -2; 621 return (-1); 622 } 623 amp->am_syncoff = ext2off(amp, ext); 624 } else { 625 /* 626 * We don't change extent, so just increase offset. 627 */ 628 amp->am_syncoff += MAXPHYS; 629 if (amp->am_syncoff >= amp->am_mediasize) { 630 amp->am_syncoff = -2; 631 return (-1); 632 } 633 } 634 635 syncoff = amp->am_syncoff; 636 left = ext2off(amp, off2ext(amp, syncoff)) + 637 amp->am_extentsize - syncoff; 638 if (syncoff + left > amp->am_mediasize) 639 left = amp->am_mediasize - syncoff; 640 if (left > MAXPHYS) 641 left = MAXPHYS; 642 643 PJDLOG_ASSERT(left >= 0 && left <= MAXPHYS); 644 PJDLOG_ASSERT(syncoff >= 0 && syncoff < amp->am_mediasize); 645 PJDLOG_ASSERT(syncoff + left >= 0 && 646 syncoff + left <= amp->am_mediasize); 647 648 *lengthp = left; 649 return (syncoff); 650 } 651 652 /* 653 * Mark extent(s) containing the given region for synchronization. 654 * Most likely one of the components is unavailable. 655 */ 656 bool 657 activemap_need_sync(struct activemap *amp, off_t offset, off_t length) 658 { 659 bool modified; 660 off_t end; 661 int ext; 662 663 PJDLOG_ASSERT(amp->am_magic == ACTIVEMAP_MAGIC); 664 665 modified = false; 666 end = offset + length - 1; 667 668 for (ext = off2ext(amp, offset); ext <= off2ext(amp, end); ext++) { 669 if (bit_test(amp->am_syncmap, ext)) { 670 /* Already marked for synchronization. */ 671 PJDLOG_ASSERT(bit_test(amp->am_memmap, ext)); 672 continue; 673 } 674 bit_set(amp->am_syncmap, ext); 675 if (!bit_test(amp->am_memmap, ext)) { 676 bit_set(amp->am_memmap, ext); 677 amp->am_ndirty++; 678 } 679 amp->am_memtab[ext] += ext2reqs(amp, ext); 680 modified = true; 681 } 682 683 return (modified); 684 } 685 686 void 687 activemap_dump(const struct activemap *amp) 688 { 689 int bit; 690 691 printf("M: "); 692 for (bit = 0; bit < amp->am_nextents; bit++) 693 printf("%d", bit_test(amp->am_memmap, bit) ? 1 : 0); 694 printf("\n"); 695 printf("D: "); 696 for (bit = 0; bit < amp->am_nextents; bit++) 697 printf("%d", bit_test(amp->am_diskmap, bit) ? 1 : 0); 698 printf("\n"); 699 printf("S: "); 700 for (bit = 0; bit < amp->am_nextents; bit++) 701 printf("%d", bit_test(amp->am_syncmap, bit) ? 1 : 0); 702 printf("\n"); 703 } 704