Nuttx信號(hào)量機(jī)制

Nuttx相關(guān)的歷史文章:

介紹

  • 信號(hào)量

在Nuttx中没宾,信號(hào)量是同步和互斥的基礎(chǔ)凌彬,Nuttx支持POSIX信號(hào)量。
信號(hào)量是獲得對(duì)資源獨(dú)占訪問(wèn)的首選機(jī)制循衰,盡管sched_lock()sched_unlock()接口也能實(shí)現(xiàn)這個(gè)功能铲敛,但是這兩個(gè)接口還是會(huì)在系統(tǒng)中帶來(lái)一些副作用,sched_lock()會(huì)同時(shí)禁止高優(yōu)先級(jí)任務(wù)的運(yùn)行会钝,這些任務(wù)不依賴于信號(hào)量管理的資源伐蒋,這會(huì)對(duì)系統(tǒng)的相應(yīng)時(shí)間產(chǎn)生負(fù)面影響。

  • 優(yōu)先級(jí)反轉(zhuǎn)

正確使用信號(hào)量可以避免sched_lock()的問(wèn)題迁酸,但是存在以下的情況:

  1. 低優(yōu)先級(jí)任務(wù)Task C先鱼,獲取一個(gè)信號(hào)量,獲得對(duì)保護(hù)資源的獨(dú)占使用奸鬓;
  2. 任務(wù)Task C掛起焙畔,讓高優(yōu)先級(jí)任務(wù)Task A執(zhí)行;
  3. 任務(wù)Task A試圖獲取任務(wù)Task C所持有的信號(hào)量而被阻塞串远,直到任務(wù)Task C放棄信號(hào)量宏多;
  4. 任務(wù)Task C允許被再次執(zhí)行,但是被某個(gè)中等優(yōu)先級(jí)的任務(wù)Task B掛起抑淫。

在這種情況下绷落,高優(yōu)先級(jí)任務(wù)Task A在任務(wù)Task B(可能還有其他中等優(yōu)先級(jí)的任務(wù))完成和任務(wù)Task C釋放信號(hào)量之前不能執(zhí)行。表現(xiàn)出來(lái)就是任務(wù)Task A的優(yōu)先級(jí)好像比任務(wù)Task C優(yōu)先級(jí)要低一樣始苇,這種現(xiàn)象就叫優(yōu)先級(jí)反轉(zhuǎn)砌烁。
在一些操作系統(tǒng)中通過(guò)增加低優(yōu)先級(jí)任務(wù)Task C來(lái)避免優(yōu)先級(jí)反轉(zhuǎn)(這種行為的可操作術(shù)語(yǔ)叫優(yōu)先級(jí)繼承)。Nuttx在CONFIG_PRIORITY_INHERITANCE被選中時(shí)是支持這種行為催式,否則的話函喉,需要設(shè)計(jì)人員提供不會(huì)發(fā)生優(yōu)先級(jí)反轉(zhuǎn)的實(shí)現(xiàn),比如:

  • 將需要同一個(gè)信號(hào)量管理的資源的所有任務(wù)設(shè)置成同一級(jí)別的優(yōu)先級(jí)
  • 當(dāng)需要獲取信號(hào)量時(shí)荣月,將低優(yōu)先級(jí)任務(wù)的優(yōu)先級(jí)提升
  • 在低優(yōu)先級(jí)任務(wù)中使用sched_lock()
  • 優(yōu)先級(jí)繼承

