|
Once the additional processors have been released and are
running, all processors are considered peers for the scheduling of threads.
Scheduling
The scheduling algorithm follows the same rules as on a
uniprocessor system. That is, the highest-priority threads will be running on the available processors. If a
new thread becomes ready to run, it will be dispatched to the processor running the lowest-priority
thread.
If more than one processor is selected as a potential target,
then the microkernel will try to dispatch the thread to the processor where it last ran. This affinity is used
as an attempt to reduce thread migration, which can affect cache performance.
Hard processor affinity
Neutrino also supports the concept of hard processor affinity
through the kernel call ThreadCtl(_NTO_TCTL_RUNMASK, runmask). Each set bit in
runmask represents a processor that a thread can run on. By default, a thread's runmask is set to
all ones, allowing it to run on any processor. A value of 0x01 would allow a
thread to execute only on the first processor. By careful use of this primitive, a systems designer can further
optimize the runtime performance of a system (e.g. by relegating non-realtime processes to a specific
processor). In general, however, this shouldn't be necessary, because Neutrino's realtime scheduler will always
preempt a lower-priority thread immediately when a higher-priority thread becomes ready. Processor locking will
likely affect only the efficiency of the cache, since threads can be prevented from migrating.
Kernel locking
In a uniprocessor system, only one thread is allowed to
execute within the microkernel at a time. Most kernel operations are short in duration (typically a few
microseconds on a Pentium-class processor). The microkernel is also designed to be completely preemptable and
restartable for those operations that take more time. This design keeps the microkernel lean and fast without
the need for large numbers of fine-grained locks. It is interesting to note that placing many locks in the main
code path through a kernel will noticeably slow the kernel down. Each lock introduces at least one conditional
branch, which can cause processor stalls.
In an SMP system, Neutrino maintains this philosophy of only
one thread in a preemptable and restartable kernel. The microkernel may be entered on any processor, but only
one processor will be granted access at a time.
For most systems, the time spent in the microkernel represents
only a small fraction of the processor's workload. Therefore, while conflicts will occur, they should be more
the exception than the norm. This is especially true for a microkernel where traditional OS services like
filesystems are separate processes and not part of the kernel itself.
Inter-processor interrupts (IPIs)
The processors communicate with each other through IPIs
(inter-processor interrupts). IPIs can effectively schedule and control threads over multiple processors. For
example, an IPI to another processor is often needed when:
- a higher-priority thread becomes ready
- a thread running on another processor is hit with a signal
- a thread running on another processor is canceled
- a thread running on another processor is destroyed.
Here's the small set of IPIs used by Neutrino:
| Type |
Description |
| IPI reschedule |
Force a processor to examine the thread it's currently executing. Thread preemption,
signals, cancellation, and death are acted upon as needed. |
| IPI kernel preempt |
Preempt a kernel running on another processor. |
| IPI TLB flush |
Flush the TLB (translation look-aside buffer) on the target processor as a result of a
change to the MMU memory mapping. |
|