Priority inheritance proposal

The idea of the ICPP is to associate a priority to each mutex (this priority should be more or less static, we can talk further about how to choose this priority). When a task locks a mutex, it also acquires the priority associated with the mutex (so there is no need to block another task to increase the priority). When the task release the mutex, it get the highest priority of the mutexes it still holds.

So, I think the list of held mutexes is indeed necessary to implement it. However, ICPP is considered far simpler than PI. BTW, I am not very comfortable with the assumption the task will hold the mutexes for a short time. I think I would prefer to spend a few bytes of memory and to not have any limitations.

I would think that assigning execution priority to mutexes is going to make assignments of priorities much harder if your goal is to establish that real time requirements will be met.

First, fundamentally, no task should try to take a mutex with a lower priority than its own, as then it is subject to priority inversion, as a task between the two priorities will run “to completion” over the lower priority task holding the mutex, delaying the higher priority task.

Putting the holder of the mutex at that high priority aways means that the lower priority task will be blocking the mid priority task even when the high priority task isn’t needing the resource.

And it isn’t just “a few bytes”, you need a list for each task of every possible mutex it is currently holding, and every mutex might be on every tasks list, so FreeRTOS’s current embedded list isn’t applicable. You are goin to need to define a value of the maximum number of mutexes any task can hold at a time, and pre allocate an array for that many pointers to every tasks. Remember, many embedded systems have design rules that prohibit dynamic memory allocations after the system has initialized itself, which makes m-n maps very difficult and expensive.

And yes, FULL PI is complicated, but FreeRTOS doesn’t implement a full PI, but a specifically simplified version that avoids the major complexity, at the cost of a task occasionally inheriting a priority “too long” which can be mitigated by not holding mutexes too long.

I suspect that in the embedded world, where mutexes are mostly guarding hardware devices and simple communication buffers, the hold time for mutexes will be short. ICPP seems to be a method designed for “bigger” machines where memory is a largely “free” resource, at least for small system object like this, and the dynamic use of memory is expected.

I have no need for changes, but would appreciate some clarification.

It is known from mastupristi et-al that a Low Priority Task owning a Semaphore and Taking a Semaphore of a High Priority Task will have its Priority raised if the High Priority Task blocks by Taking its (High Priority Task Semaphore). The Low Priority Task will Inherit the priority of the Priority Task until both Semaphores are given and only then will the High Priority Task be unblocked.

  • What happens if multiple Semaphores were taken by the Low Priority Task?
    For example, must all Semaphores be given?
  • What happens if the Low Priority Task deletes one of the other Semaphores while having Taken the Semaphore of the High Priority Task?

I am truly sorry I do NOT currently have adequate resources to test the above scenarios; easily. If possible, an answer would be appreciated

These changes necessary because otherwise your statement does not make sense.

FreeRTOS simplifies priority inheritance by not dropping the priority of a task which inherited priority until it releases all Mutexes that it holds. This does allow an extension of the time the tasks has elevated priority. This isn’t that big of an issue, as low prioirty tasks shouldn’t be holding onto multiple mutexes for long, if at all. Also, the High Priority task will still round-robin with the low priority task, so isn’t blocked forever.

If I remember right, deleting a Semaphore/Mutex that has a task waiting on it invokes undefined behavior, because either that task must NEVER be woken, or when it is, it will try to access a no longer existent object. Basic rule, you can’t destroy a shared object until it is no longer shared and you know that everyone that it had been shared with will not try to access it any longer. There is no attempt by FreeRTOS to handle complicated lifetime management of such shared resources. That is part of what makes it small and efficient.

Thank you for responding. I see a Low Priority Task access a number of Mutexes to read a value directly from a High Priority Task. The inheritance works its way to the Low Priority Task. If I test the Mutex from the High Priority Task first, that seems to put an end to it. Thanks for the confirmation! It would appear there is no way to simply disable the inheritance?

If you don’t want the inheritance, then just use a semaphore. The inheritance is there because of the priority inversion problem, which is an actual problem if your priorities are set correctly.

The issue of the inheritance lasting longer than needed with multiple mutexes normally isn’t that big of an issue. I rarely find code holding multiple mutexes which is a precondition to having the problem.

Thanks for patience and information. What I hope is a final scenario question.

  1. Low Priority Task takes some number of Mutex Semaphores and at some point takes a Mutex Semaphore shared with a High Priority Task.

  2. High Priority Task is run and attempts to take the shared Mutex Semaphore and blocks. The Low Priority Task is promoted to the Priority of the High Priority Task.

  3. Here I must make an assumption some kind of list exists for all High Priority Ready Tasks so that round robin will be preserved.

Question: Does the newly promoted High Priority Task move to the end (last entry) in the list?

Thank you again for the help.

For 3, FreeRTOS fundamentally has a list of all ready tasks, in fact, it has a separate list for each priority, and runs the tasks at the highest priority list that has any members on it, and if all of them block, it then goes and finds the next highest priority with ready tasks.

As to where the task with inherited priority goes on the list, I am not sure, but I think it is the end (as newly ready tasks at a given level tend to be added to the end). But, since the first task blocked, unless there is another task at that priority level also ready to run, it will end up running.

1 Like

It would appear we have chosen to prevent priority inheritance by changing to binary semaphores. We had several layers of semaphore takes originating from a low priority task. In certain cases the inheritance would “cascade”. The high priority task would be held off, and the low priority tasks would retain the high priority until the last mutex was given.

I want to be certain the change from mutex semaphore to binary semaphore will not create an issue when multiple tasks perform takes. When the semaphore is owned (taken) by any task, high or low priority. Will any tasks of any priority attempting to take the semaphore become blocked in order and await a give by the current owner? Each blocked task taking ownership in turn? I ask this question because this is a binary semaphore and does not count. It may be possible that FreeRTOS will queue tasks attempting to take ownership in such a way that multiple tasks can compete for a single resource.

When multiple tasks block on a binary semaphore waiting for it, the highest priority one will get it when next released, and I believe that tasks of the same priority will be served in order of their requests.

Using Semaphores instead of Mutexs says that there will be no priority inheritance so you might run into priority inversion, but will prevent the over-extended inheritance that FreeRTOS happens to have.

1 Like