上文中提到管呵,當(dāng)CONFIG_PRIORITY_INHERITANCE被選中時(shí),Nuttx支持優(yōu)先級(jí)繼承哺窄,但是這個(gè)過(guò)程比較復(fù)雜捐下。

  • CONFIG_SEM_PREALLOCHOLDERS
    首先账锹,在Nuttx中,優(yōu)先級(jí)繼承是在POSIX信號(hào)量基礎(chǔ)上實(shí)現(xiàn)的坷襟,這是因?yàn)檫@些信號(hào)量是Nuttx中最原始的等待機(jī)制奸柬,其他大多數(shù)等待方式都是基于信號(hào)量來(lái)實(shí)現(xiàn)的,因此婴程,如果為POSIX信號(hào)量實(shí)現(xiàn)了優(yōu)先級(jí)繼承廓奕,那么大多數(shù)Nuttx等待機(jī)制也就具備這個(gè)功能了。
    復(fù)雜性的出現(xiàn)是因?yàn)樾盘?hào)量可能有許多信號(hào)量計(jì)數(shù)持有者档叔,為了實(shí)現(xiàn)所有持有者的優(yōu)先級(jí)繼承桌粉,必須分配內(nèi)部數(shù)據(jù)結(jié)構(gòu)來(lái)管理與信號(hào)量關(guān)聯(lián)的各種持有者。CONFIG_SEM_PREALLOCHOLDERS定義了對(duì)具有優(yōu)先級(jí)繼承支持的信號(hào)量進(jìn)行計(jì)數(shù)的不同線程的最大數(shù)量衙四。這個(gè)設(shè)置也定義了預(yù)分配數(shù)據(jù)結(jié)構(gòu)池的大小铃肯。如果禁用了優(yōu)先級(jí)繼承,或者只使用信號(hào)量作為互斥體(只有一個(gè)持有者)届搁,或者使用計(jì)數(shù)信號(hào)量的線程不超過(guò)兩個(gè)缘薛,則可以將其設(shè)置為0.

  • CONFIG_SEM_NNESTPRIO
    此外窍育,可能存在多個(gè)不同優(yōu)先級(jí)的線程需要等待來(lái)自信號(hào)量的計(jì)數(shù)卡睦,低優(yōu)先級(jí)線程持有信號(hào)量需要被提高,但是又必須跟蹤所有提高優(yōu)先級(jí)的值以便最后能恢復(fù)漱抓,這個(gè)會(huì)讓事情變得復(fù)雜表锻。 CONFIG_SEM_NNESTPRIO定義數(shù)組的大小,每個(gè)活動(dòng)線程都有一個(gè)數(shù)組乞娄。這個(gè)值設(shè)置為等待另一個(gè)線程釋放信號(hào)量上的高優(yōu)先級(jí)線程的最大數(shù)量(-1)瞬逊。

  • 給線程行為帶來(lái)未知風(fēng)險(xiǎn)
    優(yōu)先級(jí)繼承相關(guān)的一些數(shù)據(jù)結(jié)構(gòu)與信號(hào)量的實(shí)現(xiàn)緊密耦合在一起,可能帶來(lái)某些影響仪或。比如确镊,如果線程在信號(hào)量進(jìn)行計(jì)數(shù)時(shí)執(zhí)行;或者如果線程在不調(diào)用sem_destroy()時(shí)退出范删;或者優(yōu)先級(jí)提高后的線程重新確定自己的優(yōu)先級(jí)又會(huì)怎樣蕾域。Nuttx在實(shí)現(xiàn)優(yōu)先級(jí)繼承的時(shí)候會(huì)嘗試去處理所有的corner case,但是也很有可能會(huì)遺漏到旦,最壞的情況是旨巷,內(nèi)存在優(yōu)先級(jí)繼承的情況下出現(xiàn)問(wèn)題。

  • Locking信號(hào)量 VS Signaling信號(hào)量

信號(hào)量(互斥鎖)有很多種用途添忘。

  • Locking信號(hào)量
    其中一種典型的用法是對(duì)資源的獨(dú)占訪問(wèn)采呐,也就是對(duì)臨界區(qū)的保護(hù)。需要獨(dú)占訪問(wèn)臨界區(qū)時(shí)搁骑,通過(guò)信號(hào)量來(lái)訪問(wèn)資源斧吐,訪問(wèn)完畢后又固,該線程隨后釋放信號(hào)量的計(jì)數(shù)。優(yōu)先級(jí)繼承只適用于這種用途煤率。

  • Signaling信號(hào)量
    另一種用途是用于發(fā)出信號(hào):線程A等待信號(hào)量上的事件發(fā)生口予。當(dāng)事件發(fā)生時(shí),另一個(gè)線程B將發(fā)送信號(hào)量喚醒等待的線程A涕侈。在獨(dú)占訪問(wèn)的用法中沪停,是由同一個(gè)線程來(lái)對(duì)信號(hào)量進(jìn)行計(jì)數(shù);而在這個(gè)用途中裳涛,是由一個(gè)線程等待在信號(hào)量上木张,另一個(gè)線程來(lái)發(fā)送信號(hào),這本質(zhì)上是一種線程的同步機(jī)制端三。在這種情況下舷礼,不應(yīng)該使用優(yōu)先級(jí)繼承,否則會(huì)出現(xiàn)一些奇怪的行為郊闯。

數(shù)據(jù)結(jié)構(gòu)及接口

數(shù)據(jù)結(jié)構(gòu)

/* This structure contains information about the holder of a semaphore */

