Part of the kernel's job is to determine which thread runs and when. First, let's look at when the kernel makes its scheduling decisions.
The execution of a running thread is temporarily suspended whenever the QNX Neutrino microkernel is entered as the result of a kernel call, exception, or hardware interrupt. A scheduling decision is made whenever the execution state of any thread changes—it doesn't matter which processes the threads might reside within. Threads are scheduled globally across all processes.
Normally, the execution of the suspended thread resumes, but the thread scheduler performs a context switch from one thread to another whenever the running thread:
Every thread is assigned a priority. The thread scheduler selects the next thread to run by looking at the priority assigned to every thread that is READY (that is, capable of using the CPU). The thread with the highest priority is selected to run. The following diagram shows the ready queue for five threads (B–F) that are READY. Thread A is currently running. All other threads (G–Z) are BLOCKED. Thread A, B, and C are at the highest priority, so they share the processor based on the running thread's scheduling policy.
The OS supports a total of 256 scheduling priority levels. An unprivileged thread can set its priority to a level from 1 to 63 (the highest priority), independent of the scheduling policy. Only root threads (that is, those whose effective uid is 0) or those with the PROCMGR_AID_PRIORITY ability enabled (see procmgr_ability() ) are allowed to set priorities above 63. The special idle thread (in the process manager) has priority 0 and is always ready to run. A thread inherits the priority of its parent thread by default.
You can change the allowed priority range for unprivileged processes with the procnto -P option:
procnto -P priority
You can append an s or S to this option if you want out-of-range priority requests by default to saturate at the maximum allowed value instead of resulting in an error. When you're setting a priority, you can wrap it in one these (non-POSIX) macros to specify how to handle out-of-range priority requests:
Here's a summary of the ranges:
| Priority level | Owner |
|---|---|
| 0 | Idle thread |
| 1 through priority − 1 | Unprivileged or privileged |
| priority through 255 | Privileged |
Note that to prevent priority inversion, the kernel may temporarily boost a thread's priority. For more information, see Priority inheritance and mutexes, and Priority inheritance and messages. The initial priority of the kernel's threads is 255, but the first thing they all do is block in a MsgReceive() , so after that they operate at the priority of threads that send messages to them.
The threads on the ready queue are ordered by priority. The ready queue is actually implemented as 256 separate queues, one for each priority. The first thread in the highest-priority queue is selected to run.
Most of the time, threads are queued in FIFO order in the queue of their priority, but there are some exceptions:
To meet the needs of various applications, the BlackBerry 10 OS provides these scheduling algorithms:
Each thread in the system may run using any method. The methods are effective on a per-thread basis, not on a global basis for all threads and processes on a node.
Remember that the FIFO and round-robin scheduling policies apply only when two or more threads that share the same priority are READY (that is, the threads are directly competing with each other). The sporadic method, however, employs a budget for a thread's execution. In all cases, if a higher-priority thread becomes READY, it immediately preempts all lower-priority threads.
In the following diagram, three threads of equal priority are READY. If Thread A blocks, Thread B runs.
Although a thread inherits its scheduling policy from its parent process, the thread can request to change the algorithm applied by the kernel.
In FIFO scheduling, a thread selected to run continues executing until it:
In round-robin scheduling, a thread selected to run continues executing until it:
As the following diagram shows, Thread A ran until it consumed its timeslice; the next READY thread (Thread B) now runs:
A timeslice is the unit of time assigned to every process. When it consumes its timeslice, a thread is preempted and the next READY thread at the same priority level is given control. A timeslice is 4 × the clock period. For more information, see ClockPeriod() .
The sporadic scheduling policy is generally used to provide a capped limit on the execution time of a thread within a given period of time. This behavior is essential when Rate Monotonic Analysis (RMA) is being performed on a system that services both periodic and aperiodic events. Essentially, this algorithm allows a thread to service aperiodic events without jeopardizing the hard deadlines of other threads or processes in the system.
As in FIFO scheduling, a thread using sporadic scheduling continues executing until it blocks or is preempted by a higher-priority thread. And as in adaptive scheduling, a thread using sporadic scheduling drops in priority, but with sporadic scheduling you have much more precise control over the thread's behavior.
Under sporadic scheduling, a thread's priority can oscillate dynamically between a foreground or normal priority and a background or low priority. Using the following parameters, you can control the conditions of this sporadic shift:
As the following diagram shows, the sporadic scheduling policy establishes a thread's initial execution budget (C), which is consumed by the thread as it runs and is replenished periodically (for the amount T). When a thread blocks, the amount of the execution budget that's been consumed (R) is arranged to be replenished at some later time (for example, at 40 msec) after the thread first became ready to run.
At its normal priority N, a thread executes for the amount of time defined by its initial execution budget C. As soon as this time is exhausted, the priority of the thread drops to its low priority L until the replenishment operation occurs.
Assume, for example, a system where the thread never blocks or is never preempted:
Here the thread drops to its low-priority (background) level, where it may or may not get a chance to run depending on the priority of other threads in the system.
When the replenishment occurs, the thread's priority is raised to its original level. This guarantees that within a properly configured system, the thread is given the opportunity every period T to run for a maximum execution time C. This ensures that a thread running at priority N consumes only C/T of the system's resources.
When a thread blocks multiple times, then several replenishment operations may be started and occur at different times. This could mean that the thread's execution budget will total C within a period T; however, the execution budget may not be contiguous during that period.
In the diagram above, the thread has a budget (C) of 10 msec to be consumed within each 40-msec replenishment period (T).
And so on. The thread continues to oscillate between its two priority levels, servicing aperiodic events in your system in a controlled, predictable manner.
A thread's priority can vary during its execution, either from direct manipulation by the thread itself or from the kernel adjusting the thread's priority as it receives a message from a higher-priority thread. In addition to priority, you can also select the scheduling algorithm that the kernel uses for the thread. Although our libraries provide a number of different ways to get and set the scheduling parameters, your best choices are pthread_getschedparam() , pthread_setschedparam() , and pthread_setschedprio() . For information about the other choices, see Scheduling policies.