(原创)RTOS-RTX分析系列之互斥量

2017/07/25 RTX

简介

互斥是多线程运行的必备条件,在使用互斥锁的过程中,最常见也最重要的概念就是优先级翻转的问题了。

我们先来了解下优先级翻转的概念:

优先级翻转是当一个高优先级任务通过信号量机制访问共享资源时,该信号量已被一低优先级任务占有,因此造成高优先级任务被许多具有较低优先级任务阻塞,实时性难以得到保证。

解决优先级翻转问题有优先级天花板(priority ceiling)和优先级继承(priority inheritance)两种办法。

  1. 优先级天花板是当任务申请某资源时, 把该任务的优先级提升到可访问这个资源的所有任务中的最高优先级, 这个优先级称为该资源的优先级天花板。这种方法简单易行, 不必进行复杂的判断, 不管任务是否阻塞了高优先级任务的运行, 只要任务访问共享资源都会提升任务的优先级。

  2. 优先级继承是当任务A 申请共享资源S 时, 如果S正在被任务C 使用,通过比较任务C 与自身的优先级,如发现任务C 的优先级小于自身的优先级, 则将任务C的优先级提升到自身的优先级, 任务C 释放资源S 后,再恢复任务C 的原优先级。这种方法只在占有资源的低优先级任务阻塞了高优先级任务时才动态的改变任务的优先级,如果过程较复杂, 则需要进行判断。

RTX采用的就是第2种方法,通过提升低优先级任务的优先级,达到避免优先级翻转。

关键结构

互斥量的定义:

typedef struct OS_MUCB {
  U8     cb_type;                 /* Control Block Type    固定为MUCB  */
  U16    level;                   /* Call nesting level    加锁的层级 */
  struct OS_TCB *p_lnk;           /* Chain of tasks waiting for mutex  被阻塞的任务链表 */
  struct OS_TCB *owner;           /* Mutex owner task     锁的主人,也就是第一个使用锁的任务*/
  struct OS_MUCB *p_mlnk;         /* Chain of mutexes by owner task  因为一个任务可以拥有不同的锁,这里了放一个指针,方便任务把多个锁链接起来 */
} *P_MUCB;

定义互斥量的结构体:

typedef struct os_mutex_def  {
  void                      *mutex;    ///< pointer to internal data
} osMutexDef_t;

使用osMutexDef宏来简便的定义一个互斥量:

#define osMutexDef(name)  \
uint32_t os_mutex_cb_##name[4] = { 0 }; \
const osMutexDef_t os_mutex_def_##name = { (os_mutex_cb_##name) }

关键实现

定义互斥量

osMutexId mid_Thread_Mutex;                                     // mutex id
osMutexDef (SampleMutex);                                       // mutex name definition

这里比较简单,不啰嗦了。

rt_mut_init

互斥锁的初始化最主要的就是把锁的层级清0,以及把阻塞任务清掉:

void rt_mut_init (OS_ID mutex) {
  /* Initialize a mutex object */
  P_MUCB p_MCB = mutex;

  p_MCB->cb_type = MUCB;
  p_MCB->level   = 0;
  p_MCB->p_lnk   = NULL;
  p_MCB->owner   = NULL;
  p_MCB->p_mlnk  = NULL;
}

rt_mut_wait

在进入临界代码时必须要调用这个函数,告知系统,我进入了互斥区域,神圣不可侵犯:

OS_RESULT rt_mut_wait (OS_ID mutex, U16 timeout) {
  /* Wait for a mutex, continue when mutex is free. */
  P_MUCB p_MCB = mutex;

  if (p_MCB->level == 0) {//如果这锁没有人使用,那当然我是主人
    p_MCB->owner  = os_tsk.run;//先到先得
    p_MCB->p_mlnk = os_tsk.run->p_mlnk;//把该任务的其它的锁也链接起来
    os_tsk.run->p_mlnk = p_MCB; //当前锁作为任务的整个锁链表的第一个
    goto inc;
  }
  if (p_MCB->owner == os_tsk.run) {//如果连续加锁,也是可以的,只不过后面解锁就要层次匹配,不然这把锁永远解不开。
    /* OK, running task is the owner of this mutex. */
inc:p_MCB->level++;//加一层锁
    return (OS_R_OK);
  }
  /* Mutex owned by another task, wait until released. */
  if (timeout == 0) {//如果锁不是我的,我又不愿意等,那就直接返回OS_R_TMO
    return (OS_R_TMO);
  }
  /* Raise the owner task priority if lower than current priority. */
  /* This priority inversion is called priority inheritance.       */
  if (p_MCB->owner->prio < os_tsk.run->prio) {//如果锁不是我的,我又愿意等,并且我的优先级比这把锁的主人的优先级还高
    p_MCB->owner->prio = os_tsk.run->prio;//那就把这把锁主人的优先级提到和我一样高,避免了优先级翻转,这锁主人占了我的光了。
    rt_resort_prio (p_MCB->owner);//既然把锁主人的优先级提高了,那当然得重新按优先级排下序了。
  }
  if (p_MCB->p_lnk != NULL) {//如果当前有其它任务也在等待这把锁,那就只好排队等待了。
    rt_put_prio ((P_XCB)p_MCB, os_tsk.run);//加入等待链表
  }
  else {
    p_MCB->p_lnk = os_tsk.run;//如果没有人等待,那我做第一个等待的任务
    os_tsk.run->p_lnk  = NULL;
    os_tsk.run->p_rlnk = (P_TCB)p_MCB;
  }
  rt_block(timeout, WAIT_MUT);//阻塞自己,超时等待。
  return (OS_R_TMO);
}