#ifdef CONFIG_PRIORITY_INHERITANCE
struct tcb_s; /* Forward reference */
struct semholder_s
{
#if CONFIG_SEM_PREALLOCHOLDERS > 0
  struct semholder_s *flink;     /* Implements singly linked list */
#endif
  FAR struct tcb_s *htcb;        /* Holder TCB */
  int16_t counts;                /* Number of counts owned by this holder */
};


/* This is the generic semaphore structure. */

struct sem_s
{
  volatile int16_t semcount;     /* >0 -> Num counts available */
                                 /* <0 -> Num tasks waiting for semaphore */
  /* If priority inheritance is enabled, then we have to keep track of which
   * tasks hold references to the semaphore.
   */

#ifdef CONFIG_PRIORITY_INHERITANCE
  uint8_t flags;                 /* See PRIOINHERIT_FLAGS_* definitions */
# if CONFIG_SEM_PREALLOCHOLDERS > 0
  FAR struct semholder_s *hhead; /* List of holders of semaphore counts */
# else
  struct semholder_s holder;     /* Single holder */
# endif
#endif
};

主要的數(shù)據(jù)結(jié)構(gòu)分為兩部分:

  • struct sem_s:用于描述通用的信號(hào)量妻献,其中該結(jié)構(gòu)中包含了信號(hào)量的計(jì)數(shù)變量,以及struct semholder_s成員团赁;
  • struct semholder_s:用于描述信號(hào)量的持有者育拨,對(duì)應(yīng)一個(gè)TCB,以及在該TCB所描述的任務(wù)中信號(hào)量的計(jì)數(shù)值欢摄。由于可能會(huì)存在多個(gè)任務(wù)等待一個(gè)信號(hào)量熬丧,因此這個(gè)結(jié)構(gòu)實(shí)現(xiàn)為一個(gè)單鏈表形式。

接口

  • int sem_init(sem_t *sem, int pshared, unsigned int value)
    完成未命名信號(hào)量sem的初始化怀挠,pshared未使用析蝴,value為信號(hào)量的初始化值。完成初始化之后绿淋,信號(hào)量就能被用于sem_wait()/sem_post()/sem_trywait()等接口了闷畸。

  • int sem_destroy(sem_t *sem)
    完成未命名信號(hào)量sem的銷毀,只有調(diào)用sem_init()接口創(chuàng)建的信號(hào)量吞滞,才能被sem_destroy()銷毀佑菩。調(diào)用sem_destroy() 去銷毀一個(gè)命名信號(hào)量的行為是未定義的,在sem_destroy()之后再去使用信號(hào)量的行為也是未定義的冯吓。

  • sem_t *sem_open(const char *name, int oflag, ...)
    Task和命名信號(hào)量之間建立一個(gè)連接倘待,在使用信號(hào)量名稱調(diào)用sem_open()之后,關(guān)聯(lián)的Task可以使用該函數(shù)的返回地址來(lái)引用對(duì)應(yīng)的信號(hào)量组贺。

  • int sem_close(sem_t *sem)
    當(dāng)調(diào)用任務(wù)結(jié)束使用這個(gè)命名信號(hào)量時(shí)凸舵,可以調(diào)用此接口。sem_close()會(huì)釋放系統(tǒng)為這個(gè)命名的信號(hào)量分配的任何系統(tǒng)資源失尖。如果沒(méi)有使用sem_unlink()來(lái)刪除信號(hào)量啊奄,那么sem_close()對(duì)指定的信號(hào)量沒(méi)有影響渐苏,但是,當(dāng)指定的信號(hào)量被完全解除鏈接時(shí)菇夸,信號(hào)量將在最后一個(gè)任務(wù)關(guān)閉它時(shí)消失琼富。必須小心避免刪除另一個(gè)調(diào)用任務(wù)已經(jīng)鎖定的信號(hào)量。

  • int sem_unlink(const char *name)
    這個(gè)函數(shù)將刪除由輸入名參數(shù)命名的信號(hào)量庄新,如果有一個(gè)或多個(gè)任務(wù)正在使用信號(hào)量時(shí)調(diào)用sem_unlink()鞠眉,信號(hào)量的銷毀會(huì)被延遲,直到所有引用都被調(diào)用sem_close()為止择诈。

  • int sem_wait(sem_t *sem)
    嘗試去鎖住信號(hào)量sem械蹋,如果sem信號(hào)量已經(jīng)被鎖住了,調(diào)用該接口的Task不會(huì)返回羞芍,直到它成功的獲取鎖哗戈,或者調(diào)用被信號(hào)中斷。

  • int sem_timedwait(sem_t *sem, const struct timespec *abstime)
    這個(gè)函數(shù)類似于sem_wait()荷科,不同的是唯咬,當(dāng)沒(méi)有其他線程通過(guò)sem_post()來(lái)釋放信號(hào)量的話,那么在指定時(shí)間超時(shí)過(guò)期時(shí)畏浆,這個(gè)等待將會(huì)終止胆胰。

  • int sem_trywait(sem_t *sem)
    該函數(shù)僅在當(dāng)前信號(hào)量未鎖定的情況下鎖定指定的信號(hào)量,無(wú)論如何全度,調(diào)用返回時(shí)不會(huì)阻塞煮剧。

  • int sem_post(sem_t *sem)
    當(dāng)一個(gè)任務(wù)使用完一個(gè)信號(hào)量時(shí),將調(diào)用sem_post()将鸵,該函數(shù)會(huì)解鎖信號(hào)量。如果該該操作產(chǎn)生的信號(hào)量值為正數(shù)佑颇,則不會(huì)阻塞等待信號(hào)量解鎖的任務(wù)顶掉,信號(hào)量的值只是簡(jiǎn)單的遞增。如果該操作產(chǎn)生的信號(hào)量值為0挑胸,那么在阻塞的任務(wù)中痒筒,等待信號(hào)量的任務(wù)將被允許從sem_wait()調(diào)用中成功返回。注意:可以從中斷處理程序中調(diào)用sem_post()茬贵。

  • int sem_getvalue(sem_t *sem, int *sval)
    該函數(shù)用于獲取信號(hào)量的值簿透,當(dāng)信號(hào)量被鎖住時(shí),得到的值要么為0解藻,要么為負(fù)數(shù)老充,其絕對(duì)值表示等待信號(hào)量的任務(wù)數(shù)。

  • int sem_getprotocol(FAR const pthread_mutexattr_t *attr, FAR int *protocol)
    獲取信號(hào)量協(xié)議屬性值螟左,值有: SEM_PRIO_NONE, SEM_PRIO_INHERIT, SEM_PRIO_PROTECT

  • int sem_setprotocol(FAR pthread_mutexattr_t *attr, int protocol)
    設(shè)置信號(hào)量協(xié)議屬性匆骗,值有: SEM_PRIO_NONE, SEM_PRIO_INHERIT, SEM_PRIO_PROTECT堰汉。SEM_PRIO_INHERIT只有在CONFIG_PRIORITY_INHERITANCE被選中時(shí)才支持,此外喘先,SEM_PRIO_PROTECT在當(dāng)前的配置下不支持。

