Priority Inhertance Vs Priority Ceiling

Hey folks in this article we are going to discuss the difference between Priority Inheritance Protocol and Priority ceiling protocol

In real-time operating systems (RTOS), there are two common mechanisms used for managing priority inversion are priority ceiling and priority inheritance. 

 What Is Priority Inversion ?

Priority inversion happens when a low-priority task holds a shared resource that a high-priority task needs, causing the high-priority task to wait and potentially miss its deadline. Both priority ceiling and priority inheritance aim to prevent priority inversion and ensure that high-priority tasks can execute as expected. However, these mechanisms differ in their implementation and effectiveness.

 

How To Solve Priority Inversion Problem ? 

There Are Two ways : 

  • Priority inheritance protocol (PIP) : Is a technique where the priority of a task that holds a shared resource is temporarily raised to the highest priority of any task waiting for the resource. This ensures that the highest-priority task waiting for the resource can access it as soon as possible, without being blocked by a lower-priority task. Once the task holding the resource releases it, its priority is lowered back to its original priority .

    
    #include 
    #include 
    #include 
    
    #define MAX_THREADS 5
    
    
    pthread_mutex_t mutex1;
    pthread_mutex_t mutex2;
    pthread_mutex_t mutex3;
    
    
    void task1();
    void task2();
    void task3();
    
    
    int main() {
        int i;
        pthread_t threads[MAX_THREADS];
        // Initialize mutexes
        pthread_mutex_init(&mutex1, NULL);
        pthread_mutex_init(&mutex2, NULL);
        pthread_mutex_init(&mutex3, NULL);
        // Create threads
        pthread_create(&threads[0], NULL, (void *)task1, NULL);
        pthread_create(&threads[1], NULL, (void *)task2, NULL);
        pthread_create(&threads[2], NULL, (void *)task3, NULL);
        // Wait for threads to finish
        for (i = 0; i < MAX_THREADS; i++) {
            pthread_join(threads[i], NULL);
        }
        // Destroy mutexes
        pthread_mutex_destroy(&mutex1);
        pthread_mutex_destroy(&mutex2);
        pthread_mutex_destroy(&mutex3);
        return 0;
    }
    
    
    void task1() {
        // Lock mutex 1
        pthread_mutex_lock(&mutex1); 
        // Do some work
        // Lock mutex 2
        pthread_mutex_lock(&mutex2);
        // Do some more work
        // Unlock mutex 2
        pthread_mutex_unlock(&mutex2);
        // Unlock mutex 1
        pthread_mutex_unlock(&mutex1);
    }
    
    
    void task2() {
        // Lock mutex 2
        pthread_mutex_lock(&mutex2);
        // Do some work
        // Lock mutex 3
        pthread_mutex_lock(&mutex3);
        // Do some more work 
        // Unlock mutex 3
        pthread_mutex_unlock(&mutex3);
        // Unlock mutex 2
        pthread_mutex_unlock(&mutex2);
    }
    
    
    void task3() {
        // Lock mutex 3
        pthread_mutex_lock(&mutex3);
        // Do some work
        // Lock mutex 1
        pthread_mutex_lock(&mutex1);
        // Do some more work
        // Unlock mutex 1
        pthread_mutex_unlock(&mutex1);   
        // Unlock mutex 3
        pthread_mutex_unlock(&mutex3);
    
    }
    

     

    In this example, we have three tasks, each of which locks two mutexes in a specific order. Without priority inheritance, a priority inversion could occur if task 1 (which has a lower priority than task 2) locks mutex 1 and then gets preempted by task 2, which locks mutex 2. Since task 1 is waiting for mutex 2 to be unlocked, it will be blocked until task 2 finishes. This can result in a missed deadline and degraded system performance/

    To prevent priority inversion, we can use priority inheritance. We can set the priority of task 1 to be the same as task 2 while it is holding mutex 2. This will ensure that task 1 is not preempted by task 2 and can complete its work without delay.  

    Note that this is a basic example and there are many variations of the priority inheritance algorithm that can be used depending on the specific needs of the system. Also, this implementation assumes that the tasks have been assigned priorities beforehand, which is typically done using the operating system's task scheduler.

  • Priority ceiling protocol (PCP) : Is a technique that assigns a priority ceiling to each shared resource. The priority ceiling is the highest priority of any task that can access the resource. When a task tries to access a shared resource, its priority is temporarily raised to the priority ceiling of the resource. This prevents lower-priority tasks from blocking higher-priority tasks that need to access the same resource. Priority ceiling is generally more effective than priority inheritance in preventing priority inversion because it eliminates the possibility of a task being blocked by a lower-priority task holding a shared resource. However, priority ceiling requires more overhead in terms of memory and computation, as it requires assigning a priority ceiling to each shared resource. Priority inheritance, on the other hand, is simpler to implement and requires less overhead, but is not always effective in preventing priority inversion.
  • #include 
    #include 
    #include 
    
    #define MAX_THREADS 5
    
    
    pthread_mutex_t mutex1;
    pthread_mutex_t mutex2;
    pthread_mutex_t mutex3;
    
    
    void task1();
    void task2();
    void task3();
    
    
    int main() {
    
        int i;
        pthread_t threads[MAX_THREADS];
        // Initialize mutexes
        pthread_mutex_init(&mutex1, NULL);
        pthread_mutex_init(&mutex2, NULL);
        pthread_mutex_init(&mutex3, NULL);
    
        // Create threads
        pthread_create(&threads[0], NULL, (void *)task1, NULL);
        pthread_create(&threads[1], NULL, (void *)task2, NULL);
        pthread_create(&threads[2], NULL, (void *)task3, NULL);
    
    
        // Wait for threads to finish
        for (i = 0; i < MAX_THREADS; i++) {
            pthread_join(threads[i], NULL);
        }
    
    
        // Destroy mutexes
        pthread_mutex_destroy(&mutex1);
        pthread_mutex_destroy(&mutex2);
        pthread_mutex_destroy(&mutex3);
    
    
        return 0;
    
    }
    
    
    void task1() {
        // Lock mutex 1
        pthread_mutex_lock(&mutex1); 
        // Do some work
    
        // Lock mutex 2
        pthread_mutex_lock(&mutex2);  
        // Raise priority to that of task 2
        pthread_mutex_lock(&mutex2);
        // Do some more work  
        // Unlock mutex 2
        pthread_mutex_unlock(&mutex2);    
        // Unlock mutex 1
        pthread_mutex_unlock(&mutex1);
    }
    
    void task2() {
       // Lock mutex 2
       pthread_mutex_lock(&mutex2);
       // Do some work   // Lock mutex 3
       pthread_mutex_lock(&mutex3);    
       // Do some more work
       // Unlock mutex 3
       pthread_mutex_unlock(&mutex3); 
       // Unlock mutex 2
       pthread_mutex_unlock(&mutex2);
    }
    
    
    void task3() {
        // Lock mutex 3
        pthread_mutex_lock(&mutex3);
        // Do some work
        // Lock mutex 1
        pthread_mutex_lock(&mutex1);    
        // Raise priority to that of task 2
        pthread_mutex_lock(&mutex2);    
        // Do some more work    
        // Unlock mutex 2
        pthread_mutex_unlock(&mutex2);
        // Unlock mutex 1
        pthread_mutex_unlock(&mutex1);
        // Unlock mutex 3
        pthread_mutex_unlock(&mutex3);
    }

    In this modified code, when task 1 locks mutex 2, it also locks mutex 2 again to raise its priority to that of task 2. This ensures that task 1 is not preempted by task 2 and can complete its work without delay. Similarly, when task 3 locks mutex 1, it also locks mutex 2 again to raise its priority to that of task 2. This ensures that task 3 is not preempted by task 1 and can complete its work without delay.


