1/*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2009-2010 Fabio Checconi 5 * Copyright (c) 2009-2010 Luigi Rizzo, Universita` di Pisa 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30/* 31 * $Id$ 32 * $FreeBSD$ 33 * 34 * A round-robin (RR) anticipatory scheduler, with per-client queues. 35 * 36 * The goal of this implementation is to improve throughput compared 37 * to the pure elevator algorithm, and insure some fairness among 38 * clients. 39 * 40 * Requests coming from the same client are put in the same queue. 41 * We use anticipation to help reducing seeks, and each queue 42 * is never served continuously for more than a given amount of 43 * time or data. Queues are then served in a round-robin fashion. 44 * 45 * Each queue can be in any of the following states: 46 * READY immediately serve the first pending request; 47 * BUSY one request is under service, wait for completion; 48 * IDLING do not serve incoming requests immediately, unless 49 * they are "eligible" as defined later. 50 * 51 * Scheduling is made looking at the status of all queues, 52 * and the first one in round-robin order is privileged. 53 */ 54 55#include <sys/param.h> 56#include <sys/systm.h> 57#include <sys/kernel.h> 58#include <sys/bio.h> 59#include <sys/callout.h> 60#include <sys/malloc.h> 61#include <sys/module.h> 62#include <sys/proc.h> 63#include <sys/queue.h> 64#include <sys/sbuf.h> 65#include <sys/sysctl.h> 66#include "gs_scheduler.h" 67 68/* possible states of the scheduler */ 69enum g_rr_state { 70 G_QUEUE_READY = 0, /* Ready to dispatch. */ 71 G_QUEUE_BUSY, /* Waiting for a completion. */ 72 G_QUEUE_IDLING /* Waiting for a new request. */ 73}; 74 75/* possible queue flags */ 76enum g_rr_flags { 77 /* G_FLAG_COMPLETED means that the field q_slice_end is valid. */ 78 G_FLAG_COMPLETED = 1, /* Completed a req. in the current budget. */ 79}; 80 81struct g_rr_softc; 82 83/* 84 * Queue descriptor, containing reference count, scheduling 85 * state, a queue of pending requests, configuration parameters. 86 * Queues with pending request(s) and not under service are also 87 * stored in a Round Robin (RR) list. 88 */ 89struct g_rr_queue { 90 struct g_rr_softc *q_sc; /* link to the parent */ 91 92 enum g_rr_state q_status; 93 unsigned int q_service; /* service received so far */ 94 int q_slice_end; /* actual slice end time, in ticks */ 95 enum g_rr_flags q_flags; /* queue flags */ 96 struct bio_queue_head q_bioq; 97 98 /* Scheduling parameters */ 99 unsigned int q_budget; /* slice size in bytes */ 100 unsigned int q_slice_duration; /* slice size in ticks */ 101 unsigned int q_wait_ticks; /* wait time for anticipation */ 102 103 /* Stats to drive the various heuristics. */ 104 struct g_savg q_thinktime; /* Thinktime average. */ 105 struct g_savg q_seekdist; /* Seek distance average. */ 106 107 int q_bionum; /* Number of requests. */ 108 109 off_t q_lastoff; /* Last submitted req. offset. */ 110 int q_lastsub; /* Last submitted req. time. */ 111 112 /* Expiration deadline for an empty queue. */ 113 int q_expire; 114 115 TAILQ_ENTRY(g_rr_queue) q_tailq; /* RR list link field */ 116}; 117 118/* List types. */ 119TAILQ_HEAD(g_rr_tailq, g_rr_queue); 120 121/* list of scheduler instances */ 122LIST_HEAD(g_scheds, g_rr_softc); 123 124/* Default quantum for RR between queues. */ 125#define G_RR_DEFAULT_BUDGET 0x00800000 126 127/* 128 * Per device descriptor, holding the Round Robin list of queues 129 * accessing the disk, a reference to the geom, and the timer. 130 */ 131struct g_rr_softc { 132 struct g_geom *sc_geom; 133 134 /* 135 * sc_active is the queue we are anticipating for. 136 * It is set only in gs_rr_next(), and possibly cleared 137 * only in gs_rr_next() or on a timeout. 138 * The active queue is never in the Round Robin list 139 * even if it has requests queued. 140 */ 141 struct g_rr_queue *sc_active; 142 struct callout sc_wait; /* timer for sc_active */ 143 144 struct g_rr_tailq sc_rr_tailq; /* the round-robin list */ 145 int sc_nqueues; /* number of queues */ 146 147 /* Statistics */ 148 int sc_in_flight; /* requests in the driver */ 149 150 LIST_ENTRY(g_rr_softc) sc_next; 151}; 152 153/* Descriptor for bounded values, min and max are constant. */ 154struct x_bound { 155 const int x_min; 156 int x_cur; 157 const int x_max; 158}; 159 160/* 161 * parameters, config and stats 162 */ 163struct g_rr_params { 164 int queues; /* total number of queues */ 165 int w_anticipate; /* anticipate writes */ 166 int bypass; /* bypass scheduling writes */ 167 168 int units; /* how many instances */ 169 /* sc_head is used for debugging */ 170 struct g_scheds sc_head; /* first scheduler instance */ 171 172 struct x_bound queue_depth; /* max parallel requests */ 173 struct x_bound wait_ms; /* wait time, milliseconds */ 174 struct x_bound quantum_ms; /* quantum size, milliseconds */ 175 struct x_bound quantum_kb; /* quantum size, Kb (1024 bytes) */ 176 177 /* statistics */ 178 int wait_hit; /* success in anticipation */ 179 int wait_miss; /* failure in anticipation */ 180}; 181 182/* 183 * Default parameters for the scheduler. The quantum sizes target 184 * a 80MB/s disk; if the hw is faster or slower the minimum of the 185 * two will have effect: the clients will still be isolated but 186 * the fairness may be limited. A complete solution would involve 187 * the on-line measurement of the actual disk throughput to derive 188 * these parameters. Or we may just choose to ignore service domain 189 * fairness and accept what can be achieved with time-only budgets. 190 */ 191static struct g_rr_params me = { 192 .sc_head = LIST_HEAD_INITIALIZER(&me.sc_head), 193 .w_anticipate = 1, 194 .queue_depth = { 1, 1, 50 }, 195 .wait_ms = { 1, 10, 30 }, 196 .quantum_ms = { 1, 100, 500 }, 197 .quantum_kb = { 16, 8192, 65536 }, 198}; 199 200struct g_rr_params *gs_rr_me = &me; 201 202SYSCTL_DECL(_kern_geom_sched); 203static SYSCTL_NODE(_kern_geom_sched, OID_AUTO, rr, CTLFLAG_RW, 0, 204 "GEOM_SCHED ROUND ROBIN stuff"); 205SYSCTL_INT(_kern_geom_sched_rr, OID_AUTO, units, CTLFLAG_RD, 206 &me.units, 0, "Scheduler instances"); 207SYSCTL_INT(_kern_geom_sched_rr, OID_AUTO, queues, CTLFLAG_RD, 208 &me.queues, 0, "Total rr queues"); 209SYSCTL_INT(_kern_geom_sched_rr, OID_AUTO, wait_ms, CTLFLAG_RW, 210 &me.wait_ms.x_cur, 0, "Wait time milliseconds"); 211SYSCTL_INT(_kern_geom_sched_rr, OID_AUTO, quantum_ms, CTLFLAG_RW, 212 &me.quantum_ms.x_cur, 0, "Quantum size milliseconds"); 213SYSCTL_INT(_kern_geom_sched_rr, OID_AUTO, bypass, CTLFLAG_RW, 214 &me.bypass, 0, "Bypass scheduler"); 215SYSCTL_INT(_kern_geom_sched_rr, OID_AUTO, w_anticipate, CTLFLAG_RW, 216 &me.w_anticipate, 0, "Do anticipation on writes"); 217SYSCTL_INT(_kern_geom_sched_rr, OID_AUTO, quantum_kb, CTLFLAG_RW, 218 &me.quantum_kb.x_cur, 0, "Quantum size Kbytes"); 219SYSCTL_INT(_kern_geom_sched_rr, OID_AUTO, queue_depth, CTLFLAG_RW, 220 &me.queue_depth.x_cur, 0, "Maximum simultaneous requests"); 221SYSCTL_INT(_kern_geom_sched_rr, OID_AUTO, wait_hit, CTLFLAG_RW, 222 &me.wait_hit, 0, "Hits in anticipation"); 223SYSCTL_INT(_kern_geom_sched_rr, OID_AUTO, wait_miss, CTLFLAG_RW, 224 &me.wait_miss, 0, "Misses in anticipation"); 225 226#ifdef DEBUG_QUEUES 227/* print the status of a queue */ 228static void 229gs_rr_dump_q(struct g_rr_queue *qp, int index) 230{ 231 int l = 0; 232 struct bio *bp; 233 234 TAILQ_FOREACH(bp, &(qp->q_bioq.queue), bio_queue) { 235 l++; 236 } 237 printf("--- rr queue %d %p status %d len %d ---\n", 238 index, qp, qp->q_status, l); 239} 240 241/* 242 * Dump the scheduler status when writing to this sysctl variable. 243 * XXX right now we only dump the status of the last instance created. 244 * not a severe issue because this is only for debugging 245 */ 246static int 247gs_rr_sysctl_status(SYSCTL_HANDLER_ARGS) 248{ 249 int error, val = 0; 250 struct g_rr_softc *sc; 251 252 error = sysctl_handle_int(oidp, &val, 0, req); 253 if (error || !req->newptr ) 254 return (error); 255 256 printf("called %s\n", __FUNCTION__); 257 258 LIST_FOREACH(sc, &me.sc_head, sc_next) { 259 int i, tot = 0; 260 printf("--- sc %p active %p nqueues %d " 261 "callout %d in_flight %d ---\n", 262 sc, sc->sc_active, sc->sc_nqueues, 263 callout_active(&sc->sc_wait), 264 sc->sc_in_flight); 265 for (i = 0; i < G_RR_HASH_SIZE; i++) { 266 struct g_rr_queue *qp; 267 LIST_FOREACH(qp, &sc->sc_hash[i], q_hash) { 268 gs_rr_dump_q(qp, tot); 269 tot++; 270 } 271 } 272 } 273 return (0); 274} 275 276SYSCTL_PROC(_kern_geom_sched_rr, OID_AUTO, status, 277 CTLTYPE_UINT | CTLFLAG_RW, 278 0, sizeof(int), gs_rr_sysctl_status, "I", "status"); 279 280#endif /* DEBUG_QUEUES */ 281 282/* 283 * Get a bounded value, optionally convert to a min of t_min ticks. 284 */ 285static int 286get_bounded(struct x_bound *v, int t_min) 287{ 288 int x; 289 290 x = v->x_cur; 291 if (x < v->x_min) 292 x = v->x_min; 293 else if (x > v->x_max) 294 x = v->x_max; 295 if (t_min) { 296 x = x * hz / 1000; /* convert to ticks */ 297 if (x < t_min) 298 x = t_min; 299 } 300 return x; 301} 302 303/* 304 * Get a reference to the queue for bp, using the generic 305 * classification mechanism. 306 */ 307static struct g_rr_queue * 308g_rr_queue_get(struct g_rr_softc *sc, struct bio *bp) 309{ 310 311 return (g_sched_get_class(sc->sc_geom, bp)); 312} 313 314static int 315g_rr_init_class(void *data, void *priv) 316{ 317 struct g_rr_softc *sc = data; 318 struct g_rr_queue *qp = priv; 319 320 bioq_init(&qp->q_bioq); 321 322 /* 323 * Set the initial parameters for the client: 324 * slice size in bytes and ticks, and wait ticks. 325 * Right now these are constant, but we could have 326 * autoconfiguration code to adjust the values based on 327 * the actual workload. 328 */ 329 qp->q_budget = 1024 * get_bounded(&me.quantum_kb, 0); 330 qp->q_slice_duration = get_bounded(&me.quantum_ms, 2); 331 qp->q_wait_ticks = get_bounded(&me.wait_ms, 2); 332 333 qp->q_sc = sc; /* link to the parent */ 334 qp->q_sc->sc_nqueues++; 335 me.queues++; 336 337 return (0); 338} 339 340/* 341 * Release a reference to the queue. 342 */ 343static void 344g_rr_queue_put(struct g_rr_queue *qp) 345{ 346 347 g_sched_put_class(qp->q_sc->sc_geom, qp); 348} 349 350static void 351g_rr_fini_class(void *data, void *priv) 352{ 353 struct g_rr_queue *qp = priv; 354 355 KASSERT(bioq_first(&qp->q_bioq) == NULL, 356 ("released nonempty queue")); 357 qp->q_sc->sc_nqueues--; 358 me.queues--; 359} 360 361static inline int 362g_rr_queue_expired(struct g_rr_queue *qp) 363{ 364 365 if (qp->q_service >= qp->q_budget) 366 return (1); 367 368 if ((qp->q_flags & G_FLAG_COMPLETED) && 369 ticks - qp->q_slice_end >= 0) 370 return (1); 371 372 return (0); 373} 374 375static inline int 376g_rr_should_anticipate(struct g_rr_queue *qp, struct bio *bp) 377{ 378 int wait = get_bounded(&me.wait_ms, 2); 379 380 if (!me.w_anticipate && (bp->bio_cmd == BIO_WRITE)) 381 return (0); 382 383 if (g_savg_valid(&qp->q_thinktime) && 384 g_savg_read(&qp->q_thinktime) > wait) 385 return (0); 386 387 if (g_savg_valid(&qp->q_seekdist) && 388 g_savg_read(&qp->q_seekdist) > 8192) 389 return (0); 390 391 return (1); 392} 393 394/* 395 * Called on a request arrival, timeout or completion. 396 * Try to serve a request among those queued. 397 */ 398static struct bio * 399g_rr_next(void *data, int force) 400{ 401 struct g_rr_softc *sc = data; 402 struct g_rr_queue *qp; 403 struct bio *bp, *next; 404 int expired; 405 406 qp = sc->sc_active; 407 if (me.bypass == 0 && !force) { 408 if (sc->sc_in_flight >= get_bounded(&me.queue_depth, 0)) 409 return (NULL); 410 411 /* Try with the queue under service first. */ 412 if (qp != NULL && qp->q_status != G_QUEUE_READY) { 413 /* 414 * Queue is anticipating, ignore request. 415 * We should check that we are not past 416 * the timeout, but in that case the timeout 417 * will fire immediately afterwards so we 418 * don't bother. 419 */ 420 return (NULL); 421 } 422 } else if (qp != NULL && qp->q_status != G_QUEUE_READY) { 423 g_rr_queue_put(qp); 424 sc->sc_active = qp = NULL; 425 } 426 427 /* 428 * No queue under service, look for the first in RR order. 429 * If we find it, select if as sc_active, clear service 430 * and record the end time of the slice. 431 */ 432 if (qp == NULL) { 433 qp = TAILQ_FIRST(&sc->sc_rr_tailq); 434 if (qp == NULL) 435 return (NULL); /* no queues at all, return */ 436 /* otherwise select the new queue for service. */ 437 TAILQ_REMOVE(&sc->sc_rr_tailq, qp, q_tailq); 438 sc->sc_active = qp; 439 qp->q_service = 0; 440 qp->q_flags &= ~G_FLAG_COMPLETED; 441 } 442 443 bp = bioq_takefirst(&qp->q_bioq); /* surely not NULL */ 444 qp->q_service += bp->bio_length; /* charge the service */ 445 446 /* 447 * The request at the head of the active queue is always 448 * dispatched, and gs_rr_next() will be called again 449 * immediately. 450 * We need to prepare for what to do next: 451 * 452 * 1. have we reached the end of the (time or service) slice ? 453 * If so, clear sc_active and possibly requeue the previous 454 * active queue if it has more requests pending; 455 * 2. do we have more requests in sc_active ? 456 * If yes, do not anticipate, as gs_rr_next() will run again; 457 * if no, decide whether or not to anticipate depending 458 * on read or writes (e.g., anticipate only on reads). 459 */ 460 expired = g_rr_queue_expired(qp); /* are we expired ? */ 461 next = bioq_first(&qp->q_bioq); /* do we have one more ? */ 462 if (expired) { 463 sc->sc_active = NULL; 464 /* Either requeue or release reference. */ 465 if (next != NULL) 466 TAILQ_INSERT_TAIL(&sc->sc_rr_tailq, qp, q_tailq); 467 else 468 g_rr_queue_put(qp); 469 } else if (next != NULL) { 470 qp->q_status = G_QUEUE_READY; 471 } else { 472 if (!force && g_rr_should_anticipate(qp, bp)) { 473 /* anticipate */ 474 qp->q_status = G_QUEUE_BUSY; 475 } else { 476 /* do not anticipate, release reference */ 477 g_rr_queue_put(qp); 478 sc->sc_active = NULL; 479 } 480 } 481 /* If sc_active != NULL, its q_status is always correct. */ 482 483 sc->sc_in_flight++; 484 485 return (bp); 486} 487 488static inline void 489g_rr_update_thinktime(struct g_rr_queue *qp) 490{ 491 int delta = ticks - qp->q_lastsub, wait = get_bounded(&me.wait_ms, 2); 492 493 if (qp->q_sc->sc_active != qp) 494 return; 495 496 qp->q_lastsub = ticks; 497 delta = (delta > 2 * wait) ? 2 * wait : delta; 498 if (qp->q_bionum > 7) 499 g_savg_add_sample(&qp->q_thinktime, delta); 500} 501 502static inline void 503g_rr_update_seekdist(struct g_rr_queue *qp, struct bio *bp) 504{ 505 off_t dist; 506 507 if (qp->q_lastoff > bp->bio_offset) 508 dist = qp->q_lastoff - bp->bio_offset; 509 else 510 dist = bp->bio_offset - qp->q_lastoff; 511 512 if (dist > (8192 * 8)) 513 dist = 8192 * 8; 514 515 qp->q_lastoff = bp->bio_offset + bp->bio_length; 516 517 if (qp->q_bionum > 7) 518 g_savg_add_sample(&qp->q_seekdist, dist); 519} 520 521/* 522 * Called when a real request for disk I/O arrives. 523 * Locate the queue associated with the client. 524 * If the queue is the one we are anticipating for, reset its timeout; 525 * if the queue is not in the round robin list, insert it in the list. 526 * On any error, do not queue the request and return -1, the caller 527 * will take care of this request. 528 */ 529static int 530g_rr_start(void *data, struct bio *bp) 531{ 532 struct g_rr_softc *sc = data; 533 struct g_rr_queue *qp; 534 535 if (me.bypass) 536 return (-1); /* bypass the scheduler */ 537 538 /* Get the queue for the request. */ 539 qp = g_rr_queue_get(sc, bp); 540 if (qp == NULL) 541 return (-1); /* allocation failed, tell upstream */ 542 543 if (bioq_first(&qp->q_bioq) == NULL) { 544 /* 545 * We are inserting into an empty queue. 546 * Reset its state if it is sc_active, 547 * otherwise insert it in the RR list. 548 */ 549 if (qp == sc->sc_active) { 550 qp->q_status = G_QUEUE_READY; 551 callout_stop(&sc->sc_wait); 552 } else { 553 g_sched_priv_ref(qp); 554 TAILQ_INSERT_TAIL(&sc->sc_rr_tailq, qp, q_tailq); 555 } 556 } 557 558 qp->q_bionum = 1 + qp->q_bionum - (qp->q_bionum >> 3); 559 560 g_rr_update_thinktime(qp); 561 g_rr_update_seekdist(qp, bp); 562 563 /* Inherit the reference returned by g_rr_queue_get(). */ 564 bp->bio_caller1 = qp; 565 bioq_disksort(&qp->q_bioq, bp); 566 567 return (0); 568} 569 570/* 571 * Callout executed when a queue times out anticipating a new request. 572 */ 573static void 574g_rr_wait_timeout(void *data) 575{ 576 struct g_rr_softc *sc = data; 577 struct g_geom *geom = sc->sc_geom; 578 579 g_sched_lock(geom); 580 /* 581 * We can race with other events, so check if 582 * sc_active is still valid. 583 */ 584 if (sc->sc_active != NULL) { 585 /* Release the reference to the queue. */ 586 g_rr_queue_put(sc->sc_active); 587 sc->sc_active = NULL; 588 me.wait_hit--; 589 me.wait_miss++; /* record the miss */ 590 } 591 g_sched_dispatch(geom); 592 g_sched_unlock(geom); 593} 594 595/* 596 * Module glue: allocate descriptor, initialize its fields. 597 */ 598static void * 599g_rr_init(struct g_geom *geom) 600{ 601 struct g_rr_softc *sc; 602 603 /* XXX check whether we can sleep */ 604 sc = malloc(sizeof *sc, M_GEOM_SCHED, M_NOWAIT | M_ZERO); 605 sc->sc_geom = geom; 606 TAILQ_INIT(&sc->sc_rr_tailq); 607 callout_init(&sc->sc_wait, 1); 608 LIST_INSERT_HEAD(&me.sc_head, sc, sc_next); 609 me.units++; 610 611 return (sc); 612} 613 614/* 615 * Module glue -- drain the callout structure, destroy the 616 * hash table and its element, and free the descriptor. 617 */ 618static void 619g_rr_fini(void *data) 620{ 621 struct g_rr_softc *sc = data; 622 623 callout_drain(&sc->sc_wait); 624 KASSERT(sc->sc_active == NULL, ("still a queue under service")); 625 KASSERT(TAILQ_EMPTY(&sc->sc_rr_tailq), ("still scheduled queues")); 626 627 LIST_REMOVE(sc, sc_next); 628 me.units--; 629 free(sc, M_GEOM_SCHED); 630} 631 632/* 633 * Called when the request under service terminates. 634 * Start the anticipation timer if needed. 635 */ 636static void 637g_rr_done(void *data, struct bio *bp) 638{ 639 struct g_rr_softc *sc = data; 640 struct g_rr_queue *qp; 641 642 sc->sc_in_flight--; 643 644 qp = bp->bio_caller1; 645 646 /* 647 * When the first request for this queue completes, update the 648 * duration and end of the slice. We do not do it when the 649 * slice starts to avoid charging to the queue the time for 650 * the first seek. 651 */ 652 if (!(qp->q_flags & G_FLAG_COMPLETED)) { 653 qp->q_flags |= G_FLAG_COMPLETED; 654 /* 655 * recompute the slice duration, in case we want 656 * to make it adaptive. This is not used right now. 657 * XXX should we do the same for q_quantum and q_wait_ticks ? 658 */ 659 qp->q_slice_duration = get_bounded(&me.quantum_ms, 2); 660 qp->q_slice_end = ticks + qp->q_slice_duration; 661 } 662 663 if (qp == sc->sc_active && qp->q_status == G_QUEUE_BUSY) { 664 /* The queue is trying anticipation, start the timer. */ 665 qp->q_status = G_QUEUE_IDLING; 666 /* may make this adaptive */ 667 qp->q_wait_ticks = get_bounded(&me.wait_ms, 2); 668 me.wait_hit++; 669 callout_reset(&sc->sc_wait, qp->q_wait_ticks, 670 g_rr_wait_timeout, sc); 671 } else 672 g_sched_dispatch(sc->sc_geom); 673 674 /* Release a reference to the queue. */ 675 g_rr_queue_put(qp); 676} 677 678static void 679g_rr_dumpconf(struct sbuf *sb, const char *indent, struct g_geom *gp, 680 struct g_consumer *cp, struct g_provider *pp) 681{ 682 if (indent == NULL) { /* plaintext */ 683 sbuf_printf(sb, " units %d queues %d", 684 me.units, me.queues); 685 } 686} 687 688static struct g_gsched g_rr = { 689 .gs_name = "rr", 690 .gs_priv_size = sizeof(struct g_rr_queue), 691 .gs_init = g_rr_init, 692 .gs_fini = g_rr_fini, 693 .gs_start = g_rr_start, 694 .gs_done = g_rr_done, 695 .gs_next = g_rr_next, 696 .gs_dumpconf = g_rr_dumpconf, 697 .gs_init_class = g_rr_init_class, 698 .gs_fini_class = g_rr_fini_class, 699}; 700 701DECLARE_GSCHED_MODULE(rr, &g_rr); 702