原理

還是來(lái)一張圖吧:


semaphore原理

信號(hào)量整體的框架如上圖所示廷粒,與之相關(guān)的結(jié)構(gòu)如下:

  • struct sem_s:該結(jié)構(gòu)中維護(hù)了一個(gè) 信號(hào)燈計(jì)數(shù)值窘拯,當(dāng)有任務(wù)在等待這個(gè)信號(hào)量時(shí),該計(jì)數(shù)值就加1坝茎,釋放信號(hào)量時(shí)树枫,計(jì)數(shù)值則減1.此外還維護(hù)了一個(gè)holder持有者鏈表,把所有想獲取這個(gè)信號(hào)量的任務(wù)組織成鏈表形式景东。
  • g_freeholder:全局隊(duì)列結(jié)構(gòu)砂轻,該結(jié)構(gòu)預(yù)先靜態(tài)分配好了所有的holder持有者數(shù)據(jù)結(jié)構(gòu),當(dāng)有新的任務(wù)需要等待信號(hào)量時(shí)斤吐,便從這個(gè)全局隊(duì)列中分配一個(gè)搔涝,如果釋放信號(hào)量,則將holder持有者數(shù)據(jù)結(jié)構(gòu)返回到這個(gè)隊(duì)列中和措。
  • g_waitingforsemaphore:全局任務(wù)隊(duì)列庄呈,當(dāng)有任務(wù)調(diào)用sem_wait()等待信號(hào)量,但是沒(méi)法獲取的時(shí)候派阱,就將該任務(wù)添加到g_waitingforsemaphore隊(duì)列中诬留,并讓出CPU,當(dāng)有任務(wù)調(diào)用sem_post()釋放信號(hào)量時(shí)贫母,會(huì)去查詢g_waitingforsemaphore隊(duì)列文兑,是否有等待該信號(hào)量的任務(wù)被阻塞,如果有的話腺劣,則喚醒對(duì)應(yīng)的任務(wù)绿贞。
  • struct semholder_s:信號(hào)量持有者,該結(jié)構(gòu)中主要包含了struct tcb_s橘原,對(duì)應(yīng)到等待該信號(hào)量的任務(wù)籍铁,struct tcb_s結(jié)構(gòu)中有一個(gè)waitsem字段,用于指向這個(gè)任務(wù)在等待的信號(hào)量趾断。此外還有一個(gè)counts計(jì)數(shù)值拒名,用于記錄該任務(wù)想獲取同一個(gè)信號(hào)量的次數(shù)。