The main difference between priority ceiling and priority inheritance is that priority ceiling is a static protocol while priority inheritance is a dynamic protocol. In priority ceiling, the priority ceiling for each shared resource is fixed at design time and cannot be changed during runtime. In contrast, priority inheritance dynamically adjusts the priority of tasks based on the current state of the system.

One factor to consider is the complexity of the system. Priority ceiling is generally more complex to implement than priority inheritance, as it requires assigning a priority ceiling to each shared resource. This can be challenging in systems with a large number of shared resources or complex resource dependencies. Priority inheritance, on the other hand, is simpler to implement and can be more suitable for simpler systems.

Another factor to consider is the level of control over task priorities. Priority ceiling provides a higher level of control over task priorities than priority inheritance, as the priority of a task is always fixed to the priority ceiling of the resource it is accessing. This can make it easier to ensure that high-priority tasks are always given priority over lower-priority tasks. Priority inheritance, on the other hand, can result in unpredictable changes in task priorities, which can make it more difficult to ensure that high-priority tasks are always given priority.


How To Choose Between Priority Inhertance and Priority Ceiling ? 

The choice between priority ceiling and priority inheritance will depend on the specific requirements of the system. Both techniques are effective in preventing priority inversion, but have different strengths and weaknesses. Priority ceiling is generally more effective in preventing priority inversion but requires more overhead, while priority inheritance is simpler to implement but may not always be effective in preventing priority inversion. It is important to carefully consider the needs of the system and choose the technique that best meets those needs.

