Thread scheduling

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:

When is a thread blocked?
The running thread is blocked when it must wait for some event to occur (response to an IPC request, wait on a mutex, and so on.). The blocked thread is removed from the running array and the highest-priority ready thread is then run. When the blocked thread is subsequently unblocked, it's placed on the end of the ready queue for that priority level.
When is a thread preempted?
The running thread is preempted when a higher-priority thread is placed on the ready queue (it becomes READY, as the result of its block condition being resolved). The preempted thread is put at the beginning of the ready queue for that priority and the higher-priority thread runs.
When is a thread yielded?
The running thread voluntarily yields the processor ( sched_yield() ) and is placed on the end of the ready queue for that priority. The highest-priority thread then runs (which may still be the thread that just yielded).

Scheduling priority

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.

Figure showing READY processes.

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:

  • SCHED_PRIO_LIMIT_ERROR(priority) — indicate an error
  • SCHED_PRIO_LIMIT_SATURATE(priority) — saturate at the maximum allowed priority

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:

  • A server thread that's coming out of a RECEIVE-blocked state with a message from a client is inserted at the head of the queue for that priority—that is, the order is LIFO, not FIFO.
  • If a thread sends a message with an nc (non-cancellation point) variant of MsgSend*(), then when the server replies, the thread is placed at the front of the ready queue, rather than at the end. If the scheduling policy is round-robin, the thread's timeslice isn't replenished; for example, if the thread had already used half its timeslice before sending, then it still has only half a timeslice left before being eligible for preemption.

Scheduling policies

To meet the needs of various applications, the BlackBerry 10 OS provides these scheduling algorithms:

  • FIFO scheduling
  • Round-robin scheduling
  • Sporadic scheduling

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.

Figure showing scheduling of equal-priority threads.

Although a thread inherits its scheduling policy from its parent process, the thread can request to change the algorithm applied by the kernel.

FIFO scheduling

In FIFO scheduling, a thread selected to run continues executing until it:

  • Voluntarily relinquishes control (for example, it blocks)
  • Is preempted by a higher-priority thread
Figure showing FIFO scheduling.

Round-robin scheduling

In round-robin scheduling, a thread selected to run continues executing until it:

  • Voluntarily relinquishes control
  • Is preempted by a higher-priority thread
  • Consumes its timeslice

As the following diagram shows, Thread A ran until it consumed its timeslice; the next READY thread (Thread B) now runs:

Figure showing round-robin scheduling.

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() .

Note: Apart from time slicing, round-robin scheduling is identical to FIFO scheduling.

Sporadic scheduling

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:

Initial budget (C)
The amount of time a thread is allowed to execute at its normal priority (N) before being dropped to its low priority (L).
Low priority (L)
The priority level to which the thread drops. The thread executes at this lower priority (L) while in the background, and runs at normal priority (N) while in the foreground.
Replenishment period (T)
The period of time during which a thread is allowed to consume its execution budget. To schedule replenishment operations, the POSIX implementation also uses this value as the offset from the time the thread becomes READY.
Max number of pending replenishments
This value limits the number of replenishment operations that can take place, thereby bounding the amount of system overhead consumed by the sporadic scheduling policy.
Note: In a poorly configured system, a thread's execution budget may become eroded because of too much blocking—that is, it won't receive enough replenishments.

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.

Figure showing sporadic scheduling.

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:

Figure showing sporadic scheduling.

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.

Figure showing sporadic scheduling.

In the diagram above, the thread has a budget (C) of 10 msec to be consumed within each 40-msec replenishment period (T).

  1. The initial run of the thread is blocked after 3 msec, so a replenishment operation of 3 msec is scheduled to begin at the 40-msec mark, that is, when its first replenishment period has elapsed.
  2. The thread gets an opportunity to run again at 6 msec, which marks the start of another replenishment period (T). The thread still has 7 msec remaining in its budget.
  3. The thread runs without blocking for 7 msec, thereby exhausting its budget, and then drops to low priority L, where it may or may not be able to execute. A replenishment of 7 msec is scheduled to occur at 46 msec (40 + 6), that is, when T has elapsed.
  4. The thread has 3 msec of its budget replenished at 40 msec (see Step 1) and is therefore boosted back to its normal priority.
  5. The thread consumes the 3 msec of its budget and then is dropped back to the low priority.
  6. The thread has 7 msec of its budget replenished at 46 msec (see Step 3) and is boosted back to its normal priority.

And so on. The thread continues to oscillate between its two priority levels, servicing aperiodic events in your system in a controlled, predictable manner.

Manipulating priority and scheduling policies

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.