還是從幾個(gè)關(guān)鍵的函數(shù)來(lái)分析吧:

sem_wait()

sem_wait()函數(shù)主要完成以下幾個(gè)工作:

  1. 判斷是否在中斷上下文中芋酌,由于sem_wait()可能觸發(fā)任務(wù)調(diào)度增显,造成本身睡眠,因此不能在中斷上下文中調(diào)用隔嫡;
  2. 如果信號(hào)量可用甸怕,將計(jì)數(shù)值減1甘穿,并將調(diào)用任務(wù)添加到信號(hào)量的持有者鏈表中;
  3. 如果信號(hào)量不可用梢杭,將計(jì)數(shù)值減1温兼,將調(diào)用任務(wù)中waitsem值設(shè)置成當(dāng)前信號(hào)量。如果使能了優(yōu)先級(jí)繼承武契,則提升該信號(hào)量持有者中比當(dāng)前調(diào)用任務(wù)優(yōu)先級(jí)低的任務(wù)優(yōu)先級(jí)募判。最后將調(diào)用任務(wù)添加到信號(hào)量等待隊(duì)列g_waitingforsemaphore中。
/****************************************************************************
 * Name: sem_wait
 *
 * Description:
 *   This function attempts to lock the semaphore referenced by 'sem'.  If
 *   the semaphore value is (<=) zero, then the calling task will not return
 *   until it successfully acquires the lock.
 *
 * Parameters:
 *   sem - Semaphore descriptor.
 *
 * Return Value:
 *   0 (OK), or -1 (ERROR) is unsuccessful
 *   If this function returns -1 (ERROR), then the cause of the failure will
 *   be reported in 'errno' as:
 *   - EINVAL:  Invalid attempt to get the semaphore
 *   - EINTR:   The wait was interrupted by the receipt of a signal.
 *
 * Assumptions:
 *
 ****************************************************************************/

int sem_wait(FAR sem_t *sem)
{
  FAR struct tcb_s *rtcb = this_task();
  irqstate_t flags;
  int ret  = ERROR;

  /* This API should not be called from interrupt handlers */

  DEBUGASSERT(sem != NULL && up_interrupt_context() == false);

  /* The following operations must be performed with interrupts
   * disabled because sem_post() may be called from an interrupt
   * handler.
   */

  flags = enter_critical_section();

  /* sem_wait() is a cancellation point */

  if (enter_cancellation_point())
    {
#ifdef CONFIG_CANCELLATION_POINTS
      /* If there is a pending cancellation, then do not perform
       * the wait.  Exit now with ECANCELED.
       */

      set_errno(ECANCELED);
      leave_cancellation_point();
      leave_critical_section(flags);
      return ERROR;
#endif
    }

  /* Make sure we were supplied with a valid semaphore. */

  if (sem != NULL)
    {
      /* Check if the lock is available */

      if (sem->semcount > 0)
        {
          /* It is, let the task take the semaphore. */

          sem->semcount--;
          sem_addholder(sem);
          rtcb->waitsem = NULL;
          ret = OK;
        }

      /* The semaphore is NOT available, We will have to block the
       * current thread of execution.
       */

      else
        {
          /* First, verify that the task is not already waiting on a
           * semaphore
           */

          ASSERT(rtcb->waitsem == NULL);

          /* Handle the POSIX semaphore (but don't set the owner yet) */

          sem->semcount--;

          /* Save the waited on semaphore in the TCB */

          rtcb->waitsem = sem;

          /* If priority inheritance is enabled, then check the priority of
           * the holder of the semaphore.
           */

#ifdef CONFIG_PRIORITY_INHERITANCE
          /* Disable context switching.  The following operations must be
           * atomic with regard to the scheduler.
           */

          sched_lock();

          /* Boost the priority of any threads holding a count on the
           * semaphore.
           */

          sem_boostpriority(sem);
#endif
          /* Add the TCB to the prioritized semaphore wait queue */

          set_errno(0);
          up_block_task(rtcb, TSTATE_WAIT_SEM);

          /* When we resume at this point, either (1) the semaphore has been
           * assigned to this thread of execution, or (2) the semaphore wait
           * has been interrupted by a signal or a timeout.  We can detect these
           * latter cases be examining the errno value.
           *
           * In the event that the semaphore wait was interrupted by a signal or
           * a timeout, certain semaphore clean-up operations have already been
           * performed (see sem_waitirq.c).  Specifically:
           *
           * - sem_canceled() was called to restore the priority of all threads
           *   that hold a reference to the semaphore,
           * - The semaphore count was decremented, and
           * - tcb->waitsem was nullifed.
           *
           * It is necesaary to do these things in sem_waitirq.c because a long
           * time may elapse between the time that the signal was issued and
           * this thread is awakened and this leaves a door open to several
           * race conditions.
           */

          if (get_errno() != EINTR && get_errno() != ETIMEDOUT)
            {
              /* Not awakened by a signal or a timeout...
               *
               * NOTE that in this case sem_addholder() was called by logic
               * in sem_wait() fore this thread was restarted.
               */

              ret = OK;
            }

#ifdef CONFIG_PRIORITY_INHERITANCE
          sched_unlock();
#endif
        }
    }
  else
    {
      set_errno(EINVAL);
    }

  leave_cancellation_point();
  leave_critical_section(flags);
  return ret;
}

