trim_map.c revision 276900
1/* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21/* 22 * Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>. 23 * All rights reserved. 24 */ 25 26#include <sys/zfs_context.h> 27#include <sys/spa_impl.h> 28#include <sys/vdev_impl.h> 29#include <sys/trim_map.h> 30#include <sys/time.h> 31 32/* 33 * Calculate the zio end, upgrading based on ashift which would be 34 * done by zio_vdev_io_start. 35 * 36 * This makes free range consolidation much more effective 37 * than it would otherwise be as well as ensuring that entire 38 * blocks are invalidated by writes. 39 */ 40#define TRIM_ZIO_END(vd, offset, size) (offset + \ 41 P2ROUNDUP(size, 1ULL << vd->vdev_top->vdev_ashift)) 42 43#define TRIM_MAP_SINC(tm, size) \ 44 atomic_add_64(&(tm)->tm_bytes, (size)) 45 46#define TRIM_MAP_SDEC(tm, size) \ 47 atomic_add_64(&(tm)->tm_bytes, -(size)) 48 49#define TRIM_MAP_QINC(tm) \ 50 atomic_inc_64(&(tm)->tm_pending); \ 51 52#define TRIM_MAP_QDEC(tm) \ 53 atomic_dec_64(&(tm)->tm_pending); 54 55typedef struct trim_map { 56 list_t tm_head; /* List of segments sorted by txg. */ 57 avl_tree_t tm_queued_frees; /* AVL tree of segments waiting for TRIM. */ 58 avl_tree_t tm_inflight_frees; /* AVL tree of in-flight TRIMs. */ 59 avl_tree_t tm_inflight_writes; /* AVL tree of in-flight writes. */ 60 list_t tm_pending_writes; /* Writes blocked on in-flight frees. */ 61 kmutex_t tm_lock; 62 uint64_t tm_pending; /* Count of pending TRIMs. */ 63 uint64_t tm_bytes; /* Total size in bytes of queued TRIMs. */ 64} trim_map_t; 65 66typedef struct trim_seg { 67 avl_node_t ts_node; /* AVL node. */ 68 list_node_t ts_next; /* List element. */ 69 uint64_t ts_start; /* Starting offset of this segment. */ 70 uint64_t ts_end; /* Ending offset (non-inclusive). */ 71 uint64_t ts_txg; /* Segment creation txg. */ 72 hrtime_t ts_time; /* Segment creation time. */ 73} trim_seg_t; 74 75extern boolean_t zfs_trim_enabled; 76 77static u_int trim_txg_delay = 32; 78static u_int trim_timeout = 30; 79static u_int trim_max_interval = 1; 80/* Limit outstanding TRIMs to 2G (max size for a single TRIM request) */ 81static uint64_t trim_vdev_max_bytes = 2147483648; 82/* Limit outstanding TRIMs to 64 (max ranges for a single TRIM request) */ 83static u_int trim_vdev_max_pending = 64; 84 85SYSCTL_DECL(_vfs_zfs); 86SYSCTL_NODE(_vfs_zfs, OID_AUTO, trim, CTLFLAG_RD, 0, "ZFS TRIM"); 87 88TUNABLE_INT("vfs.zfs.trim.txg_delay", &trim_txg_delay); 89SYSCTL_UINT(_vfs_zfs_trim, OID_AUTO, txg_delay, CTLFLAG_RWTUN, &trim_txg_delay, 90 0, "Delay TRIMs by up to this many TXGs"); 91 92TUNABLE_INT("vfs.zfs.trim.timeout", &trim_timeout); 93SYSCTL_UINT(_vfs_zfs_trim, OID_AUTO, timeout, CTLFLAG_RWTUN, &trim_timeout, 0, 94 "Delay TRIMs by up to this many seconds"); 95 96TUNABLE_INT("vfs.zfs.trim.max_interval", &trim_max_interval); 97SYSCTL_UINT(_vfs_zfs_trim, OID_AUTO, max_interval, CTLFLAG_RWTUN, 98 &trim_max_interval, 0, 99 "Maximum interval between TRIM queue processing (seconds)"); 100 101SYSCTL_DECL(_vfs_zfs_vdev); 102TUNABLE_QUAD("vfs.zfs.vdev.trim_max_bytes", &trim_vdev_max_bytes); 103SYSCTL_QUAD(_vfs_zfs_vdev, OID_AUTO, trim_max_bytes, CTLFLAG_RWTUN, 104 &trim_vdev_max_bytes, 0, 105 "Maximum pending TRIM bytes for a vdev"); 106 107TUNABLE_INT("vfs.zfs.vdev.trim_max_pending", &trim_vdev_max_pending); 108SYSCTL_UINT(_vfs_zfs_vdev, OID_AUTO, trim_max_pending, CTLFLAG_RWTUN, 109 &trim_vdev_max_pending, 0, 110 "Maximum pending TRIM segments for a vdev"); 111 112 113static void trim_map_vdev_commit_done(spa_t *spa, vdev_t *vd); 114 115static int 116trim_map_seg_compare(const void *x1, const void *x2) 117{ 118 const trim_seg_t *s1 = x1; 119 const trim_seg_t *s2 = x2; 120 121 if (s1->ts_start < s2->ts_start) { 122 if (s1->ts_end > s2->ts_start) 123 return (0); 124 return (-1); 125 } 126 if (s1->ts_start > s2->ts_start) { 127 if (s1->ts_start < s2->ts_end) 128 return (0); 129 return (1); 130 } 131 return (0); 132} 133 134static int 135trim_map_zio_compare(const void *x1, const void *x2) 136{ 137 const zio_t *z1 = x1; 138 const zio_t *z2 = x2; 139 140 if (z1->io_offset < z2->io_offset) { 141 if (z1->io_offset + z1->io_size > z2->io_offset) 142 return (0); 143 return (-1); 144 } 145 if (z1->io_offset > z2->io_offset) { 146 if (z1->io_offset < z2->io_offset + z2->io_size) 147 return (0); 148 return (1); 149 } 150 return (0); 151} 152 153void 154trim_map_create(vdev_t *vd) 155{ 156 trim_map_t *tm; 157 158 ASSERT(zfs_trim_enabled && !vd->vdev_notrim && 159 vd->vdev_ops->vdev_op_leaf); 160 161 tm = kmem_zalloc(sizeof (*tm), KM_SLEEP); 162 mutex_init(&tm->tm_lock, NULL, MUTEX_DEFAULT, NULL); 163 list_create(&tm->tm_head, sizeof (trim_seg_t), 164 offsetof(trim_seg_t, ts_next)); 165 list_create(&tm->tm_pending_writes, sizeof (zio_t), 166 offsetof(zio_t, io_trim_link)); 167 avl_create(&tm->tm_queued_frees, trim_map_seg_compare, 168 sizeof (trim_seg_t), offsetof(trim_seg_t, ts_node)); 169 avl_create(&tm->tm_inflight_frees, trim_map_seg_compare, 170 sizeof (trim_seg_t), offsetof(trim_seg_t, ts_node)); 171 avl_create(&tm->tm_inflight_writes, trim_map_zio_compare, 172 sizeof (zio_t), offsetof(zio_t, io_trim_node)); 173 vd->vdev_trimmap = tm; 174} 175 176void 177trim_map_destroy(vdev_t *vd) 178{ 179 trim_map_t *tm; 180 trim_seg_t *ts; 181 182 ASSERT(vd->vdev_ops->vdev_op_leaf); 183 184 if (!zfs_trim_enabled) 185 return; 186 187 tm = vd->vdev_trimmap; 188 if (tm == NULL) 189 return; 190 191 /* 192 * We may have been called before trim_map_vdev_commit_done() 193 * had a chance to run, so do it now to prune the remaining 194 * inflight frees. 195 */ 196 trim_map_vdev_commit_done(vd->vdev_spa, vd); 197 198 mutex_enter(&tm->tm_lock); 199 while ((ts = list_head(&tm->tm_head)) != NULL) { 200 avl_remove(&tm->tm_queued_frees, ts); 201 list_remove(&tm->tm_head, ts); 202 kmem_free(ts, sizeof (*ts)); 203 TRIM_MAP_SDEC(tm, ts->ts_end - ts->ts_start); 204 TRIM_MAP_QDEC(tm); 205 } 206 mutex_exit(&tm->tm_lock); 207 208 avl_destroy(&tm->tm_queued_frees); 209 avl_destroy(&tm->tm_inflight_frees); 210 avl_destroy(&tm->tm_inflight_writes); 211 list_destroy(&tm->tm_pending_writes); 212 list_destroy(&tm->tm_head); 213 mutex_destroy(&tm->tm_lock); 214 kmem_free(tm, sizeof (*tm)); 215 vd->vdev_trimmap = NULL; 216} 217 218static void 219trim_map_segment_add(trim_map_t *tm, uint64_t start, uint64_t end, uint64_t txg) 220{ 221 avl_index_t where; 222 trim_seg_t tsearch, *ts_before, *ts_after, *ts; 223 boolean_t merge_before, merge_after; 224 hrtime_t time; 225 226 ASSERT(MUTEX_HELD(&tm->tm_lock)); 227 VERIFY(start < end); 228 229 time = gethrtime(); 230 tsearch.ts_start = start; 231 tsearch.ts_end = end; 232 233 ts = avl_find(&tm->tm_queued_frees, &tsearch, &where); 234 if (ts != NULL) { 235 if (start < ts->ts_start) 236 trim_map_segment_add(tm, start, ts->ts_start, txg); 237 if (end > ts->ts_end) 238 trim_map_segment_add(tm, ts->ts_end, end, txg); 239 return; 240 } 241 242 ts_before = avl_nearest(&tm->tm_queued_frees, where, AVL_BEFORE); 243 ts_after = avl_nearest(&tm->tm_queued_frees, where, AVL_AFTER); 244 245 merge_before = (ts_before != NULL && ts_before->ts_end == start); 246 merge_after = (ts_after != NULL && ts_after->ts_start == end); 247 248 if (merge_before && merge_after) { 249 TRIM_MAP_SINC(tm, ts_after->ts_start - ts_before->ts_end); 250 TRIM_MAP_QDEC(tm); 251 avl_remove(&tm->tm_queued_frees, ts_before); 252 list_remove(&tm->tm_head, ts_before); 253 ts_after->ts_start = ts_before->ts_start; 254 ts_after->ts_txg = txg; 255 ts_after->ts_time = time; 256 kmem_free(ts_before, sizeof (*ts_before)); 257 } else if (merge_before) { 258 TRIM_MAP_SINC(tm, end - ts_before->ts_end); 259 ts_before->ts_end = end; 260 ts_before->ts_txg = txg; 261 ts_before->ts_time = time; 262 } else if (merge_after) { 263 TRIM_MAP_SINC(tm, ts_after->ts_start - start); 264 ts_after->ts_start = start; 265 ts_after->ts_txg = txg; 266 ts_after->ts_time = time; 267 } else { 268 TRIM_MAP_SINC(tm, end - start); 269 TRIM_MAP_QINC(tm); 270 ts = kmem_alloc(sizeof (*ts), KM_SLEEP); 271 ts->ts_start = start; 272 ts->ts_end = end; 273 ts->ts_txg = txg; 274 ts->ts_time = time; 275 avl_insert(&tm->tm_queued_frees, ts, where); 276 list_insert_tail(&tm->tm_head, ts); 277 } 278} 279 280static void 281trim_map_segment_remove(trim_map_t *tm, trim_seg_t *ts, uint64_t start, 282 uint64_t end) 283{ 284 trim_seg_t *nts; 285 boolean_t left_over, right_over; 286 287 ASSERT(MUTEX_HELD(&tm->tm_lock)); 288 289 left_over = (ts->ts_start < start); 290 right_over = (ts->ts_end > end); 291 292 TRIM_MAP_SDEC(tm, end - start); 293 if (left_over && right_over) { 294 nts = kmem_alloc(sizeof (*nts), KM_SLEEP); 295 nts->ts_start = end; 296 nts->ts_end = ts->ts_end; 297 nts->ts_txg = ts->ts_txg; 298 nts->ts_time = ts->ts_time; 299 ts->ts_end = start; 300 avl_insert_here(&tm->tm_queued_frees, nts, ts, AVL_AFTER); 301 list_insert_after(&tm->tm_head, ts, nts); 302 TRIM_MAP_QINC(tm); 303 } else if (left_over) { 304 ts->ts_end = start; 305 } else if (right_over) { 306 ts->ts_start = end; 307 } else { 308 avl_remove(&tm->tm_queued_frees, ts); 309 list_remove(&tm->tm_head, ts); 310 TRIM_MAP_QDEC(tm); 311 kmem_free(ts, sizeof (*ts)); 312 } 313} 314 315static void 316trim_map_free_locked(trim_map_t *tm, uint64_t start, uint64_t end, uint64_t txg) 317{ 318 zio_t zsearch, *zs; 319 320 ASSERT(MUTEX_HELD(&tm->tm_lock)); 321 322 zsearch.io_offset = start; 323 zsearch.io_size = end - start; 324 325 zs = avl_find(&tm->tm_inflight_writes, &zsearch, NULL); 326 if (zs == NULL) { 327 trim_map_segment_add(tm, start, end, txg); 328 return; 329 } 330 if (start < zs->io_offset) 331 trim_map_free_locked(tm, start, zs->io_offset, txg); 332 if (zs->io_offset + zs->io_size < end) 333 trim_map_free_locked(tm, zs->io_offset + zs->io_size, end, txg); 334} 335 336void 337trim_map_free(vdev_t *vd, uint64_t offset, uint64_t size, uint64_t txg) 338{ 339 trim_map_t *tm = vd->vdev_trimmap; 340 341 if (!zfs_trim_enabled || vd->vdev_notrim || tm == NULL) 342 return; 343 344 mutex_enter(&tm->tm_lock); 345 trim_map_free_locked(tm, offset, TRIM_ZIO_END(vd, offset, size), txg); 346 mutex_exit(&tm->tm_lock); 347} 348 349boolean_t 350trim_map_write_start(zio_t *zio) 351{ 352 vdev_t *vd = zio->io_vd; 353 trim_map_t *tm = vd->vdev_trimmap; 354 trim_seg_t tsearch, *ts; 355 boolean_t left_over, right_over; 356 uint64_t start, end; 357 358 if (!zfs_trim_enabled || vd->vdev_notrim || tm == NULL) 359 return (B_TRUE); 360 361 start = zio->io_offset; 362 end = TRIM_ZIO_END(zio->io_vd, start, zio->io_size); 363 tsearch.ts_start = start; 364 tsearch.ts_end = end; 365 366 mutex_enter(&tm->tm_lock); 367 368 /* 369 * Checking for colliding in-flight frees. 370 */ 371 ts = avl_find(&tm->tm_inflight_frees, &tsearch, NULL); 372 if (ts != NULL) { 373 list_insert_tail(&tm->tm_pending_writes, zio); 374 mutex_exit(&tm->tm_lock); 375 return (B_FALSE); 376 } 377 378 ts = avl_find(&tm->tm_queued_frees, &tsearch, NULL); 379 if (ts != NULL) { 380 /* 381 * Loop until all overlapping segments are removed. 382 */ 383 do { 384 trim_map_segment_remove(tm, ts, start, end); 385 ts = avl_find(&tm->tm_queued_frees, &tsearch, NULL); 386 } while (ts != NULL); 387 } 388 avl_add(&tm->tm_inflight_writes, zio); 389 390 mutex_exit(&tm->tm_lock); 391 392 return (B_TRUE); 393} 394 395void 396trim_map_write_done(zio_t *zio) 397{ 398 vdev_t *vd = zio->io_vd; 399 trim_map_t *tm = vd->vdev_trimmap; 400 401 /* 402 * Don't check for vdev_notrim, since the write could have 403 * started before vdev_notrim was set. 404 */ 405 if (!zfs_trim_enabled || tm == NULL) 406 return; 407 408 mutex_enter(&tm->tm_lock); 409 /* 410 * Don't fail if the write isn't in the tree, since the write 411 * could have started after vdev_notrim was set. 412 */ 413 if (zio->io_trim_node.avl_child[0] || 414 zio->io_trim_node.avl_child[1] || 415 AVL_XPARENT(&zio->io_trim_node) || 416 tm->tm_inflight_writes.avl_root == &zio->io_trim_node) 417 avl_remove(&tm->tm_inflight_writes, zio); 418 mutex_exit(&tm->tm_lock); 419} 420 421/* 422 * Return the oldest segment (the one with the lowest txg / time) or NULL if: 423 * 1. The list is empty 424 * 2. The first element's txg is greater than txgsafe 425 * 3. The first element's txg is not greater than the txg argument and the 426 * the first element's time is not greater than time argument 427 */ 428static trim_seg_t * 429trim_map_first(trim_map_t *tm, uint64_t txg, uint64_t txgsafe, hrtime_t time) 430{ 431 trim_seg_t *ts; 432 433 ASSERT(MUTEX_HELD(&tm->tm_lock)); 434 VERIFY(txgsafe >= txg); 435 436 ts = list_head(&tm->tm_head); 437 if (ts != NULL && ts->ts_txg <= txgsafe && 438 (ts->ts_txg <= txg || ts->ts_time <= time || 439 tm->tm_bytes > trim_vdev_max_bytes || 440 tm->tm_pending > trim_vdev_max_pending)) 441 return (ts); 442 return (NULL); 443} 444 445static void 446trim_map_vdev_commit(spa_t *spa, zio_t *zio, vdev_t *vd) 447{ 448 trim_map_t *tm = vd->vdev_trimmap; 449 trim_seg_t *ts; 450 uint64_t size, offset, txgtarget, txgsafe; 451 hrtime_t timelimit; 452 453 ASSERT(vd->vdev_ops->vdev_op_leaf); 454 455 if (tm == NULL) 456 return; 457 458 timelimit = gethrtime() - trim_timeout * NANOSEC; 459 if (vd->vdev_isl2cache) { 460 txgsafe = UINT64_MAX; 461 txgtarget = UINT64_MAX; 462 } else { 463 txgsafe = MIN(spa_last_synced_txg(spa), spa_freeze_txg(spa)); 464 if (txgsafe > trim_txg_delay) 465 txgtarget = txgsafe - trim_txg_delay; 466 else 467 txgtarget = 0; 468 } 469 470 mutex_enter(&tm->tm_lock); 471 /* Loop until we have sent all outstanding free's */ 472 while ((ts = trim_map_first(tm, txgtarget, txgsafe, timelimit)) 473 != NULL) { 474 list_remove(&tm->tm_head, ts); 475 avl_remove(&tm->tm_queued_frees, ts); 476 avl_add(&tm->tm_inflight_frees, ts); 477 size = ts->ts_end - ts->ts_start; 478 offset = ts->ts_start; 479 TRIM_MAP_SDEC(tm, size); 480 TRIM_MAP_QDEC(tm); 481 /* 482 * We drop the lock while we call zio_nowait as the IO 483 * scheduler can result in a different IO being run e.g. 484 * a write which would result in a recursive lock. 485 */ 486 mutex_exit(&tm->tm_lock); 487 488 zio_nowait(zio_trim(zio, spa, vd, offset, size)); 489 490 mutex_enter(&tm->tm_lock); 491 } 492 mutex_exit(&tm->tm_lock); 493} 494 495static void 496trim_map_vdev_commit_done(spa_t *spa, vdev_t *vd) 497{ 498 trim_map_t *tm = vd->vdev_trimmap; 499 trim_seg_t *ts; 500 list_t pending_writes; 501 zio_t *zio; 502 uint64_t start, size; 503 void *cookie; 504 505 ASSERT(vd->vdev_ops->vdev_op_leaf); 506 507 if (tm == NULL) 508 return; 509 510 mutex_enter(&tm->tm_lock); 511 if (!avl_is_empty(&tm->tm_inflight_frees)) { 512 cookie = NULL; 513 while ((ts = avl_destroy_nodes(&tm->tm_inflight_frees, 514 &cookie)) != NULL) { 515 kmem_free(ts, sizeof (*ts)); 516 } 517 } 518 list_create(&pending_writes, sizeof (zio_t), offsetof(zio_t, 519 io_trim_link)); 520 list_move_tail(&pending_writes, &tm->tm_pending_writes); 521 mutex_exit(&tm->tm_lock); 522 523 while ((zio = list_remove_head(&pending_writes)) != NULL) { 524 zio_vdev_io_reissue(zio); 525 zio_execute(zio); 526 } 527 list_destroy(&pending_writes); 528} 529 530static void 531trim_map_commit(spa_t *spa, zio_t *zio, vdev_t *vd) 532{ 533 int c; 534 535 if (vd == NULL) 536 return; 537 538 if (vd->vdev_ops->vdev_op_leaf) { 539 trim_map_vdev_commit(spa, zio, vd); 540 } else { 541 for (c = 0; c < vd->vdev_children; c++) 542 trim_map_commit(spa, zio, vd->vdev_child[c]); 543 } 544} 545 546static void 547trim_map_commit_done(spa_t *spa, vdev_t *vd) 548{ 549 int c; 550 551 if (vd == NULL) 552 return; 553 554 if (vd->vdev_ops->vdev_op_leaf) { 555 trim_map_vdev_commit_done(spa, vd); 556 } else { 557 for (c = 0; c < vd->vdev_children; c++) 558 trim_map_commit_done(spa, vd->vdev_child[c]); 559 } 560} 561 562static void 563trim_thread(void *arg) 564{ 565 spa_t *spa = arg; 566 zio_t *zio; 567 568#ifdef _KERNEL 569 (void) snprintf(curthread->td_name, sizeof(curthread->td_name), 570 "trim %s", spa_name(spa)); 571#endif 572 573 for (;;) { 574 mutex_enter(&spa->spa_trim_lock); 575 if (spa->spa_trim_thread == NULL) { 576 spa->spa_trim_thread = curthread; 577 cv_signal(&spa->spa_trim_cv); 578 mutex_exit(&spa->spa_trim_lock); 579 thread_exit(); 580 } 581 582 (void) cv_timedwait(&spa->spa_trim_cv, &spa->spa_trim_lock, 583 hz * trim_max_interval); 584 mutex_exit(&spa->spa_trim_lock); 585 586 zio = zio_root(spa, NULL, NULL, ZIO_FLAG_CANFAIL); 587 588 spa_config_enter(spa, SCL_STATE, FTAG, RW_READER); 589 trim_map_commit(spa, zio, spa->spa_root_vdev); 590 (void) zio_wait(zio); 591 trim_map_commit_done(spa, spa->spa_root_vdev); 592 spa_config_exit(spa, SCL_STATE, FTAG); 593 } 594} 595 596void 597trim_thread_create(spa_t *spa) 598{ 599 600 if (!zfs_trim_enabled) 601 return; 602 603 mutex_init(&spa->spa_trim_lock, NULL, MUTEX_DEFAULT, NULL); 604 cv_init(&spa->spa_trim_cv, NULL, CV_DEFAULT, NULL); 605 mutex_enter(&spa->spa_trim_lock); 606 spa->spa_trim_thread = thread_create(NULL, 0, trim_thread, spa, 0, &p0, 607 TS_RUN, minclsyspri); 608 mutex_exit(&spa->spa_trim_lock); 609} 610 611void 612trim_thread_destroy(spa_t *spa) 613{ 614 615 if (!zfs_trim_enabled) 616 return; 617 if (spa->spa_trim_thread == NULL) 618 return; 619 620 mutex_enter(&spa->spa_trim_lock); 621 /* Setting spa_trim_thread to NULL tells the thread to stop. */ 622 spa->spa_trim_thread = NULL; 623 cv_signal(&spa->spa_trim_cv); 624 /* The thread will set it back to != NULL on exit. */ 625 while (spa->spa_trim_thread == NULL) 626 cv_wait(&spa->spa_trim_cv, &spa->spa_trim_lock); 627 spa->spa_trim_thread = NULL; 628 mutex_exit(&spa->spa_trim_lock); 629 630 cv_destroy(&spa->spa_trim_cv); 631 mutex_destroy(&spa->spa_trim_lock); 632} 633 634void 635trim_thread_wakeup(spa_t *spa) 636{ 637 638 if (!zfs_trim_enabled) 639 return; 640 if (spa->spa_trim_thread == NULL) 641 return; 642 643 mutex_enter(&spa->spa_trim_lock); 644 cv_signal(&spa->spa_trim_cv); 645 mutex_exit(&spa->spa_trim_lock); 646} 647