Deadlocks

A deadlock, also called a deadly embrace, is a situation in which two tasks are each unknowingly waiting for resources held by the other.

Assume Task T1 has exclusive access to Resource R1 and Task T2 has exclusive access to Resource R2 as shown in the pseudo-code of the listing below.

Deadlock Problem
          void T1 (void *p_arg)
          {
              while (DEF_ON) {
                  Wait for event to occur;          (1)
                  Acquire M1;                       (2)
                  Access  R1;                       (3)
                  :
                  :
                  \--------  Interrupt!             (4)
                  :
                  :                                 (8)
                  Acquire M2;                       (9)
                  Access  R2;
              }
          }
          void  T2 (void *p_arg)
          {
              while (DEF_ON) {
                  Wait for event to occur;          (5)
                  Acquire M2;                       (6)
                  Access  R2;                        
                  :
                  :
                  Acquire M1;                       (7)
                  Access  R1;
              }
          }

(1) Assume that the event that task T1 is waiting for occurs and T1 is now the highest priority task that must execute.

(2) Task T1 executes and acquires Mutex M1.

(3) Resource R1 is accessed.

(4) An interrupt occurs causing the CPU to switch to task T2 since T2 has a higher priority than task T1.

(5) The ISR is the event that task T2 was waiting for and therefore T2 resumes execution.

(6) Task T2 acquires mutex M2 and is able to access resource R2.

(7) Task T2 tries to acquire mutex M1, but µC/OS-III knows that mutex M1 is owned by another task.

(8) µC/OS-III switches back to task T1 because Task T2 can no longer continue. It needs mutex M1 to access resource R1.

(9) Task T1 now tries to access mutex M2 but, unfortunately, mutex M2 is owned by task T2. At this point, the two tasks are deadlocked, neither one can continue because each owns a resource that the other one wants.

Techniques used to avoid deadlocks are for tasks to:

  • Acquire all resources before proceeding
  • Always acquire resources in the same order
  • Use timeouts on pend calls

µC/OS-III allows the calling task to specify a timeout when acquiring a mutex. This feature allows a deadlock to be broken, but the same deadlock may then recur later, or many times later. If the mutex is not available within a certain period of time, the task requesting the resource resumes execution. µC/OS-III returns an error code indicating that a timeout occurred. A return error code prevents the task from thinking it has properly obtained the resource.

The pseudo-code avoids deadlocks by first acquiring all resources as shown in Listing 13-17.

Deadlock avoidance- acquire all first and in the same order
          void T1 (void *p_arg)
          {
              while (DEF_ON) {
                  Wait for event to occur;          
                  Acquire M1;                       
                  Acquire M2;
                  Access  R1;                        
                  Access  R2;
              }
          }
           
           
          void  T2 (void *p_arg)
          {
              while (DEF_ON) {
                  Wait for event to occur;     
                  Acquire M1;                  
                  Acquire M2;                  
                  Access  R1;                        
                  Access  R2;
              }
          }


The pseudo-code to acquire all of the mutexes in the same order is shown in the listing below. This is similar to the previous example, except that it is not necessary to acquire all the mutexes first, only to make sure that the mutexes are acquired in the same order for both tasks.

Deadlock avoidance - acquire in the same order
          void T1 (void *p_arg){
              while (DEF_ON) {
                  Wait for event to occur;          
                  Acquire M1;                       
                  Access  R1;                        
                  Acquire M2;
                  Access  R2;
              }
          }
           
           
          void  T2 (void *p_arg)
          {
              while (DEF_ON) {
                  Wait for event to occur;     
                  Acquire M1;                  
                  Access  R1;                        
                  Acquire M2;                  
                  Access  R2;
              }
          }