sem_post()

sem_post()主要完成以下幾個(gè)任務(wù):

  1. 調(diào)用sem_releaseholder()接口來(lái)將本任務(wù)中持有信號(hào)量的次數(shù)減1咒唆;
  2. 增加信號(hào)量計(jì)數(shù)值届垫;
  3. 當(dāng)信號(hào)量計(jì)數(shù)值小于等于0時(shí),表明一定有任務(wù)正在睡眠等待本信號(hào)量全释,這些任務(wù)都在g_waitingforsemaphore隊(duì)列中装处,遍歷該隊(duì)列,找到優(yōu)先級(jí)最高的任務(wù)浸船,將它添加進(jìn)信號(hào)量的持有者隊(duì)列中妄迁,并調(diào)度運(yùn)行這個(gè)任務(wù)。
  4. 調(diào)用sem_restorebaseprio()接口來(lái)恢復(fù)之前的優(yōu)先級(jí)(如果有優(yōu)先級(jí)調(diào)整的話)李命,在該函數(shù)中會(huì)去判斷任務(wù)中持有信號(hào)量的計(jì)數(shù)值登淘,當(dāng)減到0時(shí),釋放該持有者封字。
/****************************************************************************
 * Name: sem_post
 *
 * Description:
 *   When a task has finished with a semaphore, it will call sem_post().
 *   This function unlocks the semaphore referenced by sem by performing the
 *   semaphore unlock operation on that semaphore.
 *
 *   If the semaphore value resulting from this operation is positive, then
 *   no tasks were blocked waiting for the semaphore to become unlocked; the
 *   semaphore is simply incremented.
 *
 *   If the value of the semaphore resulting from this operation is zero,
 *   then one of the tasks blocked waiting for the semaphore shall be
 *   allowed to return successfully from its call to sem_wait().
 *
 * Parameters:
 *   sem - Semaphore descriptor
 *
 * Return Value:
 *   0 (OK) or -1 (ERROR) if unsuccessful
 *
 * Assumptions:
 *   This function may be called from an interrupt handler.
 *
 ****************************************************************************/

