01addf6889ea04c0f5331061b4b671a5f6fd4869
[dragonfly.git] / sys / dsched / fq / dsched_fq_core.c
1 /*
2  * Copyright (c) 2009, 2010 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Alex Hornung <ahornung@gmail.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
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
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 #include <sys/param.h>
35 #include <sys/systm.h>
36 #include <sys/kernel.h>
37 #include <sys/proc.h>
38 #include <sys/sysctl.h>
39 #include <sys/buf.h>
40 #include <sys/conf.h>
41 #include <sys/diskslice.h>
42 #include <sys/disk.h>
43 #include <machine/atomic.h>
44 #include <sys/malloc.h>
45 #include <sys/thread.h>
46 #include <sys/thread2.h>
47 #include <sys/sysctl.h>
48 #include <sys/spinlock2.h>
49 #include <machine/md_var.h>
50 #include <sys/ctype.h>
51 #include <sys/syslog.h>
52 #include <sys/device.h>
53 #include <sys/msgport.h>
54 #include <sys/msgport2.h>
55 #include <sys/buf2.h>
56 #include <sys/dsched.h>
57 #include <machine/varargs.h>
58 #include <machine/param.h>
59
60 #include <dsched/fq/dsched_fq.h>
61
62 MALLOC_DECLARE(M_DSCHEDFQ);
63
64 static int      dsched_fq_version_maj = 0;
65 static int      dsched_fq_version_min = 8;
66
67 struct dsched_fq_stats  fq_stats;
68
69 struct objcache_malloc_args fq_disk_ctx_malloc_args = {
70         sizeof(struct fq_disk_ctx), M_DSCHEDFQ };
71 struct objcache_malloc_args fq_thread_io_malloc_args = {
72         sizeof(struct fq_thread_io), M_DSCHEDFQ };
73 struct objcache_malloc_args fq_thread_ctx_malloc_args = {
74         sizeof(struct fq_thread_ctx), M_DSCHEDFQ };
75
76 static struct objcache  *fq_diskctx_cache;
77 static struct objcache  *fq_tdctx_cache;
78 static struct objcache  *fq_tdio_cache;
79
80 TAILQ_HEAD(, fq_thread_ctx)     dsched_tdctx_list =
81                 TAILQ_HEAD_INITIALIZER(dsched_tdctx_list);
82
83 struct lock     fq_tdctx_lock;
84
85 extern struct dsched_policy dsched_fq_policy;
86
87 void
88 fq_disk_ctx_ref(struct fq_disk_ctx *diskctx)
89 {
90         int refcount;
91
92         refcount = atomic_fetchadd_int(&diskctx->refcount, 1);
93
94         KKASSERT(refcount >= 0);
95 }
96
97 void
98 fq_thread_io_ref(struct fq_thread_io *tdio)
99 {
100         int refcount;
101
102         refcount = atomic_fetchadd_int(&tdio->refcount, 1);
103
104         KKASSERT(refcount >= 0);
105 }
106
107 void
108 fq_thread_ctx_ref(struct fq_thread_ctx *tdctx)
109 {
110         int refcount;
111
112         refcount = atomic_fetchadd_int(&tdctx->refcount, 1);
113
114         KKASSERT(refcount >= 0);
115 }
116
117 void
118 fq_disk_ctx_unref(struct fq_disk_ctx *diskctx)
119 {
120         struct fq_thread_io     *tdio, *tdio2;
121         int refcount;
122
123         refcount = atomic_fetchadd_int(&diskctx->refcount, -1);
124
125
126         KKASSERT(refcount >= 0 || refcount <= -0x400);
127
128         if (refcount == 1) {
129                 atomic_subtract_int(&diskctx->refcount, 0x400); /* mark as: in destruction */
130 #if 1
131                 kprintf("diskctx (%p) destruction started, trace:\n", diskctx);
132                 print_backtrace(4);
133 #endif
134                 lockmgr(&diskctx->lock, LK_EXCLUSIVE);
135                 TAILQ_FOREACH_MUTABLE(tdio, &diskctx->fq_tdio_list, dlink, tdio2) {
136                         TAILQ_REMOVE(&diskctx->fq_tdio_list, tdio, dlink);
137                         tdio->flags &= ~FQ_LINKED_DISK_CTX;
138                         fq_thread_io_unref(tdio);
139                 }
140                 lockmgr(&diskctx->lock, LK_RELEASE);
141
142                 objcache_put(fq_diskctx_cache, diskctx);
143                 atomic_subtract_int(&fq_stats.diskctx_allocations, 1);
144         }
145 }
146
147 void
148 fq_thread_io_unref(struct fq_thread_io *tdio)
149 {
150         struct fq_thread_ctx    *tdctx;
151         struct fq_disk_ctx      *diskctx;
152         int refcount;
153
154         refcount = atomic_fetchadd_int(&tdio->refcount, -1);
155
156         KKASSERT(refcount >= 0 || refcount <= -0x400);
157
158         if (refcount == 1) {
159                 atomic_subtract_int(&tdio->refcount, 0x400); /* mark as: in destruction */
160 #if 0
161                 kprintf("tdio (%p) destruction started, trace:\n", tdio);
162                 print_backtrace(8);
163 #endif
164                 diskctx = tdio->diskctx;
165                 KKASSERT(diskctx != NULL);
166                 KKASSERT(tdio->qlength == 0);
167
168                 if (tdio->flags & FQ_LINKED_DISK_CTX) {
169                         lockmgr(&diskctx->lock, LK_EXCLUSIVE);
170
171                         TAILQ_REMOVE(&diskctx->fq_tdio_list, tdio, dlink);
172                         tdio->flags &= ~FQ_LINKED_DISK_CTX;
173
174                         lockmgr(&diskctx->lock, LK_RELEASE);
175                 }
176
177                 if (tdio->flags & FQ_LINKED_THREAD_CTX) {
178                         tdctx = tdio->tdctx;
179                         KKASSERT(tdctx != NULL);
180
181                         spin_lock_wr(&tdctx->lock);
182
183                         TAILQ_REMOVE(&tdctx->fq_tdio_list, tdio, link);
184                         tdio->flags &= ~FQ_LINKED_THREAD_CTX;
185
186                         spin_unlock_wr(&tdctx->lock);
187                 }
188
189                 objcache_put(fq_tdio_cache, tdio);
190                 atomic_subtract_int(&fq_stats.tdio_allocations, 1);
191 #if 0
192                 fq_disk_ctx_unref(diskctx);
193 #endif
194         }
195 }
196
197 void
198 fq_thread_ctx_unref(struct fq_thread_ctx *tdctx)
199 {
200         struct fq_thread_io     *tdio, *tdio2;
201         int refcount;
202
203         refcount = atomic_fetchadd_int(&tdctx->refcount, -1);
204
205         KKASSERT(refcount >= 0 || refcount <= -0x400);
206
207         if (refcount == 1) {
208                 atomic_subtract_int(&tdctx->refcount, 0x400); /* mark as: in destruction */
209 #if 0
210                 kprintf("tdctx (%p) destruction started, trace:\n", tdctx);
211                 print_backtrace(8);
212 #endif
213                 FQ_GLOBAL_THREAD_CTX_LOCK();
214
215                 TAILQ_FOREACH_MUTABLE(tdio, &tdctx->fq_tdio_list, link, tdio2) {
216                         TAILQ_REMOVE(&tdctx->fq_tdio_list, tdio, link);
217                         tdio->flags &= ~FQ_LINKED_THREAD_CTX;
218                         fq_thread_io_unref(tdio);
219                 }
220                 TAILQ_REMOVE(&dsched_tdctx_list, tdctx, link);
221
222                 FQ_GLOBAL_THREAD_CTX_UNLOCK();
223
224                 objcache_put(fq_tdctx_cache, tdctx);
225                 atomic_subtract_int(&fq_stats.tdctx_allocations, 1);
226         }
227 }
228
229
230 struct fq_thread_io *
231 fq_thread_io_alloc(struct disk *dp, struct fq_thread_ctx *tdctx)
232 {
233         struct fq_thread_io     *tdio;
234 #if 0
235         fq_disk_ctx_ref(dsched_get_disk_priv(dp));
236 #endif
237         tdio = objcache_get(fq_tdio_cache, M_WAITOK);
238         bzero(tdio, sizeof(struct fq_thread_io));
239
240         /* XXX: maybe we do need another ref for the disk list for tdio */
241         fq_thread_io_ref(tdio);
242
243         FQ_THREAD_IO_LOCKINIT(tdio);
244         tdio->dp = dp;
245
246         tdio->diskctx = dsched_get_disk_priv(dp);
247         TAILQ_INIT(&tdio->queue);
248
249         TAILQ_INSERT_TAIL(&tdio->diskctx->fq_tdio_list, tdio, dlink);
250         tdio->flags |= FQ_LINKED_DISK_CTX;
251
252         if (tdctx) {
253                 tdio->tdctx = tdctx;
254                 tdio->p = tdctx->p;
255
256                 /* Put the tdio in the tdctx list */
257                 FQ_THREAD_CTX_LOCK(tdctx);
258                 TAILQ_INSERT_TAIL(&tdctx->fq_tdio_list, tdio, link);
259                 FQ_THREAD_CTX_UNLOCK(tdctx);
260                 tdio->flags |= FQ_LINKED_THREAD_CTX;
261         }
262
263         atomic_add_int(&fq_stats.tdio_allocations, 1);
264         return tdio;
265 }
266
267
268 struct fq_disk_ctx *
269 fq_disk_ctx_alloc(struct disk *dp)
270 {
271         struct fq_disk_ctx *diskctx;
272
273         diskctx = objcache_get(fq_diskctx_cache, M_WAITOK);
274         bzero(diskctx, sizeof(struct fq_disk_ctx));
275         fq_disk_ctx_ref(diskctx);
276         diskctx->dp = dp;
277         diskctx->avg_rq_time = 0;
278         diskctx->incomplete_tp = 0;
279         FQ_DISK_CTX_LOCKINIT(diskctx);
280         TAILQ_INIT(&diskctx->fq_tdio_list);
281
282         atomic_add_int(&fq_stats.diskctx_allocations, 1);
283         return diskctx;
284 }
285
286
287 struct fq_thread_ctx *
288 fq_thread_ctx_alloc(struct proc *p)
289 {
290         struct fq_thread_ctx    *tdctx;
291         struct fq_thread_io     *tdio;
292         struct disk     *dp = NULL;
293
294         tdctx = objcache_get(fq_tdctx_cache, M_WAITOK);
295         bzero(tdctx, sizeof(struct fq_thread_ctx));
296         fq_thread_ctx_ref(tdctx);
297 #if 0
298         kprintf("fq_thread_ctx_alloc, new tdctx = %p\n", tdctx);
299 #endif
300         FQ_THREAD_CTX_LOCKINIT(tdctx);
301         TAILQ_INIT(&tdctx->fq_tdio_list);
302         tdctx->p = p;
303
304         while ((dp = dsched_disk_enumerate(dp, &dsched_fq_policy))) {
305                 tdio = fq_thread_io_alloc(dp, tdctx);
306 #if 0
307                 fq_thread_io_ref(tdio);
308 #endif
309         }
310
311         FQ_GLOBAL_THREAD_CTX_LOCK();
312         TAILQ_INSERT_TAIL(&dsched_tdctx_list, tdctx, link);
313         FQ_GLOBAL_THREAD_CTX_UNLOCK();
314
315         atomic_add_int(&fq_stats.tdctx_allocations, 1);
316         return tdctx;
317 }
318
319
320 void
321 fq_dispatcher(struct fq_disk_ctx *diskctx)
322 {
323         struct fq_thread_ctx    *tdctx;
324         struct fq_thread_io     *tdio, *tdio2;
325         struct bio *bio, *bio2;
326         int idle;
327
328         /*
329          * We need to manually assign an tdio to the tdctx of this thread
330          * since it isn't assigned one during fq_prepare, as the disk
331          * is not set up yet.
332          */
333         tdctx = dsched_get_thread_priv(curthread);
334         KKASSERT(tdctx != NULL);
335
336         tdio = fq_thread_io_alloc(diskctx->dp, tdctx);
337 #if 0
338         fq_thread_io_ref(tdio);
339 #endif
340
341         FQ_DISK_CTX_LOCK(diskctx);
342         for(;;) {
343                 idle = 0;
344                 /* sleep ~60 ms */
345                 if ((lksleep(diskctx, &diskctx->lock, 0, "fq_dispatcher", hz/15) == 0)) {
346                         /*
347                          * We've been woken up; this either means that we are
348                          * supposed to die away nicely or that the disk is idle.
349                          */
350
351                         if (__predict_false(diskctx->die == 1)) {
352                                 /* If we are supposed to die, drain all queues */
353                                 fq_drain(diskctx, FQ_DRAIN_FLUSH);
354
355                                 /* Now we can safely unlock and exit */
356                                 FQ_DISK_CTX_UNLOCK(diskctx);
357                                 kprintf("fq_dispatcher is peacefully dying\n");
358                                 lwkt_exit();
359                                 /* NOTREACHED */
360                         }
361
362                         /*
363                          * We have been awakened because the disk is idle.
364                          * So let's get ready to dispatch some extra bios.
365                          */
366                         idle = 1;
367                 }
368
369                 /* Maybe the disk is idle and we just didn't get the wakeup */
370                 if (idle == 0)
371                         idle = diskctx->idle;
372
373                 /*
374                  * XXX: further room for improvements here. It would be better
375                  *      to dispatch a few requests from each tdio as to ensure
376                  *      real fairness.
377                  */
378                 TAILQ_FOREACH_MUTABLE(tdio, &diskctx->fq_tdio_list, dlink, tdio2) {
379                         if (tdio->qlength == 0)
380                                 continue;
381
382                         FQ_THREAD_IO_LOCK(tdio);
383                         if (atomic_cmpset_int(&tdio->rebalance, 1, 0))
384                                 fq_balance_self(tdio);
385                         /*
386                          * XXX: why 5 extra? should probably be dynamic,
387                          *      relying on information on latency.
388                          */
389                         if ((tdio->max_tp > 0) && idle &&
390                             (tdio->issued >= tdio->max_tp)) {
391                                 tdio->max_tp += 5;
392                         }
393
394                         TAILQ_FOREACH_MUTABLE(bio, &tdio->queue, link, bio2) {
395                                 if (atomic_cmpset_int(&tdio->rebalance, 1, 0))
396                                         fq_balance_self(tdio);
397                                 if ((tdio->max_tp > 0) &&
398                                     ((tdio->issued >= tdio->max_tp)))
399                                         break;
400
401                                 TAILQ_REMOVE(&tdio->queue, bio, link);
402                                 --tdio->qlength;
403
404                                 /*
405                                  * beware that we do have an tdio reference
406                                  * from the queueing
407                                  */
408                                 fq_dispatch(diskctx, bio, tdio);
409                         }
410                         FQ_THREAD_IO_UNLOCK(tdio);
411
412                 }
413         }
414 }
415
416 void
417 fq_balance_thread(struct fq_disk_ctx *diskctx)
418 {
419         struct  fq_thread_io    *tdio, *tdio2;
420         struct timeval tv, old_tv;
421         int64_t total_budget, product;
422         int64_t budget[FQ_PRIO_MAX+1];
423         int     n, i, sum, total_disk_time;
424         int     lost_bits;
425
426         FQ_DISK_CTX_LOCK(diskctx);
427
428         getmicrotime(&diskctx->start_interval);
429
430         for (;;) {
431                 /* sleep ~1s */
432                 if ((lksleep(curthread, &diskctx->lock, 0, "fq_balancer", hz/2) == 0)) {
433                         if (__predict_false(diskctx->die)) {
434                                 FQ_DISK_CTX_UNLOCK(diskctx);
435                                 lwkt_exit();
436                         }
437                 }
438
439                 bzero(budget, sizeof(budget));
440                 total_budget = 0;
441                 n = 0;
442
443                 old_tv = diskctx->start_interval;
444                 getmicrotime(&tv);
445
446                 total_disk_time = (int)(1000000*((tv.tv_sec - old_tv.tv_sec)) +
447                     (tv.tv_usec - old_tv.tv_usec));
448
449                 if (total_disk_time == 0)
450                         total_disk_time = 1;
451
452                 dsched_debug(LOG_INFO, "total_disk_time = %d\n", total_disk_time);
453
454                 diskctx->start_interval = tv;
455
456                 diskctx->disk_busy = (100*(total_disk_time - diskctx->idle_time)) / total_disk_time;
457                 if (diskctx->disk_busy < 0)
458                         diskctx->disk_busy = 0;
459
460                 diskctx->idle_time = 0;
461                 lost_bits = 0;
462
463                 TAILQ_FOREACH_MUTABLE(tdio, &diskctx->fq_tdio_list, dlink, tdio2) {
464                         tdio->interval_avg_latency = tdio->avg_latency;
465                         tdio->interval_transactions = tdio->transactions;
466                         if (tdio->interval_transactions > 0) {
467                                 product = (int64_t)tdio->interval_avg_latency *
468                                     tdio->interval_transactions;
469                                 product >>= lost_bits;
470                                 while(total_budget >= INT64_MAX - product) {
471                                         ++lost_bits;
472                                         product >>= 1;
473                                         total_budget >>= 1;
474                                 }
475                                 total_budget += product;
476                                 ++budget[(tdio->p) ? tdio->p->p_ionice : 0];
477                                 KKASSERT(total_budget >= 0);
478                                 dsched_debug(LOG_INFO,
479                                     "%d) avg_latency = %d, transactions = %d, ioprio = %d\n",
480                                     n, tdio->interval_avg_latency, tdio->interval_transactions,
481                                     (tdio->p) ? tdio->p->p_ionice : 0);
482                                 ++n;
483                         } else {
484                                 tdio->max_tp = 0;
485                         }
486                         tdio->rebalance = 0;
487                         tdio->transactions = 0;
488                         tdio->avg_latency = 0;
489                         tdio->issued = 0;
490                 }
491
492                 dsched_debug(LOG_INFO, "%d procs competing for disk\n"
493                     "total_budget = %jd (lost bits = %d)\n"
494                     "incomplete tp = %d\n", n, (intmax_t)total_budget,
495                     lost_bits, diskctx->incomplete_tp);
496
497                 if (n == 0)
498                         continue;
499
500                 sum = 0;
501
502                 for (i = 0; i < FQ_PRIO_MAX+1; i++) {
503                         if (budget[i] == 0)
504                                 continue;
505                         sum += (FQ_PRIO_BIAS+i)*budget[i];
506                 }
507
508                 if (sum == 0)
509                         sum = 1;
510
511                 dsched_debug(LOG_INFO, "sum = %d\n", sum);
512
513                 for (i = 0; i < FQ_PRIO_MAX+1; i++) {
514                         if (budget[i] == 0)
515                                 continue;
516
517                         /*
518                          * XXX: if we still overflow here, we really need to switch to
519                          *      some more advanced mechanism such as compound int128 or
520                          *      storing the lost bits so they can be used in the
521                          *      fq_balance_self.
522                          */
523                         diskctx->budgetpb[i] = ((FQ_PRIO_BIAS+i)*total_budget/sum) << lost_bits;
524                         KKASSERT(diskctx->budgetpb[i] >= 0);
525                 }
526
527                 dsched_debug(4, "disk is %d%% busy\n", diskctx->disk_busy);
528                 TAILQ_FOREACH(tdio, &diskctx->fq_tdio_list, dlink) {
529                         tdio->rebalance = 1;
530                 }
531
532                 diskctx->prev_full = diskctx->last_full;
533                 diskctx->last_full = (diskctx->disk_busy >= 90)?1:0;
534         }
535 }
536
537
538 /*
539  * fq_balance_self should be called from all sorts of dispatchers. It basically
540  * offloads some of the heavier calculations on throttling onto the process that
541  * wants to do I/O instead of doing it in the fq_balance thread.
542  * - should be called with diskctx lock held
543  */
544 void
545 fq_balance_self(struct fq_thread_io *tdio) {
546         struct fq_disk_ctx *diskctx;
547
548         int64_t budget, used_budget;
549         int64_t avg_latency;
550         int64_t transactions;
551
552         transactions = (int64_t)tdio->interval_transactions;
553         avg_latency = (int64_t)tdio->interval_avg_latency;
554         diskctx = tdio->diskctx;
555
556 #if 0
557         /* XXX: do we really require the lock? */
558         FQ_DISK_CTX_LOCK_ASSERT(diskctx);
559 #endif
560
561         used_budget = ((int64_t)avg_latency * transactions);
562         budget = diskctx->budgetpb[(tdio->p) ? tdio->p->p_ionice : 0];
563
564         if (used_budget > 0) {
565                 dsched_debug(LOG_INFO,
566                     "info: used_budget = %jd, budget = %jd\n",
567                     (intmax_t)used_budget, budget);
568         }
569
570         if ((used_budget > budget) && (diskctx->disk_busy >= 90)) {
571                 KKASSERT(avg_latency != 0);
572
573                 tdio->max_tp = budget/(avg_latency);
574                 atomic_add_int(&fq_stats.procs_limited, 1);
575
576                 dsched_debug(LOG_INFO,
577                     "rate limited to %d transactions\n", tdio->max_tp);
578
579         } else if (((used_budget*2 < budget) || (diskctx->disk_busy < 80)) &&
580             (!diskctx->prev_full && !diskctx->last_full)) {
581                 tdio->max_tp = 0;
582         }
583 }
584
585
586 static int
587 do_fqstats(SYSCTL_HANDLER_ARGS)
588 {
589         return (sysctl_handle_opaque(oidp, &fq_stats, sizeof(struct dsched_fq_stats), req));
590 }
591
592
593 SYSCTL_PROC(_kern, OID_AUTO, fq_stats, CTLTYPE_OPAQUE|CTLFLAG_RD,
594     0, sizeof(struct dsched_fq_stats), do_fqstats, "fq_stats",
595     "dsched_fq statistics");
596
597
598 static void
599 fq_init(void)
600 {
601
602 }
603
604 static void
605 fq_uninit(void)
606 {
607
608 }
609
610 static void
611 fq_earlyinit(void)
612 {
613         fq_tdio_cache = objcache_create("fq-tdio-cache", 0, 0,
614                                            NULL, NULL, NULL,
615                                            objcache_malloc_alloc,
616                                            objcache_malloc_free,
617                                            &fq_thread_io_malloc_args );
618
619         fq_tdctx_cache = objcache_create("fq-tdctx-cache", 0, 0,
620                                            NULL, NULL, NULL,
621                                            objcache_malloc_alloc,
622                                            objcache_malloc_free,
623                                            &fq_thread_ctx_malloc_args );
624
625         FQ_GLOBAL_THREAD_CTX_LOCKINIT();
626
627         fq_diskctx_cache = objcache_create("fq-diskctx-cache", 0, 0,
628                                            NULL, NULL, NULL,
629                                            objcache_malloc_alloc,
630                                            objcache_malloc_free,
631                                            &fq_disk_ctx_malloc_args );
632
633         bzero(&fq_stats, sizeof(struct dsched_fq_stats));
634
635         dsched_register(&dsched_fq_policy);
636
637         kprintf("FQ scheduler policy version %d.%d loaded\n",
638             dsched_fq_version_maj, dsched_fq_version_min);
639 }
640
641 static void
642 fq_earlyuninit(void)
643 {
644         return;
645 }
646
647 SYSINIT(fq_register, SI_SUB_PRE_DRIVERS, SI_ORDER_ANY, fq_init, NULL);
648 SYSUNINIT(fq_register, SI_SUB_PRE_DRIVERS, SI_ORDER_FIRST, fq_uninit, NULL);
649
650 SYSINIT(fq_early, SI_SUB_CREATE_INIT-1, SI_ORDER_FIRST, fq_earlyinit, NULL);
651 SYSUNINIT(fq_early, SI_SUB_CREATE_INIT-1, SI_ORDER_ANY, fq_earlyuninit, NULL);