1/*
2 * Copyright 2013, Pawe�� Dziepak, pdziepak@quarnos.org.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "scheduler_cpu.h"
8
9#include <util/AutoLock.h>
10
11#include <algorithm>
12
13#include "scheduler_thread.h"
14
15
16namespace Scheduler {
17
18
19CPUEntry* gCPUEntries;
20
21CoreEntry* gCoreEntries;
22CoreLoadHeap gCoreLoadHeap;
23CoreLoadHeap gCoreHighLoadHeap;
24rw_spinlock gCoreHeapsLock = B_RW_SPINLOCK_INITIALIZER;
25int32 gCoreCount;
26
27PackageEntry* gPackageEntries;
28IdlePackageList gIdlePackageList;
29rw_spinlock gIdlePackageLock = B_RW_SPINLOCK_INITIALIZER;
30int32 gPackageCount;
31
32
33}	// namespace Scheduler
34
35using namespace Scheduler;
36
37
38class Scheduler::DebugDumper {
39public:
40	static	void		DumpCPURunQueue(CPUEntry* cpu);
41	static	void		DumpCoreRunQueue(CoreEntry* core);
42	static	void		DumpCoreLoadHeapEntry(CoreEntry* core);
43	static	void		DumpIdleCoresInPackage(PackageEntry* package);
44
45private:
46	struct CoreThreadsData {
47			CoreEntry*	fCore;
48			int32		fLoad;
49	};
50
51	static	void		_AnalyzeCoreThreads(Thread* thread, void* data);
52};
53
54
55static CPUPriorityHeap sDebugCPUHeap;
56static CoreLoadHeap sDebugCoreHeap;
57
58
59void
60ThreadRunQueue::Dump() const
61{
62	ThreadRunQueue::ConstIterator iterator = GetConstIterator();
63	if (!iterator.HasNext())
64		kprintf("Run queue is empty.\n");
65	else {
66		kprintf("thread      id      priority penalty  name\n");
67		while (iterator.HasNext()) {
68			ThreadData* threadData = iterator.Next();
69			Thread* thread = threadData->GetThread();
70
71			kprintf("%p  %-7" B_PRId32 " %-8" B_PRId32 " %-8" B_PRId32 " %s\n",
72				thread, thread->id, thread->priority,
73				thread->priority - threadData->GetEffectivePriority(),
74				thread->name);
75		}
76	}
77}
78
79
80CPUEntry::CPUEntry()
81	:
82	fLoad(0),
83	fMeasureActiveTime(0),
84	fMeasureTime(0),
85	fUpdateLoadEvent(false)
86{
87	B_INITIALIZE_RW_SPINLOCK(&fSchedulerModeLock);
88	B_INITIALIZE_SPINLOCK(&fQueueLock);
89}
90
91
92void
93CPUEntry::Init(int32 id, CoreEntry* core)
94{
95	fCPUNumber = id;
96	fCore = core;
97}
98
99
100void
101CPUEntry::Start()
102{
103	fLoad = 0;
104	fCore->AddCPU(this);
105}
106
107
108void
109CPUEntry::Stop()
110{
111	cpu_ent* entry = &gCPU[fCPUNumber];
112
113	// get rid of irqs
114	SpinLocker locker(entry->irqs_lock);
115	irq_assignment* irq
116		= (irq_assignment*)list_get_first_item(&entry->irqs);
117	while (irq != NULL) {
118		locker.Unlock();
119
120		assign_io_interrupt_to_cpu(irq->irq, -1);
121
122		locker.Lock();
123		irq = (irq_assignment*)list_get_first_item(&entry->irqs);
124	}
125	locker.Unlock();
126}
127
128
129void
130CPUEntry::PushFront(ThreadData* thread, int32 priority)
131{
132	SCHEDULER_ENTER_FUNCTION();
133	fRunQueue.PushFront(thread, priority);
134}
135
136
137void
138CPUEntry::PushBack(ThreadData* thread, int32 priority)
139{
140	SCHEDULER_ENTER_FUNCTION();
141	fRunQueue.PushBack(thread, priority);
142}
143
144
145void
146CPUEntry::Remove(ThreadData* thread)
147{
148	SCHEDULER_ENTER_FUNCTION();
149	ASSERT(thread->IsEnqueued());
150	thread->SetDequeued();
151	fRunQueue.Remove(thread);
152}
153
154
155ThreadData*
156CoreEntry::PeekThread() const
157{
158	SCHEDULER_ENTER_FUNCTION();
159	return fRunQueue.PeekMaximum();
160}
161
162
163ThreadData*
164CPUEntry::PeekThread() const
165{
166	SCHEDULER_ENTER_FUNCTION();
167	return fRunQueue.PeekMaximum();
168}
169
170
171ThreadData*
172CPUEntry::PeekIdleThread() const
173{
174	SCHEDULER_ENTER_FUNCTION();
175	return fRunQueue.GetHead(B_IDLE_PRIORITY);
176}
177
178
179void
180CPUEntry::UpdatePriority(int32 priority)
181{
182	SCHEDULER_ENTER_FUNCTION();
183
184	ASSERT(!gCPU[fCPUNumber].disabled);
185
186	int32 oldPriority = CPUPriorityHeap::GetKey(this);
187	if (oldPriority == priority)
188		return;
189	fCore->CPUHeap()->ModifyKey(this, priority);
190
191	if (oldPriority == B_IDLE_PRIORITY)
192		fCore->CPUWakesUp(this);
193	else if (priority == B_IDLE_PRIORITY)
194		fCore->CPUGoesIdle(this);
195}
196
197
198void
199CPUEntry::ComputeLoad()
200{
201	SCHEDULER_ENTER_FUNCTION();
202
203	ASSERT(gTrackCPULoad);
204	ASSERT(!gCPU[fCPUNumber].disabled);
205	ASSERT(fCPUNumber == smp_get_current_cpu());
206
207	int oldLoad = compute_load(fMeasureTime, fMeasureActiveTime, fLoad,
208			system_time());
209	if (oldLoad < 0)
210		return;
211
212	if (fLoad > kVeryHighLoad)
213		gCurrentMode->rebalance_irqs(false);
214}
215
216
217ThreadData*
218CPUEntry::ChooseNextThread(ThreadData* oldThread, bool putAtBack)
219{
220	SCHEDULER_ENTER_FUNCTION();
221
222	int32 oldPriority = -1;
223	if (oldThread != NULL)
224		oldPriority = oldThread->GetEffectivePriority();
225
226	CPURunQueueLocker cpuLocker(this);
227
228	ThreadData* pinnedThread = fRunQueue.PeekMaximum();
229	int32 pinnedPriority = -1;
230	if (pinnedThread != NULL)
231		pinnedPriority = pinnedThread->GetEffectivePriority();
232
233	CoreRunQueueLocker coreLocker(fCore);
234
235	ThreadData* sharedThread = fCore->PeekThread();
236	ASSERT(sharedThread != NULL || pinnedThread != NULL || oldThread != NULL);
237
238	int32 sharedPriority = -1;
239	if (sharedThread != NULL)
240		sharedPriority = sharedThread->GetEffectivePriority();
241
242	int32 rest = std::max(pinnedPriority, sharedPriority);
243	if (oldPriority > rest || (!putAtBack && oldPriority == rest))
244		return oldThread;
245
246	if (sharedPriority > pinnedPriority) {
247		fCore->Remove(sharedThread);
248		return sharedThread;
249	}
250
251	coreLocker.Unlock();
252
253	Remove(pinnedThread);
254	return pinnedThread;
255}
256
257
258void
259CPUEntry::TrackActivity(ThreadData* oldThreadData, ThreadData* nextThreadData)
260{
261	SCHEDULER_ENTER_FUNCTION();
262
263	cpu_ent* cpuEntry = &gCPU[fCPUNumber];
264
265	Thread* oldThread = oldThreadData->GetThread();
266	if (!thread_is_idle_thread(oldThread)) {
267		bigtime_t active
268			= (oldThread->kernel_time - cpuEntry->last_kernel_time)
269				+ (oldThread->user_time - cpuEntry->last_user_time);
270
271		WriteSequentialLocker locker(cpuEntry->active_time_lock);
272		cpuEntry->active_time += active;
273		locker.Unlock();
274
275		fMeasureActiveTime += active;
276		fCore->IncreaseActiveTime(active);
277
278		oldThreadData->UpdateActivity(active);
279	}
280
281	if (gTrackCPULoad) {
282		if (!cpuEntry->disabled)
283			ComputeLoad();
284		_RequestPerformanceLevel(nextThreadData);
285	}
286
287	Thread* nextThread = nextThreadData->GetThread();
288	if (!thread_is_idle_thread(nextThread)) {
289		cpuEntry->last_kernel_time = nextThread->kernel_time;
290		cpuEntry->last_user_time = nextThread->user_time;
291
292		nextThreadData->SetLastInterruptTime(cpuEntry->interrupt_time);
293	}
294}
295
296
297void
298CPUEntry::StartQuantumTimer(ThreadData* thread, bool wasPreempted)
299{
300	cpu_ent* cpu = &gCPU[ID()];
301
302	if (!wasPreempted || fUpdateLoadEvent)
303		cancel_timer(&cpu->quantum_timer);
304	fUpdateLoadEvent = false;
305
306	if (!thread->IsIdle()) {
307		bigtime_t quantum = thread->GetQuantumLeft();
308		add_timer(&cpu->quantum_timer, &CPUEntry::_RescheduleEvent, quantum,
309			B_ONE_SHOT_RELATIVE_TIMER);
310	} else if (gTrackCoreLoad) {
311		add_timer(&cpu->quantum_timer, &CPUEntry::_UpdateLoadEvent,
312			kLoadMeasureInterval * 2, B_ONE_SHOT_RELATIVE_TIMER);
313		fUpdateLoadEvent = true;
314	}
315}
316
317
318void
319CPUEntry::_RequestPerformanceLevel(ThreadData* threadData)
320{
321	SCHEDULER_ENTER_FUNCTION();
322
323	if (gCPU[fCPUNumber].disabled) {
324		decrease_cpu_performance(kCPUPerformanceScaleMax);
325		return;
326	}
327
328	int32 load = std::max(threadData->GetLoad(), fCore->GetLoad());
329	ASSERT_PRINT(load >= 0 && load <= kMaxLoad, "load is out of range %"
330		B_PRId32 " (max of %" B_PRId32 " %" B_PRId32 ")", load,
331		threadData->GetLoad(), fCore->GetLoad());
332
333	if (load < kTargetLoad) {
334		int32 delta = kTargetLoad - load;
335
336		delta *= kTargetLoad;
337		delta /= kCPUPerformanceScaleMax;
338
339		decrease_cpu_performance(delta);
340	} else {
341		int32 delta = load - kTargetLoad;
342		delta *= kMaxLoad - kTargetLoad;
343		delta /= kCPUPerformanceScaleMax;
344
345		increase_cpu_performance(delta);
346	}
347}
348
349
350/* static */ int32
351CPUEntry::_RescheduleEvent(timer* /* unused */)
352{
353	get_cpu_struct()->invoke_scheduler = true;
354	get_cpu_struct()->preempted = true;
355	return B_HANDLED_INTERRUPT;
356}
357
358
359/* static */ int32
360CPUEntry::_UpdateLoadEvent(timer* /* unused */)
361{
362	CoreEntry::GetCore(smp_get_current_cpu())->ChangeLoad(0);
363	CPUEntry::GetCPU(smp_get_current_cpu())->fUpdateLoadEvent = false;
364	return B_HANDLED_INTERRUPT;
365}
366
367
368CPUPriorityHeap::CPUPriorityHeap(int32 cpuCount)
369	:
370	Heap<CPUEntry, int32>(cpuCount)
371{
372}
373
374
375void
376CPUPriorityHeap::Dump()
377{
378	kprintf("cpu priority load\n");
379	CPUEntry* entry = PeekRoot();
380	while (entry) {
381		int32 cpu = entry->ID();
382		int32 key = GetKey(entry);
383		kprintf("%3" B_PRId32 " %8" B_PRId32 " %3" B_PRId32 "%%\n", cpu, key,
384			entry->GetLoad() / 10);
385
386		RemoveRoot();
387		sDebugCPUHeap.Insert(entry, key);
388
389		entry = PeekRoot();
390	}
391
392	entry = sDebugCPUHeap.PeekRoot();
393	while (entry) {
394		int32 key = GetKey(entry);
395		sDebugCPUHeap.RemoveRoot();
396		Insert(entry, key);
397		entry = sDebugCPUHeap.PeekRoot();
398	}
399}
400
401
402CoreEntry::CoreEntry()
403	:
404	fCPUCount(0),
405	fIdleCPUCount(0),
406	fThreadCount(0),
407	fActiveTime(0),
408	fLoad(0),
409	fCurrentLoad(0),
410	fLoadMeasurementEpoch(0),
411	fHighLoad(false),
412	fLastLoadUpdate(0)
413{
414	B_INITIALIZE_SPINLOCK(&fCPULock);
415	B_INITIALIZE_SPINLOCK(&fQueueLock);
416	B_INITIALIZE_SEQLOCK(&fActiveTimeLock);
417	B_INITIALIZE_RW_SPINLOCK(&fLoadLock);
418}
419
420
421void
422CoreEntry::Init(int32 id, PackageEntry* package)
423{
424	fCoreID = id;
425	fPackage = package;
426}
427
428
429void
430CoreEntry::PushFront(ThreadData* thread, int32 priority)
431{
432	SCHEDULER_ENTER_FUNCTION();
433
434	fRunQueue.PushFront(thread, priority);
435	atomic_add(&fThreadCount, 1);
436}
437
438
439void
440CoreEntry::PushBack(ThreadData* thread, int32 priority)
441{
442	SCHEDULER_ENTER_FUNCTION();
443
444	fRunQueue.PushBack(thread, priority);
445	atomic_add(&fThreadCount, 1);
446}
447
448
449void
450CoreEntry::Remove(ThreadData* thread)
451{
452	SCHEDULER_ENTER_FUNCTION();
453
454	ASSERT(!thread->IsIdle());
455
456	ASSERT(thread->IsEnqueued());
457	thread->SetDequeued();
458
459	fRunQueue.Remove(thread);
460	atomic_add(&fThreadCount, -1);
461}
462
463
464void
465CoreEntry::AddCPU(CPUEntry* cpu)
466{
467	ASSERT(fCPUCount >= 0);
468	ASSERT(fIdleCPUCount >= 0);
469
470	fIdleCPUCount++;
471	if (fCPUCount++ == 0) {
472		// core has been reenabled
473		fLoad = 0;
474		fCurrentLoad = 0;
475		fHighLoad = false;
476		gCoreLoadHeap.Insert(this, 0);
477
478		fPackage->AddIdleCore(this);
479	}
480
481	fCPUHeap.Insert(cpu, B_IDLE_PRIORITY);
482}
483
484
485void
486CoreEntry::RemoveCPU(CPUEntry* cpu, ThreadProcessing& threadPostProcessing)
487{
488	ASSERT(fCPUCount > 0);
489	ASSERT(fIdleCPUCount > 0);
490
491	fIdleCPUCount--;
492	if (--fCPUCount == 0) {
493		// unassign threads
494		thread_map(CoreEntry::_UnassignThread, this);
495
496		// core has been disabled
497		if (fHighLoad) {
498			gCoreHighLoadHeap.ModifyKey(this, -1);
499			ASSERT(gCoreHighLoadHeap.PeekMinimum() == this);
500			gCoreHighLoadHeap.RemoveMinimum();
501		} else {
502			gCoreLoadHeap.ModifyKey(this, -1);
503			ASSERT(gCoreLoadHeap.PeekMinimum() == this);
504			gCoreLoadHeap.RemoveMinimum();
505		}
506
507		fPackage->RemoveIdleCore(this);
508
509		// get rid of threads
510		while (fRunQueue.PeekMaximum() != NULL) {
511			ThreadData* threadData = fRunQueue.PeekMaximum();
512
513			Remove(threadData);
514
515			ASSERT(threadData->Core() == NULL);
516			threadPostProcessing(threadData);
517		}
518
519		fThreadCount = 0;
520	}
521
522	fCPUHeap.ModifyKey(cpu, -1);
523	ASSERT(fCPUHeap.PeekRoot() == cpu);
524	fCPUHeap.RemoveRoot();
525
526	ASSERT(cpu->GetLoad() >= 0 && cpu->GetLoad() <= kMaxLoad);
527	ASSERT(fLoad >= 0);
528}
529
530
531void
532CoreEntry::_UpdateLoad(bool forceUpdate)
533{
534	SCHEDULER_ENTER_FUNCTION();
535
536	if (fCPUCount <= 0)
537		return;
538
539	bigtime_t now = system_time();
540	bool intervalEnded = now >= kLoadMeasureInterval + fLastLoadUpdate;
541	bool intervalSkipped = now >= kLoadMeasureInterval * 2 + fLastLoadUpdate;
542
543	if (!intervalEnded && !forceUpdate)
544		return;
545
546	WriteSpinLocker coreLocker(gCoreHeapsLock);
547
548	int32 newKey;
549	if (intervalEnded) {
550		WriteSpinLocker locker(fLoadLock);
551
552		newKey = intervalSkipped ? fCurrentLoad : GetLoad();
553
554		ASSERT(fCurrentLoad >= 0);
555		ASSERT(fLoad >= fCurrentLoad);
556
557		fLoad = fCurrentLoad;
558		fLoadMeasurementEpoch++;
559		fLastLoadUpdate = now;
560	} else
561		newKey = GetLoad();
562
563	int32 oldKey = CoreLoadHeap::GetKey(this);
564
565	ASSERT(oldKey >= 0);
566	ASSERT(newKey >= 0);
567
568	if (oldKey == newKey)
569		return;
570
571	if (newKey > kHighLoad) {
572		if (!fHighLoad) {
573			gCoreLoadHeap.ModifyKey(this, -1);
574			ASSERT(gCoreLoadHeap.PeekMinimum() == this);
575			gCoreLoadHeap.RemoveMinimum();
576
577			gCoreHighLoadHeap.Insert(this, newKey);
578
579			fHighLoad = true;
580		} else
581			gCoreHighLoadHeap.ModifyKey(this, newKey);
582	} else if (newKey < kMediumLoad) {
583		if (fHighLoad) {
584			gCoreHighLoadHeap.ModifyKey(this, -1);
585			ASSERT(gCoreHighLoadHeap.PeekMinimum() == this);
586			gCoreHighLoadHeap.RemoveMinimum();
587
588			gCoreLoadHeap.Insert(this, newKey);
589
590			fHighLoad = false;
591		} else
592			gCoreLoadHeap.ModifyKey(this, newKey);
593	} else {
594		if (fHighLoad)
595			gCoreHighLoadHeap.ModifyKey(this, newKey);
596		else
597			gCoreLoadHeap.ModifyKey(this, newKey);
598	}
599}
600
601
602/* static */ void
603CoreEntry::_UnassignThread(Thread* thread, void* data)
604{
605	CoreEntry* core = static_cast<CoreEntry*>(data);
606	ThreadData* threadData = thread->scheduler_data;
607
608	if (threadData->Core() == core && thread->pinned_to_cpu == 0)
609		threadData->UnassignCore();
610}
611
612
613CoreLoadHeap::CoreLoadHeap(int32 coreCount)
614	:
615	MinMaxHeap<CoreEntry, int32>(coreCount)
616{
617}
618
619
620void
621CoreLoadHeap::Dump()
622{
623	CoreEntry* entry = PeekMinimum();
624	while (entry) {
625		int32 key = GetKey(entry);
626
627		DebugDumper::DumpCoreLoadHeapEntry(entry);
628
629		RemoveMinimum();
630		sDebugCoreHeap.Insert(entry, key);
631
632		entry = PeekMinimum();
633	}
634
635	entry = sDebugCoreHeap.PeekMinimum();
636	while (entry) {
637		int32 key = GetKey(entry);
638		sDebugCoreHeap.RemoveMinimum();
639		Insert(entry, key);
640		entry = sDebugCoreHeap.PeekMinimum();
641	}
642}
643
644
645PackageEntry::PackageEntry()
646	:
647	fIdleCoreCount(0),
648	fCoreCount(0)
649{
650	B_INITIALIZE_RW_SPINLOCK(&fCoreLock);
651}
652
653
654void
655PackageEntry::Init(int32 id)
656{
657	fPackageID = id;
658}
659
660
661void
662PackageEntry::AddIdleCore(CoreEntry* core)
663{
664	fCoreCount++;
665	fIdleCoreCount++;
666	fIdleCores.Add(core);
667
668	if (fCoreCount == 1)
669		gIdlePackageList.Add(this);
670}
671
672
673void
674PackageEntry::RemoveIdleCore(CoreEntry* core)
675{
676	fIdleCores.Remove(core);
677	fIdleCoreCount--;
678	fCoreCount--;
679
680	if (fCoreCount == 0)
681		gIdlePackageList.Remove(this);
682}
683
684
685/* static */ void
686DebugDumper::DumpCPURunQueue(CPUEntry* cpu)
687{
688	ThreadRunQueue::ConstIterator iterator = cpu->fRunQueue.GetConstIterator();
689
690	if (iterator.HasNext()
691		&& !thread_is_idle_thread(iterator.Next()->GetThread())) {
692		kprintf("\nCPU %" B_PRId32 " run queue:\n", cpu->ID());
693		cpu->fRunQueue.Dump();
694	}
695}
696
697
698/* static */ void
699DebugDumper::DumpCoreRunQueue(CoreEntry* core)
700{
701	core->fRunQueue.Dump();
702}
703
704
705/* static */ void
706DebugDumper::DumpCoreLoadHeapEntry(CoreEntry* entry)
707{
708	CoreThreadsData threadsData;
709	threadsData.fCore = entry;
710	threadsData.fLoad = 0;
711	thread_map(DebugDumper::_AnalyzeCoreThreads, &threadsData);
712
713	kprintf("%4" B_PRId32 " %11" B_PRId32 "%% %11" B_PRId32 "%% %11" B_PRId32
714		"%% %7" B_PRId32 " %5" B_PRIu32 "\n", entry->ID(), entry->fLoad / 10,
715		entry->fCurrentLoad / 10, threadsData.fLoad, entry->ThreadCount(),
716		entry->fLoadMeasurementEpoch);
717}
718
719
720/* static */ void
721DebugDumper::DumpIdleCoresInPackage(PackageEntry* package)
722{
723	kprintf("%-7" B_PRId32 " ", package->fPackageID);
724
725	DoublyLinkedList<CoreEntry>::ReverseIterator iterator
726		= package->fIdleCores.GetReverseIterator();
727	if (iterator.HasNext()) {
728		while (iterator.HasNext()) {
729			CoreEntry* coreEntry = iterator.Next();
730			kprintf("%" B_PRId32 "%s", coreEntry->ID(),
731				iterator.HasNext() ? ", " : "");
732		}
733	} else
734		kprintf("-");
735	kprintf("\n");
736}
737
738
739/* static */ void
740DebugDumper::_AnalyzeCoreThreads(Thread* thread, void* data)
741{
742	CoreThreadsData* threadsData = static_cast<CoreThreadsData*>(data);
743	if (thread->scheduler_data->Core() == threadsData->fCore)
744		threadsData->fLoad += thread->scheduler_data->GetLoad();
745}
746
747
748static int
749dump_run_queue(int /* argc */, char** /* argv */)
750{
751	int32 cpuCount = smp_get_num_cpus();
752	int32 coreCount = gCoreCount;
753
754	for (int32 i = 0; i < coreCount; i++) {
755		kprintf("%sCore %" B_PRId32 " run queue:\n", i > 0 ? "\n" : "", i);
756		DebugDumper::DumpCoreRunQueue(&gCoreEntries[i]);
757	}
758
759	for (int32 i = 0; i < cpuCount; i++)
760		DebugDumper::DumpCPURunQueue(&gCPUEntries[i]);
761
762	return 0;
763}
764
765
766static int
767dump_cpu_heap(int /* argc */, char** /* argv */)
768{
769	kprintf("core average_load current_load threads_load threads epoch\n");
770	gCoreLoadHeap.Dump();
771	kprintf("\n");
772	gCoreHighLoadHeap.Dump();
773
774	for (int32 i = 0; i < gCoreCount; i++) {
775		if (gCoreEntries[i].CPUCount() < 2)
776			continue;
777
778		kprintf("\nCore %" B_PRId32 " heap:\n", i);
779		gCoreEntries[i].CPUHeap()->Dump();
780	}
781
782	return 0;
783}
784
785
786static int
787dump_idle_cores(int /* argc */, char** /* argv */)
788{
789	kprintf("Idle packages:\n");
790	IdlePackageList::ReverseIterator idleIterator
791		= gIdlePackageList.GetReverseIterator();
792
793	if (idleIterator.HasNext()) {
794		kprintf("package cores\n");
795
796		while (idleIterator.HasNext())
797			DebugDumper::DumpIdleCoresInPackage(idleIterator.Next());
798	} else
799		kprintf("No idle packages.\n");
800
801	return 0;
802}
803
804
805void Scheduler::init_debug_commands()
806{
807	new(&sDebugCPUHeap) CPUPriorityHeap(smp_get_num_cpus());
808	new(&sDebugCoreHeap) CoreLoadHeap(smp_get_num_cpus());
809
810	add_debugger_command_etc("run_queue", &dump_run_queue,
811		"List threads in run queue", "\nLists threads in run queue", 0);
812	if (!gSingleCore) {
813		add_debugger_command_etc("cpu_heap", &dump_cpu_heap,
814			"List CPUs in CPU priority heap",
815			"\nList CPUs in CPU priority heap", 0);
816		add_debugger_command_etc("idle_cores", &dump_idle_cores,
817			"List idle cores", "\nList idle cores", 0);
818	}
819}
820
821