]> git.saurik.com Git - apple/xnu.git/blob - osfmk/kern/sched_multiq.c
xnu-3789.21.4.tar.gz
[apple/xnu.git] / osfmk / kern / sched_multiq.c
1 /*
2 * Copyright (c) 2013 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29 #include <mach/mach_types.h>
30 #include <mach/machine.h>
31
32 #include <machine/machine_routines.h>
33 #include <machine/sched_param.h>
34 #include <machine/machine_cpu.h>
35
36 #include <kern/kern_types.h>
37 #include <kern/debug.h>
38 #include <kern/mach_param.h>
39 #include <kern/machine.h>
40 #include <kern/misc_protos.h>
41 #include <kern/processor.h>
42 #include <kern/queue.h>
43 #include <kern/sched.h>
44 #include <kern/sched_prim.h>
45 #include <kern/task.h>
46 #include <kern/thread.h>
47
48 #include <sys/kdebug.h>
49
50 /*
51 * Theory Statement
52 *
53 * How does the task scheduler work?
54 *
55 * It schedules threads across a few levels.
56 *
57 * RT threads are dealt with above us
58 * Bound threads go into the per-processor runq
59 * Non-bound threads are linked on their task's sched_group's runq
60 * sched_groups' sched_entries are linked on the pset's runq
61 *
62 * TODO: make this explicit - bound threads should have a different enqueue fxn
63 *
64 * When we choose a new thread, we will decide whether to look at the bound runqueue, the global runqueue
65 * or the current group's runqueue, then dequeue the next thread in that runqueue.
66 *
67 * We then manipulate the sched_entries to reflect the invariant that:
68 * Each non-empty priority level in a group's runq is represented by one sched_entry enqueued in the global
69 * runqueue.
70 *
71 * A sched_entry represents a chance at running - for each priority in each task, there is one chance of getting
72 * to run. This reduces the excess contention bonus given to processes which have work spread among many threads
73 * as compared to processes which do the same amount of work under fewer threads.
74 *
75 * NOTE: Currently, the multiq scheduler only supports one pset.
76 *
77 * NOTE ABOUT thread->sched_pri:
78 *
79 * It can change after enqueue - it's changed without pset lock but with thread lock if thread->runq is 0.
80 * Therefore we can only depend on it not changing during the enqueue and remove path, not the dequeue.
81 *
82 * TODO: Future features:
83 *
84 * Decouple the task priority from the sched_entry priority, allowing for:
85 * fast task priority change without having to iterate and re-dispatch all threads in the task.
86 * i.e. task-wide priority, task-wide boosting
87 * fancier group decay features
88 *
89 * Group (or task) decay:
90 * Decay is used for a few different things:
91 * Prioritizing latency-needing threads over throughput-needing threads for time-to-running
92 * Balancing work between threads in a process
93 * Balancing work done at the same priority between different processes
94 * Recovering from priority inversions between two threads in the same process
95 * Recovering from priority inversions between two threads in different processes
96 * Simulating a proportional share scheduler by allowing lower priority threads
97 * to run for a certain percentage of the time
98 *
99 * Task decay lets us separately address the 'same process' and 'different process' needs,
100 * which will allow us to make smarter tradeoffs in different cases.
101 * For example, we could resolve priority inversion in the same process by reordering threads without dropping the
102 * process below low priority threads in other processes.
103 *
104 * One lock to rule them all (or at least all the runqueues) instead of the pset locks
105 *
106 * Shrink sched_entry size to the size of a queue_chain_t by inferring priority, group, and perhaps runq field.
107 * The entries array is 5K currently so it'd be really great to reduce.
108 * One way to get sched_group below 4K without a new runq structure would be to remove the extra queues above realtime.
109 *
110 * When preempting a processor, store a flag saying if the preemption
111 * was from a thread in the same group or different group,
112 * and tell choose_thread about it.
113 *
114 * When choosing a processor, bias towards those running in the same
115 * group as I am running (at the same priority, or within a certain band?).
116 *
117 * Decide if we need to support psets.
118 * Decide how to support psets - do we need duplicate entries for each pset,
119 * or can we get away with putting the entry in either one or the other pset?
120 *
121 * Consider the right way to handle runq count - I don't want to iterate groups.
122 * Perhaps keep a global counter.
123 * Alternate option - remove it from choose_processor. It doesn't add much value
124 * now that we have global runq.
125 *
126 * Need a better way of finding group to target instead of looking at current_task.
127 * Perhaps choose_thread could pass in the current thread?
128 *
129 * Consider unifying runq copy-pastes.
130 *
131 * Thoughts on having a group central quantum bucket:
132 *
133 * I see two algorithms to decide quanta:
134 * A) Hand off only when switching thread to thread in the same group
135 * B) Allocate and return quanta to the group's pool
136 *
137 * Issues:
138 * If a task blocks completely, should it come back with the leftover quanta
139 * or brand new quanta?
140 *
141 * Should I put a flag saying zero out a quanta you grab when youre dispatched'?
142 *
143 * Resolution:
144 * Handing off quanta between threads will help with jumping around in the current task
145 * but will not help when a thread from a different task is involved.
146 * Need an algorithm that works with round robin-ing between threads in different tasks
147 *
148 * But wait - round robining can only be triggered by quantum expire or blocking.
149 * We need something that works with preemption or yielding - that's the more interesting idea.
150 *
151 * Existing algorithm - preemption doesn't re-set quantum, puts thread on head of runq.
152 * Blocking or quantum expiration does re-set quantum, puts thread on tail of runq.
153 *
154 * New algorithm -
155 * Hand off quanta when hopping between threads with same sched_group
156 * Even if thread was blocked it uses last thread remaining quanta when it starts.
157 *
158 * If we use the only cycle entry at quantum algorithm, then the quantum pool starts getting
159 * interesting.
160 *
161 * A thought - perhaps the handoff approach doesn't work so well in the presence of
162 * non-handoff wakeups i.e. wake other thread then wait then block - doesn't mean that
163 * woken thread will be what I switch to - other processor may have stolen it.
164 * What do we do there?
165 *
166 * Conclusions:
167 * We currently don't know of a scenario where quantum buckets on the task is beneficial.
168 * We will instead handoff quantum between threads in the task, and keep quantum
169 * on the preempted thread if it's preempted by something outside the task.
170 *
171 */
172
173 #if DEBUG || DEVELOPMENT
174 #define MULTIQ_SANITY_CHECK
175 #endif
176
177 typedef struct sched_entry {
178 queue_chain_t entry_links;
179 int16_t sched_pri; /* scheduled (current) priority */
180 int16_t runq;
181 int32_t pad;
182 } *sched_entry_t;
183
184 typedef run_queue_t entry_queue_t; /* A run queue that holds sched_entries instead of threads */
185 typedef run_queue_t group_runq_t; /* A run queue that is part of a sched_group */
186
187 #define SCHED_ENTRY_NULL ((sched_entry_t) 0)
188 #define MULTIQ_ERUNQ (-4) /* Indicates entry is on the main runq */
189
190 /* Each level in the run queue corresponds to one entry in the entries array */
191 struct sched_group {
192 struct sched_entry entries[NRQS];
193 struct run_queue runq;
194 queue_chain_t sched_groups;
195 };
196
197 /*
198 * Keep entry on the head of the runqueue while dequeueing threads.
199 * Only cycle it to the end of the runqueue when a thread in the task
200 * hits its quantum.
201 */
202 static boolean_t deep_drain = FALSE;
203
204 /* Verify the consistency of the runq before touching it */
205 static boolean_t multiq_sanity_check = FALSE;
206
207 /*
208 * Draining threads from the current task is preferred
209 * when they're less than X steps below the current
210 * global highest priority
211 */
212 #define DEFAULT_DRAIN_BAND_LIMIT MAXPRI
213 static integer_t drain_band_limit;
214
215 /*
216 * Don't go below this priority level if there is something above it in another task
217 */
218 #define DEFAULT_DRAIN_DEPTH_LIMIT MAXPRI_THROTTLE
219 static integer_t drain_depth_limit;
220
221 /*
222 * Don't favor the task when there's something above this priority in another task.
223 */
224 #define DEFAULT_DRAIN_CEILING BASEPRI_FOREGROUND
225 static integer_t drain_ceiling;
226
227 static struct zone *sched_group_zone;
228
229 static uint64_t num_sched_groups = 0;
230 static queue_head_t sched_groups;
231
232 static lck_attr_t sched_groups_lock_attr;
233 static lck_grp_t sched_groups_lock_grp;
234 static lck_grp_attr_t sched_groups_lock_grp_attr;
235
236 static lck_mtx_t sched_groups_lock;
237
238
239 static void
240 sched_multiq_init(void);
241
242 static thread_t
243 sched_multiq_steal_thread(processor_set_t pset);
244
245 static void
246 sched_multiq_thread_update_scan(sched_update_scan_context_t scan_context);
247
248 static boolean_t
249 sched_multiq_processor_enqueue(processor_t processor, thread_t thread, integer_t options);
250
251 static boolean_t
252 sched_multiq_processor_queue_remove(processor_t processor, thread_t thread);
253
254 void
255 sched_multiq_quantum_expire(thread_t thread);
256
257 static ast_t
258 sched_multiq_processor_csw_check(processor_t processor);
259
260 static boolean_t
261 sched_multiq_processor_queue_has_priority(processor_t processor, int priority, boolean_t gte);
262
263 static int
264 sched_multiq_runq_count(processor_t processor);
265
266 static boolean_t
267 sched_multiq_processor_queue_empty(processor_t processor);
268
269 static uint64_t
270 sched_multiq_runq_stats_count_sum(processor_t processor);
271
272 static int
273 sched_multiq_processor_bound_count(processor_t processor);
274
275 static void
276 sched_multiq_pset_init(processor_set_t pset);
277
278 static void
279 sched_multiq_processor_init(processor_t processor);
280
281 static thread_t
282 sched_multiq_choose_thread(processor_t processor, int priority, ast_t reason);
283
284 static void
285 sched_multiq_processor_queue_shutdown(processor_t processor);
286
287 static sched_mode_t
288 sched_multiq_initial_thread_sched_mode(task_t parent_task);
289
290 const struct sched_dispatch_table sched_multiq_dispatch = {
291 .sched_name = "multiq",
292 .init = sched_multiq_init,
293 .timebase_init = sched_timeshare_timebase_init,
294 .processor_init = sched_multiq_processor_init,
295 .pset_init = sched_multiq_pset_init,
296 .maintenance_continuation = sched_timeshare_maintenance_continue,
297 .choose_thread = sched_multiq_choose_thread,
298 .steal_thread_enabled = FALSE,
299 .steal_thread = sched_multiq_steal_thread,
300 .compute_timeshare_priority = sched_compute_timeshare_priority,
301 .choose_processor = choose_processor,
302 .processor_enqueue = sched_multiq_processor_enqueue,
303 .processor_queue_shutdown = sched_multiq_processor_queue_shutdown,
304 .processor_queue_remove = sched_multiq_processor_queue_remove,
305 .processor_queue_empty = sched_multiq_processor_queue_empty,
306 .priority_is_urgent = priority_is_urgent,
307 .processor_csw_check = sched_multiq_processor_csw_check,
308 .processor_queue_has_priority = sched_multiq_processor_queue_has_priority,
309 .initial_quantum_size = sched_timeshare_initial_quantum_size,
310 .initial_thread_sched_mode = sched_multiq_initial_thread_sched_mode,
311 .can_update_priority = can_update_priority,
312 .update_priority = update_priority,
313 .lightweight_update_priority = lightweight_update_priority,
314 .quantum_expire = sched_multiq_quantum_expire,
315 .processor_runq_count = sched_multiq_runq_count,
316 .processor_runq_stats_count_sum = sched_multiq_runq_stats_count_sum,
317 .processor_bound_count = sched_multiq_processor_bound_count,
318 .thread_update_scan = sched_multiq_thread_update_scan,
319 .direct_dispatch_to_idle_processors = FALSE,
320 .multiple_psets_enabled = FALSE,
321 .sched_groups_enabled = TRUE,
322 };
323
324
325 static void
326 sched_multiq_init(void)
327 {
328 #if defined(MULTIQ_SANITY_CHECK)
329 PE_parse_boot_argn("-multiq-sanity-check", &multiq_sanity_check, sizeof(multiq_sanity_check));
330 #endif
331
332 PE_parse_boot_argn("-multiq-deep-drain", &deep_drain, sizeof(deep_drain));
333
334 if (!PE_parse_boot_argn("multiq_drain_ceiling", &drain_ceiling, sizeof(drain_ceiling))) {
335 drain_ceiling = DEFAULT_DRAIN_CEILING;
336 }
337
338 if (!PE_parse_boot_argn("multiq_drain_depth_limit", &drain_depth_limit, sizeof(drain_depth_limit))) {
339 drain_depth_limit = DEFAULT_DRAIN_DEPTH_LIMIT;
340 }
341
342 if (!PE_parse_boot_argn("multiq_drain_band_limit", &drain_band_limit, sizeof(drain_band_limit))) {
343 drain_band_limit = DEFAULT_DRAIN_BAND_LIMIT;
344 }
345
346 printf("multiq scheduler config: deep-drain %d, ceiling %d, depth limit %d, band limit %d, sanity check %d\n",
347 deep_drain, drain_ceiling, drain_depth_limit, drain_band_limit, multiq_sanity_check);
348
349 sched_group_zone = zinit(
350 sizeof(struct sched_group),
351 task_max * sizeof(struct sched_group),
352 PAGE_SIZE,
353 "sched groups");
354
355 zone_change(sched_group_zone, Z_NOENCRYPT, TRUE);
356 zone_change(sched_group_zone, Z_NOCALLOUT, TRUE);
357
358 queue_init(&sched_groups);
359
360 lck_grp_attr_setdefault(&sched_groups_lock_grp_attr);
361 lck_grp_init(&sched_groups_lock_grp, "sched_groups", &sched_groups_lock_grp_attr);
362 lck_attr_setdefault(&sched_groups_lock_attr);
363 lck_mtx_init(&sched_groups_lock, &sched_groups_lock_grp, &sched_groups_lock_attr);
364
365 sched_timeshare_init();
366 }
367
368 static void
369 sched_multiq_processor_init(processor_t processor)
370 {
371 run_queue_init(&processor->runq);
372 }
373
374 static void
375 sched_multiq_pset_init(processor_set_t pset)
376 {
377 run_queue_init(&pset->pset_runq);
378 }
379
380 static sched_mode_t
381 sched_multiq_initial_thread_sched_mode(task_t parent_task)
382 {
383 if (parent_task == kernel_task)
384 return TH_MODE_FIXED;
385 else
386 return TH_MODE_TIMESHARE;
387 }
388
389 sched_group_t
390 sched_group_create(void)
391 {
392 sched_group_t sched_group;
393
394 if (!SCHED(sched_groups_enabled))
395 return SCHED_GROUP_NULL;
396
397 sched_group = (sched_group_t)zalloc(sched_group_zone);
398
399 bzero(sched_group, sizeof(struct sched_group));
400
401 run_queue_init(&sched_group->runq);
402
403 for (int i = 0; i < NRQS; i++) {
404 sched_group->entries[i].runq = 0;
405 sched_group->entries[i].sched_pri = i;
406 }
407
408 lck_mtx_lock(&sched_groups_lock);
409 queue_enter(&sched_groups, sched_group, sched_group_t, sched_groups);
410 num_sched_groups++;
411 lck_mtx_unlock(&sched_groups_lock);
412
413 return (sched_group);
414 }
415
416 void
417 sched_group_destroy(sched_group_t sched_group)
418 {
419 if (!SCHED(sched_groups_enabled)) {
420 assert(sched_group == SCHED_GROUP_NULL);
421 return;
422 }
423
424 assert(sched_group != SCHED_GROUP_NULL);
425 assert(sched_group->runq.count == 0);
426
427 for (int i = 0; i < NRQS; i++) {
428 assert(sched_group->entries[i].runq == 0);
429 assert(sched_group->entries[i].sched_pri == i);
430 }
431
432 lck_mtx_lock(&sched_groups_lock);
433 queue_remove(&sched_groups, sched_group, sched_group_t, sched_groups);
434 num_sched_groups--;
435 lck_mtx_unlock(&sched_groups_lock);
436
437 zfree(sched_group_zone, sched_group);
438 }
439
440 __attribute__((always_inline))
441 static inline entry_queue_t
442 multiq_main_entryq(processor_t processor)
443 {
444 return (entry_queue_t)&processor->processor_set->pset_runq;
445 }
446
447 __attribute__((always_inline))
448 static inline run_queue_t
449 multiq_bound_runq(processor_t processor)
450 {
451 return &processor->runq;
452 }
453
454 __attribute__((always_inline))
455 static inline sched_entry_t
456 group_entry_for_pri(sched_group_t group, integer_t pri)
457 {
458 return &group->entries[pri];
459 }
460
461 __attribute__((always_inline))
462 static inline sched_group_t
463 group_for_entry(sched_entry_t entry)
464 {
465 #pragma clang diagnostic push
466 #pragma clang diagnostic ignored "-Wcast-align"
467 sched_group_t group = (sched_group_t)(entry - entry->sched_pri);
468 #pragma clang diagnostic pop
469 return group;
470 }
471
472 /* Peek at the head of the runqueue */
473 static sched_entry_t
474 entry_queue_first_entry(entry_queue_t rq)
475 {
476 assert(rq->count != 0);
477
478 queue_t queue = &rq->queues[rq->highq];
479
480 sched_entry_t entry = qe_queue_first(queue, struct sched_entry, entry_links);
481
482 assert(entry->sched_pri == rq->highq);
483
484 return entry;
485 }
486
487 #if defined(MULTIQ_SANITY_CHECK)
488
489 #if MACH_ASSERT
490 __attribute__((always_inline))
491 static inline boolean_t
492 queue_chain_linked(queue_chain_t* chain)
493 {
494 if (chain->next != NULL) {
495 assert(chain->prev != NULL);
496 return TRUE;
497 } else {
498 assert(chain->prev == NULL);
499 return FALSE;
500 }
501 }
502 #endif /* MACH_ASSERT */
503
504 static thread_t
505 group_first_thread(sched_group_t group)
506 {
507 group_runq_t rq = &group->runq;
508
509 assert(rq->count != 0);
510
511 queue_t queue = &rq->queues[rq->highq];
512
513 thread_t thread = qe_queue_first(queue, struct thread, runq_links);
514
515 assert(thread != THREAD_NULL);
516 assert_thread_magic(thread);
517
518 assert(thread->sched_group == group);
519
520 /* TODO: May not be safe */
521 assert(thread->sched_pri == rq->highq);
522
523 return thread;
524 }
525
526 /* Asserts if entry is not in entry runq at pri */
527 static void
528 entry_queue_check_entry(entry_queue_t runq, sched_entry_t entry, int expected_pri)
529 {
530 queue_t q;
531 sched_entry_t elem;
532
533 assert(queue_chain_linked(&entry->entry_links));
534 assert(entry->runq == MULTIQ_ERUNQ);
535
536 q = &runq->queues[expected_pri];
537
538 qe_foreach_element(elem, q, entry_links) {
539 if (elem == entry)
540 return;
541 }
542
543 panic("runq %p doesn't contain entry %p at pri %d", runq, entry, expected_pri);
544 }
545
546 /* Asserts if thread is not in group at its priority */
547 static void
548 sched_group_check_thread(sched_group_t group, thread_t thread)
549 {
550 queue_t q;
551 thread_t elem;
552 int pri = thread->sched_pri;
553
554 assert(thread->runq != PROCESSOR_NULL);
555
556 q = &group->runq.queues[pri];
557
558 qe_foreach_element(elem, q, runq_links) {
559 if (elem == thread)
560 return;
561 }
562
563 panic("group %p doesn't contain thread %p at pri %d", group, thread, pri);
564 }
565
566 static void
567 global_check_entry_queue(entry_queue_t main_entryq)
568 {
569 if (main_entryq->count == 0)
570 return;
571
572 sched_entry_t entry = entry_queue_first_entry(main_entryq);
573
574 assert(entry->runq == MULTIQ_ERUNQ);
575
576 sched_group_t group = group_for_entry(entry);
577
578 thread_t thread = group_first_thread(group);
579
580 __assert_only sched_entry_t thread_entry = group_entry_for_pri(thread->sched_group, thread->sched_pri);
581
582 assert(entry->sched_pri == group->runq.highq);
583
584 assert(entry == thread_entry);
585 assert(thread->runq != PROCESSOR_NULL);
586 }
587
588 static void
589 group_check_run_queue(entry_queue_t main_entryq, sched_group_t group)
590 {
591 if (group->runq.count == 0)
592 return;
593
594 thread_t thread = group_first_thread(group);
595
596 assert(thread->runq != PROCESSOR_NULL);
597
598 sched_entry_t sched_entry = group_entry_for_pri(thread->sched_group, thread->sched_pri);
599
600 entry_queue_check_entry(main_entryq, sched_entry, thread->sched_pri);
601
602 assert(sched_entry->sched_pri == thread->sched_pri);
603 assert(sched_entry->runq == MULTIQ_ERUNQ);
604 }
605
606 #endif /* defined(MULTIQ_SANITY_CHECK) */
607
608 /*
609 * The run queue must not be empty.
610 */
611 static sched_entry_t
612 entry_queue_dequeue_entry(entry_queue_t rq)
613 {
614 sched_entry_t sched_entry;
615 queue_t queue = &rq->queues[rq->highq];
616
617 assert(rq->count > 0);
618 assert(!queue_empty(queue));
619
620 sched_entry = qe_dequeue_head(queue, struct sched_entry, entry_links);
621
622 SCHED_STATS_RUNQ_CHANGE(&rq->runq_stats, rq->count);
623 rq->count--;
624 if (SCHED(priority_is_urgent)(rq->highq)) {
625 rq->urgency--; assert(rq->urgency >= 0);
626 }
627 if (queue_empty(queue)) {
628 rq_bitmap_clear(rq->bitmap, rq->highq);
629 rq->highq = bitmap_first(rq->bitmap, NRQS);
630 }
631
632 sched_entry->runq = 0;
633
634 return (sched_entry);
635 }
636
637 /*
638 * The run queue must not be empty.
639 */
640 static boolean_t
641 entry_queue_enqueue_entry(
642 entry_queue_t rq,
643 sched_entry_t entry,
644 integer_t options)
645 {
646 int sched_pri = entry->sched_pri;
647 queue_t queue = &rq->queues[sched_pri];
648 boolean_t result = FALSE;
649
650 assert(entry->runq == 0);
651
652 if (queue_empty(queue)) {
653 enqueue_tail(queue, &entry->entry_links);
654
655 rq_bitmap_set(rq->bitmap, sched_pri);
656 if (sched_pri > rq->highq) {
657 rq->highq = sched_pri;
658 result = TRUE;
659 }
660 } else {
661 if (options & SCHED_TAILQ)
662 enqueue_tail(queue, &entry->entry_links);
663 else
664 enqueue_head(queue, &entry->entry_links);
665 }
666 if (SCHED(priority_is_urgent)(sched_pri))
667 rq->urgency++;
668 SCHED_STATS_RUNQ_CHANGE(&rq->runq_stats, rq->count);
669 rq->count++;
670
671 entry->runq = MULTIQ_ERUNQ;
672
673 return (result);
674 }
675
676 /*
677 * The entry must be in this runqueue.
678 */
679 static void
680 entry_queue_remove_entry(
681 entry_queue_t rq,
682 sched_entry_t entry)
683 {
684 int sched_pri = entry->sched_pri;
685
686 #if defined(MULTIQ_SANITY_CHECK)
687 if (multiq_sanity_check) {
688 entry_queue_check_entry(rq, entry, sched_pri);
689 }
690 #endif
691
692 remqueue(&entry->entry_links);
693
694 SCHED_STATS_RUNQ_CHANGE(&rq->runq_stats, rq->count);
695 rq->count--;
696 if (SCHED(priority_is_urgent)(sched_pri)) {
697 rq->urgency--; assert(rq->urgency >= 0);
698 }
699
700 if (queue_empty(&rq->queues[sched_pri])) {
701 /* update run queue status */
702 rq_bitmap_clear(rq->bitmap, sched_pri);
703 rq->highq = bitmap_first(rq->bitmap, NRQS);
704 }
705
706 entry->runq = 0;
707 }
708
709 static void
710 entry_queue_change_entry(
711 entry_queue_t rq,
712 sched_entry_t entry,
713 integer_t options)
714 {
715 int sched_pri = entry->sched_pri;
716 queue_t queue = &rq->queues[sched_pri];
717
718 #if defined(MULTIQ_SANITY_CHECK)
719 if (multiq_sanity_check) {
720 entry_queue_check_entry(rq, entry, sched_pri);
721 }
722 #endif
723
724 if (options & SCHED_TAILQ)
725 re_queue_tail(queue, &entry->entry_links);
726 else
727 re_queue_head(queue, &entry->entry_links);
728 }
729 /*
730 * The run queue must not be empty.
731 *
732 * sets queue_empty to TRUE if queue is now empty at thread_pri
733 */
734 static thread_t
735 group_run_queue_dequeue_thread(
736 group_runq_t rq,
737 integer_t *thread_pri,
738 boolean_t *queue_empty)
739 {
740 thread_t thread;
741 queue_t queue = &rq->queues[rq->highq];
742
743 assert(rq->count > 0);
744 assert(!queue_empty(queue));
745
746 *thread_pri = rq->highq;
747
748 thread = qe_dequeue_head(queue, struct thread, runq_links);
749 assert_thread_magic(thread);
750
751 SCHED_STATS_RUNQ_CHANGE(&rq->runq_stats, rq->count);
752 rq->count--;
753 if (SCHED(priority_is_urgent)(rq->highq)) {
754 rq->urgency--; assert(rq->urgency >= 0);
755 }
756 if (queue_empty(queue)) {
757 rq_bitmap_clear(rq->bitmap, rq->highq);
758 rq->highq = bitmap_first(rq->bitmap, NRQS);
759 *queue_empty = TRUE;
760 } else {
761 *queue_empty = FALSE;
762 }
763
764 return thread;
765 }
766
767 /*
768 * The run queue must not be empty.
769 * returns TRUE if queue was empty at thread_pri
770 */
771 static boolean_t
772 group_run_queue_enqueue_thread(
773 group_runq_t rq,
774 thread_t thread,
775 integer_t thread_pri,
776 integer_t options)
777 {
778 queue_t queue = &rq->queues[thread_pri];
779 boolean_t result = FALSE;
780
781 assert(thread->runq == PROCESSOR_NULL);
782 assert_thread_magic(thread);
783
784 if (queue_empty(queue)) {
785 enqueue_tail(queue, &thread->runq_links);
786
787 rq_bitmap_set(rq->bitmap, thread_pri);
788 if (thread_pri > rq->highq) {
789 rq->highq = thread_pri;
790 }
791 result = TRUE;
792 } else {
793 if (options & SCHED_TAILQ)
794 enqueue_tail(queue, &thread->runq_links);
795 else
796 enqueue_head(queue, &thread->runq_links);
797 }
798 if (SCHED(priority_is_urgent)(thread_pri))
799 rq->urgency++;
800 SCHED_STATS_RUNQ_CHANGE(&rq->runq_stats, rq->count);
801 rq->count++;
802
803 return (result);
804 }
805
806 /*
807 * The thread must be in this runqueue.
808 * returns TRUE if queue is now empty at thread_pri
809 */
810 static boolean_t
811 group_run_queue_remove_thread(
812 group_runq_t rq,
813 thread_t thread,
814 integer_t thread_pri)
815 {
816 boolean_t result = FALSE;
817
818 assert_thread_magic(thread);
819 assert(thread->runq != PROCESSOR_NULL);
820
821 remqueue(&thread->runq_links);
822
823 SCHED_STATS_RUNQ_CHANGE(&rq->runq_stats, rq->count);
824 rq->count--;
825 if (SCHED(priority_is_urgent)(thread_pri)) {
826 rq->urgency--; assert(rq->urgency >= 0);
827 }
828
829 if (queue_empty(&rq->queues[thread_pri])) {
830 /* update run queue status */
831 rq_bitmap_clear(rq->bitmap, thread_pri);
832 rq->highq = bitmap_first(rq->bitmap, NRQS);
833 result = TRUE;
834 }
835
836 thread->runq = PROCESSOR_NULL;
837
838 return result;
839 }
840
841 /*
842 * A thread's sched pri may change out from under us because
843 * we're clearing thread->runq here without the thread locked.
844 * Do not rely on it to be the same as when we enqueued.
845 */
846 static thread_t
847 sched_global_dequeue_thread(entry_queue_t main_entryq)
848 {
849 boolean_t pri_level_empty = FALSE;
850 sched_entry_t entry;
851 group_runq_t group_runq;
852 thread_t thread;
853 integer_t thread_pri;
854 sched_group_t group;
855
856 assert(main_entryq->count > 0);
857
858 entry = entry_queue_dequeue_entry(main_entryq);
859
860 group = group_for_entry(entry);
861 group_runq = &group->runq;
862
863 thread = group_run_queue_dequeue_thread(group_runq, &thread_pri, &pri_level_empty);
864
865 thread->runq = PROCESSOR_NULL;
866
867 if (!pri_level_empty) {
868 entry_queue_enqueue_entry(main_entryq, entry, SCHED_TAILQ);
869 }
870
871 return thread;
872 }
873
874 /* Dequeue a thread from the global runq without moving the entry */
875 static thread_t
876 sched_global_deep_drain_dequeue_thread(entry_queue_t main_entryq)
877 {
878 boolean_t pri_level_empty = FALSE;
879 sched_entry_t entry;
880 group_runq_t group_runq;
881 thread_t thread;
882 integer_t thread_pri;
883 sched_group_t group;
884
885 assert(main_entryq->count > 0);
886
887 entry = entry_queue_first_entry(main_entryq);
888
889 group = group_for_entry(entry);
890 group_runq = &group->runq;
891
892 thread = group_run_queue_dequeue_thread(group_runq, &thread_pri, &pri_level_empty);
893
894 thread->runq = PROCESSOR_NULL;
895
896 if (pri_level_empty) {
897 entry_queue_remove_entry(main_entryq, entry);
898 }
899
900 return thread;
901 }
902
903
904 static thread_t
905 sched_group_dequeue_thread(
906 entry_queue_t main_entryq,
907 sched_group_t group)
908 {
909 group_runq_t group_runq = &group->runq;
910 boolean_t pri_level_empty = FALSE;
911 thread_t thread;
912 integer_t thread_pri;
913
914 thread = group_run_queue_dequeue_thread(group_runq, &thread_pri, &pri_level_empty);
915
916 thread->runq = PROCESSOR_NULL;
917
918 if (pri_level_empty) {
919 entry_queue_remove_entry(main_entryq, group_entry_for_pri(group, thread_pri));
920 }
921
922 return thread;
923 }
924
925 static void
926 sched_group_remove_thread(
927 entry_queue_t main_entryq,
928 sched_group_t group,
929 thread_t thread)
930 {
931 integer_t thread_pri = thread->sched_pri;
932 sched_entry_t sched_entry = group_entry_for_pri(group, thread_pri);
933
934 #if defined(MULTIQ_SANITY_CHECK)
935 if (multiq_sanity_check) {
936 global_check_entry_queue(main_entryq);
937 group_check_run_queue(main_entryq, group);
938
939 sched_group_check_thread(group, thread);
940 entry_queue_check_entry(main_entryq, sched_entry, thread_pri);
941 }
942 #endif
943
944 boolean_t pri_level_empty = group_run_queue_remove_thread(&group->runq, thread, thread_pri);
945
946 if (pri_level_empty) {
947 entry_queue_remove_entry(main_entryq, sched_entry);
948 }
949
950 #if defined(MULTIQ_SANITY_CHECK)
951 if (multiq_sanity_check) {
952 global_check_entry_queue(main_entryq);
953 group_check_run_queue(main_entryq, group);
954 }
955 #endif
956 }
957
958 static void
959 sched_group_enqueue_thread(
960 entry_queue_t main_entryq,
961 sched_group_t group,
962 thread_t thread,
963 integer_t options)
964 {
965 #if defined(MULTIQ_SANITY_CHECK)
966 if (multiq_sanity_check) {
967 global_check_entry_queue(main_entryq);
968 group_check_run_queue(main_entryq, group);
969 }
970 #endif
971
972 int sched_pri = thread->sched_pri;
973
974 boolean_t pri_level_was_empty = group_run_queue_enqueue_thread(&group->runq, thread, sched_pri, options);
975
976 if (pri_level_was_empty) {
977 /*
978 * TODO: Need to figure out if passing options here is a good idea or not
979 * What effects would it have?
980 */
981 entry_queue_enqueue_entry(main_entryq, &group->entries[sched_pri], options);
982 } else if (options & SCHED_HEADQ) {
983 /* The thread should be at the head of the line - move its entry to the front */
984 entry_queue_change_entry(main_entryq, &group->entries[sched_pri], options);
985 }
986 }
987
988 /*
989 * Locate a thread to execute from the run queue and return it.
990 * Only choose a thread with greater or equal priority.
991 *
992 * pset is locked, thread is not locked.
993 *
994 * Returns THREAD_NULL if it cannot find a valid thread.
995 *
996 * Note: we cannot rely on the value of thread->sched_pri in this path because
997 * we don't have the thread locked.
998 *
999 * TODO: Remove tracepoints
1000 */
1001 static thread_t
1002 sched_multiq_choose_thread(
1003 processor_t processor,
1004 int priority,
1005 ast_t reason)
1006 {
1007 entry_queue_t main_entryq = multiq_main_entryq(processor);
1008 run_queue_t bound_runq = multiq_bound_runq(processor);
1009
1010 boolean_t choose_bound_runq = FALSE;
1011
1012 if (bound_runq->highq < priority &&
1013 main_entryq->highq < priority)
1014 return THREAD_NULL;
1015
1016 if (bound_runq->count && main_entryq->count) {
1017 if (bound_runq->highq >= main_entryq->highq) {
1018 choose_bound_runq = TRUE;
1019 } else {
1020 /* Use main runq */
1021 }
1022 } else if (bound_runq->count) {
1023 choose_bound_runq = TRUE;
1024 } else if (main_entryq->count) {
1025 /* Use main runq */
1026 } else {
1027 return (THREAD_NULL);
1028 }
1029
1030 if (choose_bound_runq) {
1031 KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
1032 MACHDBG_CODE(DBG_MACH_SCHED, MACH_MULTIQ_DEQUEUE) | DBG_FUNC_NONE,
1033 MACH_MULTIQ_BOUND, main_entryq->highq, bound_runq->highq, 0, 0);
1034
1035 return run_queue_dequeue(bound_runq, SCHED_HEADQ);
1036 }
1037
1038 sched_group_t group = current_thread()->sched_group;
1039
1040 #if defined(MULTIQ_SANITY_CHECK)
1041 if (multiq_sanity_check) {
1042 global_check_entry_queue(main_entryq);
1043 group_check_run_queue(main_entryq, group);
1044 }
1045 #endif
1046
1047 /*
1048 * Determine if we should look at the group or the global queue
1049 *
1050 * TODO:
1051 * Perhaps pass reason as a 'should look inside' argument to choose_thread
1052 * Should YIELD AST override drain limit?
1053 */
1054 if (group->runq.count != 0 && (reason & AST_PREEMPTION) == 0) {
1055 boolean_t favor_group = TRUE;
1056
1057 integer_t global_pri = main_entryq->highq;
1058 integer_t group_pri = group->runq.highq;
1059
1060 /*
1061 * Favor the current group if the group is still the globally highest.
1062 *
1063 * Otherwise, consider choosing a thread from the current group
1064 * even if it's lower priority than the global highest priority.
1065 */
1066 if (global_pri > group_pri) {
1067 /*
1068 * If there's something elsewhere above the depth limit,
1069 * don't pick a thread below the limit.
1070 */
1071 if (global_pri > drain_depth_limit && group_pri <= drain_depth_limit)
1072 favor_group = FALSE;
1073
1074 /*
1075 * If there's something at or above the ceiling,
1076 * don't favor the group.
1077 */
1078 if (global_pri >= drain_ceiling)
1079 favor_group = FALSE;
1080
1081 /*
1082 * Don't go more than X steps below the global highest
1083 */
1084 if ((global_pri - group_pri) >= drain_band_limit)
1085 favor_group = FALSE;
1086 }
1087
1088 if (favor_group) {
1089 /* Pull from local runq */
1090 KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
1091 MACHDBG_CODE(DBG_MACH_SCHED, MACH_MULTIQ_DEQUEUE) | DBG_FUNC_NONE,
1092 MACH_MULTIQ_GROUP, global_pri, group_pri, 0, 0);
1093
1094 return sched_group_dequeue_thread(main_entryq, group);
1095 }
1096 }
1097
1098 KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
1099 MACHDBG_CODE(DBG_MACH_SCHED, MACH_MULTIQ_DEQUEUE) | DBG_FUNC_NONE,
1100 MACH_MULTIQ_GLOBAL, main_entryq->highq, group->runq.highq, 0, 0);
1101
1102 /* Couldn't pull from local runq, pull from global runq instead */
1103 if (deep_drain) {
1104 return sched_global_deep_drain_dequeue_thread(main_entryq);
1105 } else {
1106 return sched_global_dequeue_thread(main_entryq);
1107 }
1108 }
1109
1110
1111 /*
1112 * Thread must be locked, and not already be on a run queue.
1113 * pset is locked.
1114 */
1115 static boolean_t
1116 sched_multiq_processor_enqueue(
1117 processor_t processor,
1118 thread_t thread,
1119 integer_t options)
1120 {
1121 boolean_t result;
1122
1123 assert(processor == thread->chosen_processor);
1124
1125 if (thread->bound_processor != PROCESSOR_NULL) {
1126 assert(thread->bound_processor == processor);
1127
1128 result = run_queue_enqueue(multiq_bound_runq(processor), thread, options);
1129 thread->runq = processor;
1130
1131 return result;
1132 }
1133
1134 sched_group_enqueue_thread(multiq_main_entryq(processor),
1135 thread->sched_group,
1136 thread, options);
1137
1138 thread->runq = processor;
1139
1140 return (FALSE);
1141 }
1142
1143 /*
1144 * Called in the context of thread with thread and pset unlocked,
1145 * after updating thread priority but before propagating that priority
1146 * to the processor
1147 */
1148 void
1149 sched_multiq_quantum_expire(thread_t thread)
1150 {
1151 if (deep_drain) {
1152 /*
1153 * Move the entry at this priority to the end of the queue,
1154 * to allow the next task a shot at running.
1155 */
1156
1157 processor_t processor = thread->last_processor;
1158 processor_set_t pset = processor->processor_set;
1159 entry_queue_t entryq = multiq_main_entryq(processor);
1160
1161 pset_lock(pset);
1162
1163 sched_entry_t entry = group_entry_for_pri(thread->sched_group, processor->current_pri);
1164
1165 if (entry->runq == MULTIQ_ERUNQ) {
1166 entry_queue_change_entry(entryq, entry, SCHED_TAILQ);
1167 }
1168
1169 pset_unlock(pset);
1170 }
1171 }
1172
1173 static boolean_t
1174 sched_multiq_processor_queue_empty(processor_t processor)
1175 {
1176 return multiq_main_entryq(processor)->count == 0 &&
1177 multiq_bound_runq(processor)->count == 0;
1178 }
1179
1180 static ast_t
1181 sched_multiq_processor_csw_check(processor_t processor)
1182 {
1183 boolean_t has_higher;
1184 int pri;
1185
1186 entry_queue_t main_entryq = multiq_main_entryq(processor);
1187 run_queue_t bound_runq = multiq_bound_runq(processor);
1188
1189 assert(processor->active_thread != NULL);
1190
1191 pri = MAX(main_entryq->highq, bound_runq->highq);
1192
1193 if (processor->first_timeslice) {
1194 has_higher = (pri > processor->current_pri);
1195 } else {
1196 has_higher = (pri >= processor->current_pri);
1197 }
1198
1199 if (has_higher) {
1200 if (main_entryq->urgency > 0)
1201 return (AST_PREEMPT | AST_URGENT);
1202
1203 if (bound_runq->urgency > 0)
1204 return (AST_PREEMPT | AST_URGENT);
1205
1206 return AST_PREEMPT;
1207 }
1208
1209 return AST_NONE;
1210 }
1211
1212 static boolean_t
1213 sched_multiq_processor_queue_has_priority(
1214 processor_t processor,
1215 int priority,
1216 boolean_t gte)
1217 {
1218 run_queue_t main_runq = multiq_main_entryq(processor);
1219 run_queue_t bound_runq = multiq_bound_runq(processor);
1220
1221 if (main_runq->count == 0 && bound_runq->count == 0)
1222 return FALSE;
1223
1224 int qpri = MAX(main_runq->highq, bound_runq->highq);
1225
1226 if (gte)
1227 return qpri >= priority;
1228 else
1229 return qpri > priority;
1230 }
1231
1232 static int
1233 sched_multiq_runq_count(processor_t processor)
1234 {
1235 /*
1236 * TODO: Decide whether to keep a count of runnable threads in the pset
1237 * or just return something less than the true count.
1238 *
1239 * This needs to be fast, so no iterating the whole runq.
1240 *
1241 * Another possible decision is to remove this - with global runq
1242 * it doesn't make much sense.
1243 */
1244 return multiq_main_entryq(processor)->count + multiq_bound_runq(processor)->count;
1245 }
1246
1247 static uint64_t
1248 sched_multiq_runq_stats_count_sum(processor_t processor)
1249 {
1250 /*
1251 * TODO: This one does need to go through all the runqueues, but it's only needed for
1252 * the sched stats tool
1253 */
1254
1255 uint64_t bound_sum = multiq_bound_runq(processor)->runq_stats.count_sum;
1256
1257 if (processor->cpu_id == processor->processor_set->cpu_set_low)
1258 return bound_sum + multiq_main_entryq(processor)->runq_stats.count_sum;
1259 else
1260 return bound_sum;
1261 }
1262
1263 static int
1264 sched_multiq_processor_bound_count(processor_t processor)
1265 {
1266 return multiq_bound_runq(processor)->count;
1267 }
1268
1269 static void
1270 sched_multiq_processor_queue_shutdown(processor_t processor)
1271 {
1272 processor_set_t pset = processor->processor_set;
1273 entry_queue_t main_entryq = multiq_main_entryq(processor);
1274 thread_t thread;
1275 queue_head_t tqueue;
1276
1277 /* We only need to migrate threads if this is the last active processor in the pset */
1278 if (pset->online_processor_count > 0) {
1279 pset_unlock(pset);
1280 return;
1281 }
1282
1283 queue_init(&tqueue);
1284
1285 /* Note that we do not remove bound threads from the queues here */
1286
1287 while (main_entryq->count > 0) {
1288 thread = sched_global_dequeue_thread(main_entryq);
1289 enqueue_tail(&tqueue, &thread->runq_links);
1290 }
1291
1292 pset_unlock(pset);
1293
1294 qe_foreach_element_safe(thread, &tqueue, runq_links) {
1295
1296 remqueue(&thread->runq_links);
1297
1298 thread_lock(thread);
1299
1300 thread_setrun(thread, SCHED_TAILQ);
1301
1302 thread_unlock(thread);
1303 }
1304 }
1305
1306 /*
1307 * Thread is locked
1308 *
1309 * This is why we can never read sched_pri unless we have the thread locked.
1310 * Which we do in the enqueue and remove cases, but not the dequeue case.
1311 */
1312 static boolean_t
1313 sched_multiq_processor_queue_remove(
1314 processor_t processor,
1315 thread_t thread)
1316 {
1317 boolean_t removed = FALSE;
1318 processor_set_t pset = processor->processor_set;
1319
1320 pset_lock(pset);
1321
1322 if (thread->runq != PROCESSOR_NULL) {
1323 /*
1324 * Thread is on a run queue and we have a lock on
1325 * that run queue.
1326 */
1327
1328 assert(thread->runq == processor);
1329
1330 if (thread->bound_processor != PROCESSOR_NULL) {
1331 assert(processor == thread->bound_processor);
1332 run_queue_remove(multiq_bound_runq(processor), thread);
1333 thread->runq = PROCESSOR_NULL;
1334 } else {
1335 sched_group_remove_thread(multiq_main_entryq(processor),
1336 thread->sched_group,
1337 thread);
1338 }
1339
1340 removed = TRUE;
1341 }
1342
1343 pset_unlock(pset);
1344
1345 return removed;
1346 }
1347
1348 /* pset is locked, returned unlocked */
1349 static thread_t
1350 sched_multiq_steal_thread(processor_set_t pset)
1351 {
1352 pset_unlock(pset);
1353 return (THREAD_NULL);
1354 }
1355
1356 /*
1357 * Scan the global queue for candidate groups, and scan those groups for
1358 * candidate threads.
1359 *
1360 * TODO: This iterates every group runq in its entirety for each entry it has in the runq, which is O(N^2)
1361 * Instead, iterate only the queue in the group runq matching the priority of the entry.
1362 *
1363 * Returns TRUE if retry is needed.
1364 */
1365 static boolean_t
1366 group_scan(entry_queue_t runq, sched_update_scan_context_t scan_context) {
1367 int count = runq->count;
1368 int queue_index;
1369
1370 assert(count >= 0);
1371
1372 if (count == 0)
1373 return FALSE;
1374
1375 for (queue_index = bitmap_first(runq->bitmap, NRQS);
1376 queue_index >= 0;
1377 queue_index = bitmap_next(runq->bitmap, queue_index)) {
1378
1379 sched_entry_t entry;
1380
1381 qe_foreach_element(entry, &runq->queues[queue_index], entry_links) {
1382 assert(count > 0);
1383
1384 sched_group_t group = group_for_entry(entry);
1385 if (group->runq.count > 0) {
1386 if (runq_scan(&group->runq, scan_context))
1387 return (TRUE);
1388 }
1389 count--;
1390 }
1391 }
1392
1393 return (FALSE);
1394 }
1395
1396 static void
1397 sched_multiq_thread_update_scan(sched_update_scan_context_t scan_context)
1398 {
1399 boolean_t restart_needed = FALSE;
1400 processor_t processor = processor_list;
1401 processor_set_t pset;
1402 thread_t thread;
1403 spl_t s;
1404
1405 /*
1406 * We update the threads associated with each processor (bound and idle threads)
1407 * and then update the threads in each pset runqueue.
1408 */
1409
1410 do {
1411 do {
1412 pset = processor->processor_set;
1413
1414 s = splsched();
1415 pset_lock(pset);
1416
1417 restart_needed = runq_scan(multiq_bound_runq(processor), scan_context);
1418
1419 pset_unlock(pset);
1420 splx(s);
1421
1422 if (restart_needed)
1423 break;
1424
1425 thread = processor->idle_thread;
1426 if (thread != THREAD_NULL && thread->sched_stamp != sched_tick) {
1427 if (thread_update_add_thread(thread) == FALSE) {
1428 restart_needed = TRUE;
1429 break;
1430 }
1431 }
1432 } while ((processor = processor->processor_list) != NULL);
1433
1434 /* Ok, we now have a collection of candidates -- fix them. */
1435 thread_update_process_threads();
1436
1437 } while (restart_needed);
1438
1439 pset = &pset0;
1440
1441 do {
1442 do {
1443 s = splsched();
1444 pset_lock(pset);
1445
1446 restart_needed = group_scan(&pset->pset_runq, scan_context);
1447
1448 pset_unlock(pset);
1449 splx(s);
1450
1451 if (restart_needed)
1452 break;
1453 } while ((pset = pset->pset_list) != NULL);
1454
1455 /* Ok, we now have a collection of candidates -- fix them. */
1456 thread_update_process_threads();
1457
1458 } while (restart_needed);
1459 }
1460
1461