Notice: We have migrated to GitLab launching 2024-05-01 see here: https://gitlab.rtems.org/

#3359 closed defect (fixed)

4.10: Improved Priority Inheritance Protocol

Reported by: Gedare Bloom Owned by: Gedare Bloom
Priority: normal Milestone: 4.10.3
Component: score Version: 4.10
Severity: normal Keywords:
Cc: Blocked By:
Blocking:

Description (last modified by Gedare Bloom)

See also #2412.

Problem

The RTEMS mutexes implement only a very simple approximation of the priority inheritance protocol. The real priority of a thread is only restored once it releases its last mutex. Lets consider this scenario. We have a file system instance protected by one mutex and a dynamic memory allocator protected by another mutex. A low priority thread performs writes some log data into a file, thus it acquires the file system instance mutex. The file system allocates dynamic memory. Now a high priority thread interrupts and tries to allocate dynamic memory. The allocator mutex is already owned, so the priority of the low priority thread is raised to the priority of the high priority thread. The memory allocation completes and the allocator mutex is released, since the low priority thread still owns the file system instance mutex it continues to execute with the high priority (the high priority thread is not scheduled). It may now perform complex and long file system operations (e.g. garbage collection, polled flash erase and write functions) with a high priority.

Functional requirements

  • The mutex shall use the priority inheritance protocol to prevent priority inversion.
  • The mutex shall allow vertical nesting (a thread owns multiple mutexes).
  • The mutex shall allow horizontal nesting (a thread waits for ownership of a mutex those owner waits for ownership of a mutex, and so on).
  • Threads shall wait in priority order. The highest priority thread shall be dequeued first.
  • The highest priority waiting thread shall wait in FIFO order.
  • The mutex shall provide an acquire operation with timeout.
  • In case a mutex is released, then the previous owner shall no longer use the priorities inherited by this mutex.
  • In case a mutex acquire operation timeout occurs, then the current owner of the mutex shall no longer use the priorities inherited by the acquiring thread.
  • The order of the mutex release operations may differ from the order of the mutex acquire operations.
  • Priority changes not originating due to the priority inheritance protocol shall take place immediately.

Performance requirements

  • The mutex acquire operation shall use only object-specific locks in case the mutex is not owned currently.
  • The mutex release operation shall use only object-specific locks in case no threads wait for ownership of this mutex.

Invariants

  • A mutex shall be owned by at most one thread.
  • A thread shall wait for ownership of at most one mutex.

Proposed Implementation

Use a recursive data structure to determine the highest priority available to a thread for each scheduler instance, e.g.

typedef struct Thread_Priority_node {
  Chain_Node Node;
  Priority_Control current_priority;
  Priority_Control real_priority;
  struct CORE_mutex_Control *waiting_to_hold;
  Chain_Control Inherited_priorities;
} Thread_Priority_node;

typedef struct {
  ...
  Thread_Priority_node Priority_node;
  ...
} Thread_Control;

Initially a thread has a priority node reflecting its real priority. The Thread_Priority_node::owner is NULL. The Thread_Priority_node::current_priority is set to the real priority. The Thread_Priority_node::Inherited_priorities is empty.

In case the thread must wait for ownership of a mutex, then it enqueues its priority node in Thread_Priority_node::Inherited_priorities of the mutex owner.

In case the thread is dequeued from the wait queue of a mutex, then it dequeues its priority node in Thread_Priority_node::Inherited_priorities of the previous mutex owner (ownership transfer) or the current mutex owner (acquire timeout).

In case the minimum of Thread_Priority_node::real_priority and Thread_Priority_node::Inherited_priorities changes, then Thread_Priority_node::current_priority is updated. In case the Thread_Priority_node::owner its not NULL, the priority change propagates to the owner, and so on. In case Thread_Priority_node::current_priority changes, the scheduler is notified.

Change History (4)

comment:1 Changed on 03/23/18 at 14:37:00 by Gedare Bloom

Description: modified (diff)

comment:2 Changed on 03/23/18 at 14:37:11 by Gedare Bloom

Status: assignedaccepted

comment:3 Changed on 04/02/18 at 03:16:10 by Gedare Bloom <gedare@…>

Resolution: fixed
Status: acceptedclosed

In [changeset:"b50468c6bff4e770aa038b9f87e514a2a1da906b/rtems" b50468c/rtems]:

score: add Inherited_priorities priority queue and functions

Adds enqueue, dequeue, requeue, evaluate, and release functions
for the thread priority node priority queue of inherited priorities.
Add calls to these functions as needed to maintain the priority
queue due to blocking, unblocking, and priority changes.

Closes #3359.

comment:4 Changed on 01/03/20 at 18:11:27 by Gedare Bloom <gedare@…>

In [changeset:"e3f6d35f65a49a2e5f79ddba0645bb9b7e51f182/rtems" e3f6d35/rtems]:

cpukit/score: avoid NULL and races in priority mutex

The PIP modifications from #3359 introduced new data structures
to track priority inheritance. Prioritized mutexes without PIP
share some of the code paths, and may result in NULL pointer
accesses. This patch checks for NULL, and also adds ISR critical
sections to an uncovered corner case during thread restarts.

Closes #3829.

Note: See TracTickets for help on using tickets.