Synchronization

In most cases, the access to a resource that is processing by the other task can cause that the invalid (not yet completely produced) information is obtained. To avoid this situation, a correct synchronization should be applied. Sirius RTOS provides a wide set of synchronization methods, such as controlling critical sections with priority inversion avoidance, events signaling, counting semaphores and timers significantly simplifies applications design and development.

Waiting for object signalization

A system object can be in one of two states: signaled or non-signaled. Each task could be waiting for one or more objects, to check its state. When the task calls one of the system wait functions on a non-signaled object, its execution will be suspended until the object will not change its state to the signaled. When a task waits for many objects, it will be released if at least one of the objects will be in signaled state. When multiple tasks are waiting for the same object, they are queued in order to their priority. If the task performs wait operation on already signaled object, it will continue its execution, and the wait function will return with success immediately.

A task can be released only when both terms are fulfilled: the object is signaled and the task could be running (currently, in the system, tasks with higher priority are not being processed). If the task with the higher priority is executed in the system, and the object changes its state for signaled, waiting task will be started only when all tasks with the higher priority became blocked (see scheduling). If the object will change its state for a non-signaled state, before the task can be started, the wait function will not return until both conditions will not be fulfilled.

To wait for one object, an osWaitForObject function should be performed. To wait for more objects use an osWaitForObjects function. It is only available when the maximal number of the objects (that task could be waiting is greater than one. Use OS_MAX_WAIT_FOR_OBJECTS constant to define the maximal number of the objects that task can be waiting.

The wait functions allow passing a timeout parameter. It defines how long a task should wait for an object. When it is 0 (or AR_TIME_IGNORE), task will only check the object state and then will return immediately. When the object is signaled, it will return with success, otherwise will fail and set the last error code to ERR_WAIT_TIMEOUT. Specifying an AR_TIME_INFINITE as a timeout will cause that the task waits until the object will change its state into signaled, a timeout is ignored. All other value passed to wait function is a timeout specified in a time units. Values other than 0, AR_TIME_IGNORE and AR_TIME_INFINITE may be specified only, when the OS_USE_WAITING_WITH_TIME_OUT is set to 1. The task will wait until object state is changed to signaled or specified timeout elapses. For more information on time units, look into following section: "Time and timeouts".

Sleeping

Each task can suspend its execution for a specified amount of time, by calling osSleep function. When a task begins sleeping, it will be executed the scheduler that decides which task should run as the next. When the specified sleep time is 0, a task will voluntarily yield its execution. Values other than 0 and AR_TIME_IGNORE may be specified only, when the OS_USE_WAITING_WITH_TIME_OUT is set to 1.

For more information on the time, please look into following section "Time and timeouts".

Critical sections

Mutexes and semaphores are a special kind of objects used to control the accesses to critical sections that provides a priority inversion avoidance mechanism, deadlock detection and critical section abandon control. The semaphores are dedicated only to control the critical sections. For a general use, a faster counting semaphores should be applied. The basic difference between mutex and semaphore is that the mutex can be owned only by one task, but the semaphore can be acquired many times. Another difference is when the task starts waiting for a mutex already owned by this task, it will acquire this mutex without waiting. For a semaphore, a task can acquire it many times, and will wait if semaphore is non-signaled (even if it was already acquired by the task).

The incorrect situation is when the task owning a critical section is terminated or osCloseHandle function is called for owned critical section. It can mean that the resource protected by critical section may be incoherent (e.g. results were not completely produced). In this case the object is automatically released by kernel as after using osReleaseMutexor osReleaseSemaphore for semaphore. If a task acquires abandoned mutex, it will own it, however the wait operation will fail and a last error code will be set for ERR_WAIT_ABANDON. This situation may be ignored only when you are certain that it is safe, however it is treated as an error. This information can be used to perform recovering.

Another dangerous situation is priority inversion. When the task with a low priority (L) owns a critical section and a task with a middle priority (M) is ready, execution of the task L will be stopped and task M will be performed. If during this time the task with a high priority (H) is ready and tries to acquire the critical section, it will be blocked, because it is already owned by L task. In this case, task M will be resumed. When the kernel detects priority inversion, it will use a priority inheritance algorithm to immediately increase a priority of the task owning critical section to the highest priority of tasks waiting for this critical section. This allows running a task L that finishes the performed operation and releases the critical section. After that the original task L priority is restored and the task H can run to acquire the critical section.

In worse designed systems a priority inversion can occurs in the chain between many tasks owning different critical sections and tasks waiting for it. In this case the kernel expects a linear time to update a priority for many tasks that it requires. It is dangerous for a real-time system, and should be avoided by proper system designing.

During the acquiring the critical section, the deadlock can occur. This situation occurs in wrong designed systems. Sirius RTOS can detect a deadlock when it occurs in the critical section regions controlled by mutexes and semaphores. In this situation the wait operation will be finished and the last error code will be set for ERR_WAIT_DEADLOCK.

If many tasks are waiting for mutex (or semaphore), as first a task with highest priority will acquire the critical section. If all waiting tasks have the lower priority, than the task that has just released a critical section, they will be let in, when they are ready to run. If the other task with the higher priority (also task that has just left mutex) starts waiting for it, it will acquire the mutex immediately, as it has the highest priority than the waiting tasks. If the tasks that try to acquire the mutex have the same or lower priority, it will be added at the end of the pending tasks queue.

Mutexes and semaphores should be used where it is required the priority inversion protection. Well designed applications should be written against priority inversion that should never occur. If you are sure that a priority inversion will not occurs, it is better to use auto-reset eventsthat behave in a similar way to mutexes and faster counting semaphores, in place of semaphores.

Auto-reset feature

Some objects e.g. events or timers have an auto-reset feature. If many tasks are waiting for an auto-reset object and this object state is changed into signaled, only the first task will be let in. After it the object state changes into non-signaled state. All remaining tasks will be let in one by one when this state is changed again into signaled.

Similarly to mutexes and semaphores, when many tasks are waiting for the object that changes its state into signaled, only the first task with the highest priority will be let in. If the other task, with the higher than the highest priority of tasks waiting for the object, performs one of the system wait functions, the task will not be blocked, and the object state will be changed into non-signaled. Otherwise, if this task has the same or lower priority, it will be blocked and added into the end of the pending tasks queue. This time the first task in the queue will run.

Timers

Timers are objects that change its state to signaled, after specified amount of time. When the timer is reset it changes state to non-signaled and remains in this state until specified amount of time elapses. Timers can be reset only the specified number of times or they can be periodical timers that never expire. The additional auto-reset feature enables to automatically reset the timer, when one of the waiting tasks will run. For more information about timers, please refer to the timers section. For more information on time, see below to following "Time and timeouts" section.

Time and timeouts

A time passed to osWaitForObject, osWaitForObjects and osSleep functions or to timerobjects is specified in time units that depend on value stored in AR_TICKS_PER_SECONDconstant. When a port files provided by SpaceShadow are used, it is set to 1000 that responds to milliseconds. A waiting task can be released only when the scheduler is executed. If it is executed on every 10 millisecond, specifying osSleep(1), will release a task after less than 10 milliseconds. Generally in real-time applications, scheduler should be executed on every millisecond or more often.

SpaceShadow documentation