rt_mut_release

执行完临界代码,切记切记要调用锁释放,加锁解锁次数最好要匹配。释放多次没有问题,但少释放一次就有严重问题:

OS_RESULT rt_mut_release (OS_ID mutex) {
  /* Release a mutex object */
  P_MUCB p_MCB = mutex;
  P_TCB  p_TCB;
  P_MUCB p_mlnk;
  U8     prio;

  if (p_MCB->level == 0 || p_MCB->owner != os_tsk.run) {//如果这锁还没有人加,是把新锁,或者这锁根本不是我的,那就直接返回OS_R_NOK
    /* Unbalanced mutex release or task is not the owner */
    return (OS_R_NOK);
  }
  if (--p_MCB->level != 0) {//除了上述条件,当然就只剩下这不是新锁,并且锁就是我的,那就看看这锁能否解开,解到第一层才算完全解开,如果还是大于0,说明还没解完,直接返回OS_R_OK
    return (OS_R_OK);
  }

  /* Remove mutex from task mutex owner list. */
  //到这,说明这把锁完全解开了,那就得做一系列操作了
  p_mlnk = os_tsk.run->p_mlnk;//找到这锁主人的全部锁链表
  if (p_mlnk == p_MCB) {//如果第一个就是这把锁,那就直接把这锁从锁链表移除。
    os_tsk.run->p_mlnk = p_MCB->p_mlnk;
  }
  else {
    while (p_mlnk) {//不然就循环遍历找到这把锁,并且移除
      if (p_mlnk->p_mlnk == p_MCB) {
	p_mlnk->p_mlnk = p_MCB->p_mlnk;
	break;
      }
      p_mlnk = p_mlnk->p_mlnk;
    }
  }

  /* Restore owner task's priority. */
  prio = os_tsk.run->prio_base;//找到这个任务最初定义的优先级
  p_mlnk = os_tsk.run->p_mlnk;//找到这个任务剩余锁的链表
  while (p_mlnk) {
    if (p_mlnk->p_lnk && (p_mlnk->p_lnk->prio > prio)) {//循环遍历这个任务其它的锁链表,每个锁的等待任务链表只看第一个就够了,因为已经按优先级排好序了,找出剩余锁优先级翻转的最大优先级。
      /* A task with higher priority is waiting for mutex. */
      prio = p_mlnk->p_lnk->prio;//找出最大的优先级是多少
    }
    p_mlnk = p_mlnk->p_mlnk;
  }
  os_tsk.run->prio = prio;//将剩余锁的最大优先级重新赋值给这个任务,毕竟我这个锁解开了,还有其它锁在,同样会有优先级翻转的现象,因此不能简单的恢复到最初的优先级。

  if (p_MCB->p_lnk != NULL) {
    /* A task is waiting for mutex. */
    p_TCB = rt_get_first ((P_XCB)p_MCB);//锁完全打开了,就要找到其它等待这把锁优先级最高的任务
#ifdef __CMSIS_RTOS
    rt_ret_val(p_TCB, 0/*osOK*/);//通知这个心任务已经获得这把锁,可以放心的继续执行任务。
#else
    rt_ret_val(p_TCB, OS_R_MUT); 
#endif
    rt_rmv_dly (p_TCB);//从延时任务链表移除这个新任务
    /* A waiting task becomes the owner of this mutex. */
    p_MCB->level  = 1;//因为要把这把锁换个新主人,所以,既然有了新主人,锁就要加上,不然就无主了。
    p_MCB->owner  = p_TCB;//换成新主人,也就上面说的新任务
    p_MCB->p_mlnk = p_TCB->p_mlnk;//把这把锁链接到新任务的锁链表中去
    p_TCB->p_mlnk = p_MCB; 
    /* Priority inversion, check which task continues. */
    if (os_tsk.run->prio >= rt_rdy_prio()) {//由于为了避免优先级翻转的问题,如果有高优先级在等待锁,会提升锁主人任务的优先级,因此p_TCB->prio<=os_tsk.run->prio。
      rt_dispatch (p_TCB);//在调度内部,还会判断一次优先级,由于p_TCB->prio<=os_tsk.run->prio的,个人认为这里没有必要这么写直接把p_TCB放置就绪任务链表即可。
    }
    else {
      /* Ready task has higher priority than running task. */
      rt_put_prio (&os_rdy, os_tsk.run);//因为os_tsk.run->prio = prio;当前任务的优先级因为解锁,优先级被改变过一次,如果比就绪优先级最高的任务低,必须立马开始调度。
      rt_put_prio (&os_rdy, p_TCB);
      os_tsk.run->state = READY;
      p_TCB->state      = READY;
      rt_dispatch (NULL);
    }
  }
  else {//如果没有任务在等待这把锁
    /* Check if own priority lowered by priority inversion. */
    if (rt_rdy_prio() > os_tsk.run->prio) {//再判断当前是否有更高优先级的任务在等待,毕竟这个任务的优先级已经改变。如果有的话就得排序,重新调度下。
      rt_put_prio (&os_rdy, os_tsk.run);
      os_tsk.run->state = READY;
      rt_dispatch (NULL);
    }
  }
  return (OS_R_OK);
}

