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)題迁酸,但是存在以下的情況:
- 低優(yōu)先級(jí)任務(wù)
Task C
先鱼,獲取一個(gè)信號(hào)量,獲得對(duì)保護(hù)資源的獨(dú)占使用奸鬓; - 任務(wù)
Task C
掛起焙畔,讓高優(yōu)先級(jí)任務(wù)Task A
執(zhí)行; - 任務(wù)
Task A
試圖獲取任務(wù)Task C
所持有的信號(hào)量而被阻塞串远,直到任務(wù)Task C
放棄信號(hào)量宏多; - 任務(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)量
VSSignaling信號(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)一張圖吧:
信號(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è)工作:
- 判斷是否在中斷上下文中芋酌,由于
sem_wait()
可能觸發(fā)任務(wù)調(diào)度增显,造成本身睡眠,因此不能在中斷上下文中調(diào)用隔嫡; - 如果信號(hào)量可用甸怕,將計(jì)數(shù)值減1甘穿,并將調(diào)用任務(wù)添加到信號(hào)量的持有者鏈表中;
- 如果信號(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ù):
- 調(diào)用
sem_releaseholder()
接口來(lái)將本任務(wù)中持有信號(hào)量的次數(shù)減1咒唆; - 增加信號(hào)量計(jì)數(shù)值届垫;
- 當(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ù)。 - 調(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í)繼承是一種解決方法。