Another factor to consider when choosing between priority ceiling and priority inheritance is the potential for deadlock. Deadlock is a situation where two or more tasks are blocked waiting for each other to release shared resources, resulting in a system that is unable to make progress. Deadlock can occur in systems that use priority inheritance if there is a cycle of task dependencies, where each task is waiting for a shared resource held by another task in the cycle. Priority ceiling can help to prevent deadlock, as it ensures that each task has a maximum priority that it can raise to when accessing a shared resource. This can prevent a cycle of task dependencies from forming.

Another factor to consider is the predictability of the system. Priority ceiling can provide more predictability in real-time systems, as it guarantees that a high-priority task will not be blocked by a lower-priority task holding a shared resource. This can make it easier to analyze the timing behavior of the system and ensure that all tasks meet their deadlines. Priority inheritance, on the other hand, can result in unpredictable changes in task priorities, which can make it more difficult to analyze the timing behavior of the system.



Some examples of real-time systems that commonly use priority ceiling or priority inheritance:


  1. Aerospace and Defense Systems: Many aerospace and defense systems use real-time operating systems with priority ceiling or priority inheritance to ensure that critical tasks are executed on time and with high reliability. Examples include flight control systems, missile guidance systems, and satellite control systems.
  2. Automotive Systems: Real-time operating systems are also used in automotive systems to control various functions such as engine management, anti-lock braking systems, and airbag deployment. These systems require high reliability and safety, and priority ceiling or priority inheritance is often used to prevent priority inversion and ensure timely execution of critical tasks.
  3. Medical Devices: Real-time operating systems are used in a variety of medical devices, such as patient monitors, infusion pumps, and surgical robots. These systems require high reliability and accuracy, and priority ceiling or priority inheritance is often used to prevent priority inversion and ensure timely execution of critical tasks.
  4. Industrial Control Systems: Real-time operating systems are used in a variety of industrial control systems, such as factory automation systems, process control systems, and power plant control systems. These systems require high reliability and stability, and priority ceiling or priority inheritance is often used to prevent priority inversion and ensure timely execution of critical tasks.


Overall, priority ceiling and priority inheritance are widely used in a variety of real-time systems that require high reliability, safety, and accuracy. These techniques help to prevent priority inversion and ensure that critical tasks are executed on time, even in complex and demanding environments.

 

In summary, both priority ceiling and priority inheritance are techniques used in real-time operating systems to prevent priority inversion and ensure that high-priority tasks can execute as expected. Priority ceiling is a static protocol that assigns a priority ceiling to each shared resource, while priority inheritance is a dynamic protocol that temporarily raises the priority of a task holding a shared resource to the highest priority of any waiting tasks. While priority ceiling is generally more effective in preventing priority inversion, it also requires more overhead than priority inheritance.

 

Referance :