rt_mut_delete

锁的删除和释放有点类似,只不过有两点不同:

  1. 锁的删除会恢复等待这把锁所有的任务,锁的释放只能恢复一个任务。
  2. 锁的删除可以在其它线程调用,而锁释放只能在锁主人的线程里调用。

    OS_RESULT rt_mut_delete (OS_ID mutex) { /* Delete a mutex object */ P_MUCB p_MCB = mutex; P_TCB p_TCB; P_MUCB p_mlnk; U8 prio;

    if (p_MCB->level != 0) {//如果这把锁还有人使用

    p_TCB = p_MCB->owner;//找到锁的主人
    	
     /* Remove mutex from task mutex owner list. */
     p_mlnk = p_TCB->p_mlnk;
     if (p_mlnk == p_MCB) {//如果这把锁刚好是这把锁主人所有锁的第一把锁,有点绕哈,简单来说,就是这是主人的第一把锁。
       p_TCB->p_mlnk = p_MCB->p_mlnk;//取出这把锁
     }
     else {
       while (p_mlnk) {//如果不是主人的第一把锁,就循环遍历下,取出这把锁
     if (p_mlnk->p_mlnk == p_MCB) {
       p_mlnk->p_mlnk = p_MCB->p_mlnk;
       break;
     }
     p_mlnk = p_mlnk->p_mlnk;
       }
    }
    	
     /* Restore owner task's priority. */
     prio = p_TCB->prio_base;//先恢复创建时定义的优先级
     p_mlnk = p_TCB->p_mlnk;
     while (p_mlnk) {//如果锁主人还有其它的锁,同样为了避免优先级翻转,得遍历下其它锁的优先级翻转的情况了
       if (p_mlnk->p_lnk && (p_mlnk->p_lnk->prio > prio)) {
     /* A task with higher priority is waiting for mutex. */
     prio = p_mlnk->p_lnk->prio;//找到其它锁提升的优先级
       }
       p_mlnk = p_mlnk->p_mlnk;
     }
     if (p_TCB->prio != prio) {//如果优先级改变了,就要申请调度
       p_TCB->prio = prio;
       if (p_TCB != os_tsk.run) {
     rt_resort_prio (p_TCB);
       }
    }
    

    } //这锁目前没有人使用 while (p_MCB->p_lnk != NULL) {//锁都要销毁了,当然得把等待这把锁的任务都恢复出来 /* A task is waiting for mutex. / p_TCB = rt_get_first ((P_XCB)p_MCB);//找找还有谁在等待这把锁 rt_ret_val(p_TCB, 0/osOK*/);//告诉它,ok了,你得到这把锁了 rt_rmv_dly(p_TCB); p_TCB->state = READY; rt_put_prio (&os_rdy, p_TCB);//加入就绪任务链表,等着执行吧 }

    if (os_rdy.p_lnk && (os_rdy.p_lnk->prio > os_tsk.run->prio)) {//如果正在执行的任务优先级比就绪的低了,当然得调度一次 /* preempt running task */ rt_put_prio (&os_rdy, os_tsk.run); os_tsk.run->state = READY; rt_dispatch (NULL); }

    p_MCB->cb_type = 0;//清除控制块标志位

    return (OS_R_OK); }


知识共享许可协议
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

站内搜索

    撩我备注-博客

    joinee

    目录结构