int sem_post(FAR sem_t *sem)
{
  FAR struct tcb_s *stcb = NULL;
  irqstate_t flags;
  int ret = ERROR;

  /* Make sure we were supplied with a valid semaphore. */

  if (sem)
    {
      /* The following operations must be performed with interrupts
       * disabled because sem_post() may be called from an interrupt
       * handler.
       */

      flags = enter_critical_section();

      /* Perform the semaphore unlock operation, releasing this task as a
       * holder then also incrementing the count on the semaphore.
       *
       * NOTE:  When semaphores are used for signaling purposes, the holder
       * of the semaphore may not be this thread!  In this case,
       * sem_releaseholder() will do nothing.
       *
       * In the case of a mutex this could be simply resolved since there is
       * only one holder but for the case of counting semaphores, there may
       * be many holders and if the holder is not this thread, then it is
       * not possible to know which thread/holder should be released.
       *
       * For this reason, it is recommended that priority inheritance be
       * disabled via sem_setprotocol(SEM_PRIO_NONE) when the semahore is
       * initialixed if the semaphore is to used for signaling purposes.
       */

      ASSERT(sem->semcount < SEM_VALUE_MAX);
      sem_releaseholder(sem);
      sem->semcount++;

#ifdef CONFIG_PRIORITY_INHERITANCE
      /* Don't let any unblocked tasks run until we complete any priority
       * restoration steps.  Interrupts are disabled, but we do not want
       * the head of the read-to-run list to be modified yet.
       *
       * NOTE: If this sched_lock is called from an interrupt handler, it
       * will do nothing.
       */

      sched_lock();
#endif
      /* If the result of of semaphore unlock is non-positive, then
       * there must be some task waiting for the semaphore.
       */

      if (sem->semcount <= 0)
        {
          /* Check if there are any tasks in the waiting for semaphore
           * task list that are waiting for this semaphore. This is a
           * prioritized list so the first one we encounter is the one
           * that we want.
           */

          for (stcb = (FAR struct tcb_s *)g_waitingforsemaphore.head;
               (stcb && stcb->waitsem != sem);
               stcb = stcb->flink);

          if (stcb != NULL)
            {
              /* The task will be the new holder of the semaphore when
               * it is awakened.
               */

              sem_addholder_tcb(stcb, sem);

              /* It is, let the task take the semaphore */

              stcb->waitsem = NULL;

              /* Restart the waiting task. */

              up_unblock_task(stcb);
            }
        }

      /* Check if we need to drop the priority of any threads holding
       * this semaphore.  The priority could have been boosted while they
       * held the semaphore.
       */

#ifdef CONFIG_PRIORITY_INHERITANCE
      sem_restorebaseprio(stcb, sem);
      sched_unlock();
#endif
      ret = OK;

      /* Interrupts may now be enabled. */

      leave_critical_section(flags);
    }
  else
    {
      set_errno(EINVAL);
    }

  return ret;
}

sem_timedwait()

sem_timedwait()機(jī)制與sem_wait()大體類似黔州,它們的區(qū)別跟消息隊(duì)列進(jìn)行消息接收時(shí)mq_receive()/mq_timedreceive()區(qū)別類似,也是在代碼中創(chuàng)建一個(gè)watchdog進(jìn)行計(jì)時(shí)阔籽,當(dāng)計(jì)時(shí)結(jié)束后還沒(méi)等到信號(hào)量時(shí)流妻,此時(shí)會(huì)回調(diào)sem_timeout()接口,在該接口中取消該任務(wù)的等待仿耽,并重新調(diào)度該任務(wù)執(zhí)行合冀。

/****************************************************************************
 * Name: sem_timedwait
 *
 * Description:
 *   This function will lock the semaphore referenced by sem as in the
 *   sem_wait() function. However, if the semaphore cannot be locked without
 *   waiting for another process or thread to unlock the semaphore by
 *   performing a sem_post() function, this wait will be terminated when the
 *   specified timeout expires.
 *
 *   The timeout will expire when the absolute time specified by abstime
 *   passes, as measured by the clock on which timeouts are based (that is,
 *   when the value of that clock equals or exceeds abstime), or if the
 *   absolute time specified by abstime has already been passed at the
 *   time of the call.
 *
 * Parameters:
 *   sem - Semaphore object
 *   abstime - The absolute time to wait until a timeout is declared.
 *
 * Return Value:
 *   Zero (OK) is returned on success.  On failure, -1 (ERROR) is returned
 *   and the errno is set appropriately:
 *
 *   EINVAL    The sem argument does not refer to a valid semaphore.  Or the
 *             thread would have blocked, and the abstime parameter specified
 *             a nanoseconds field value less than zero or greater than or
 *             equal to 1000 million.
 *   ETIMEDOUT The semaphore could not be locked before the specified timeout
 *             expired.
 *   EDEADLK   A deadlock condition was detected.
 *   EINTR     A signal interrupted this function.
 *
 ****************************************************************************/

int sem_timedwait(FAR sem_t *sem, FAR const struct timespec *abstime)
{
  FAR struct tcb_s *rtcb = this_task();
  irqstate_t flags;
  int        ticks;
  int        errcode;
  int        ret = ERROR;

  DEBUGASSERT(up_interrupt_context() == false && rtcb->waitdog == NULL);

  /* sem_timedwait() is a cancellation point */

  (void)enter_cancellation_point();

  /* Verify the input parameters and, in case of an error, set
   * errno appropriately.
   */

#ifdef CONFIG_DEBUG_FEATURES
  if (!abstime || !sem)
    {
      errcode = EINVAL;
      goto errout;
    }
#endif

  /* Create a watchdog.  We will not actually need this watchdog
   * unless the semaphore is unavailable, but we will reserve it up
   * front before we enter the following critical section.
   */

  rtcb->waitdog = wd_create();
  if (!rtcb->waitdog)
    {
      errcode = ENOMEM;
      goto errout;
    }

  /* We will disable interrupts until we have completed the semaphore
   * wait.  We need to do this (as opposed to just disabling pre-emption)
   * because there could be interrupt handlers that are asynchronously
   * posting semaphores and to prevent race conditions with watchdog
   * timeout.  This is not too bad because interrupts will be re-
   * enabled while we are blocked waiting for the semaphore.
   */

  flags = enter_critical_section();

  /* Try to take the semaphore without waiting. */

  ret = sem_trywait(sem);
  if (ret == OK)
    {
      /* We got it! */

      goto success_with_irqdisabled;
    }

  /* We will have to wait for the semaphore.  Make sure that we were provided
   * with a valid timeout.
   */

  if (abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000)
    {
      errcode = EINVAL;
      goto errout_with_irqdisabled;
    }

  /* Convert the timespec to clock ticks.  We must have interrupts
   * disabled here so that this time stays valid until the wait begins.
   */

  errcode = clock_abstime2ticks(CLOCK_REALTIME, abstime, &ticks);

  /* If the time has already expired return immediately. */

  if (errcode == OK && ticks <= 0)
    {
      errcode = ETIMEDOUT;
      goto errout_with_irqdisabled;
    }

  /* Handle any time-related errors */

  if (errcode != OK)
    {
      goto errout_with_irqdisabled;
    }

  /* Start the watchdog */

  (void)wd_start(rtcb->waitdog, ticks, (wdentry_t)sem_timeout, 1, getpid());

  /* Now perform the blocking wait */

  ret = sem_wait(sem);
  if (ret < 0)
    {
      /* sem_wait() failed.  Save the errno value */

      errcode = get_errno();
    }

  /* Stop the watchdog timer */

  wd_cancel(rtcb->waitdog);

  if (errcode != OK)
    {
      goto errout_with_irqdisabled;
    }

  /* We can now restore interrupts and delete the watchdog */

  /* Success exits */

success_with_irqdisabled:
  leave_critical_section(flags);
  wd_delete(rtcb->waitdog);
  rtcb->waitdog = NULL;
  leave_cancellation_point();
  return OK;

  /* Error exits */

errout_with_irqdisabled:
  leave_critical_section(flags);
  wd_delete(rtcb->waitdog);
  rtcb->waitdog = NULL;

errout:
  set_errno(errcode);
  leave_cancellation_point();
  return ERROR;
}

總結(jié)

總體來(lái)說(shuō),在Nuttx中信號(hào)量既可用于同步和互斥處理项贺,當(dāng)任務(wù)等不到信號(hào)量時(shí),便添加到相關(guān)的任務(wù)隊(duì)列中進(jìn)行阻塞睡眠峭判,當(dāng)釋放信號(hào)量時(shí)开缎,再去該任務(wù)隊(duì)列中進(jìn)行查詢,重新調(diào)度該任務(wù)執(zhí)行林螃。如果遇到優(yōu)先級(jí)反轉(zhuǎn)的情況奕删,優(yōu)先級(jí)繼承是一種解決方法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疗认,一起剝皮案震驚了整個(gè)濱河市完残,隨后出現(xiàn)的幾起案子伏钠,更是在濱河造成了極大的恐慌,老刑警劉巖谨设,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熟掂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡扎拣,警方通過(guò)查閱死者的電腦和手機(jī)赴肚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)二蓝,“玉大人誉券,你說(shuō)我怎么就攤上這事】蓿” “怎么了踊跟?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鸥诽。 經(jīng)常有香客問(wèn)我商玫,道長(zhǎng),這世上最難降的妖魔是什么衙传? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任决帖,我火速辦了婚禮,結(jié)果婚禮上蓖捶,老公的妹妹穿的比我還像新娘地回。我一直安慰自己,他們只是感情好俊鱼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布刻像。 她就那樣靜靜地躺著,像睡著了一般并闲。 火紅的嫁衣襯著肌膚如雪细睡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天帝火,我揣著相機(jī)與錄音溜徙,去河邊找鬼。 笑死犀填,一個(gè)胖子當(dāng)著我的面吹牛蠢壹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播九巡,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼图贸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起疏日,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤偿洁,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后沟优,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涕滋,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年净神,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了何吝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鹃唯,死狀恐怖爱榕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坡慌,我是刑警寧澤黔酥,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站洪橘,受9級(jí)特大地震影響跪者,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜熄求,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一渣玲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弟晚,春花似錦忘衍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至瑟押,卻和暖如春搀捷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背多望。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工嫩舟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怀偷。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓至壤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親枢纠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容