溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

TencentOS tiny信號(hào)量的原理以及創(chuàng)建、銷(xiāo)毀、獲取信號(hào)量的方法

發(fā)布時(shí)間:2021-09-04 09:33:34 來(lái)源:億速云 閱讀:162 作者:chen 欄目:互聯(lián)網(wǎng)科技

這篇文章主要介紹“TencentOS  tiny信號(hào)量的原理以及創(chuàng)建、銷(xiāo)毀、獲取信號(hào)量的方法”,在日常操作中,相信很多人在TencentOS  tiny信號(hào)量的原理以及創(chuàng)建、銷(xiāo)毀、獲取信號(hào)量的方法問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”TencentOS  tiny信號(hào)量的原理以及創(chuàng)建、銷(xiāo)毀、獲取信號(hào)量的方法”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

信號(hào)量

信號(hào)量(sem)在操作系統(tǒng)中是一種實(shí)現(xiàn)系統(tǒng)中任務(wù)與任務(wù)、任務(wù)與中斷間同步或者臨界資源互斥保護(hù)的機(jī)制。在多任務(wù)系統(tǒng)中,各任務(wù)之間常需要同步或互斥,信號(hào)量就可以為用戶提供這方面的支持。

抽象來(lái)說(shuō),信號(hào)量是一個(gè)非負(fù)整數(shù),每當(dāng)信號(hào)量被獲取(pend)時(shí),該整數(shù)會(huì)減一,當(dāng)該整數(shù)的值為 0 時(shí),表示信號(hào)量處于無(wú)效狀態(tài),將無(wú)法被再次獲取,所有試圖獲取它的任務(wù)將進(jìn)入阻塞態(tài)。通常一個(gè)信號(hào)量是有計(jì)數(shù)值的,它的計(jì)數(shù)值可以用于系統(tǒng)資源計(jì)數(shù)(統(tǒng)計(jì))。

一般來(lái)說(shuō)信號(hào)量的值有兩種:

  • 0:表示沒(méi)有積累下來(lái)的post信號(hào)量操作,且可能有任務(wù)阻塞在此信號(hào)量上。

  • 正值:表示有一個(gè)或多個(gè)post信號(hào)量操作。

一般來(lái)說(shuō)信號(hào)量多用于同步而非互斥,因?yàn)椴僮飨到y(tǒng)中會(huì)提供另一種互斥機(jī)制(互斥鎖),互斥量的互斥作用更完善:互斥鎖有優(yōu)先級(jí)繼承機(jī)制,而信號(hào)量則沒(méi)有這個(gè)機(jī)制,此外互斥量還擁有所有者屬性,我們會(huì)在后續(xù)講解。

信號(hào)量也如隊(duì)列一樣,擁有阻塞機(jī)制。任務(wù)需要等待某個(gè)中斷發(fā)生后,再去執(zhí)行對(duì)應(yīng)的處理,那么任務(wù)可以處于阻塞態(tài)等待信號(hào)量,直到中斷發(fā)生后釋放信號(hào)量后,該任務(wù)才被喚醒去執(zhí)行對(duì)應(yīng)的處理。在釋放(post)信號(hào)量的時(shí)候能立即將等待的任務(wù)轉(zhuǎn)變?yōu)榫途w態(tài),如果任務(wù)的優(yōu)先級(jí)在就緒任務(wù)中是最高的,任務(wù)就能立即被運(yùn)行,這就是操作系統(tǒng)中的“實(shí)時(shí)響應(yīng),實(shí)時(shí)處理”。在操作系統(tǒng)中使用信號(hào)量可以提高處理的效率。

信號(hào)量的數(shù)據(jù)結(jié)構(gòu)

信號(hào)量控制塊

TencentOS tiny 通過(guò)信號(hào)量控制塊操作信號(hào)量,其數(shù)據(jù)類型為k_sem_t ,信號(hào)量控制塊由多個(gè)元素組成,主要有 pend_obj_t 類型的pend_obj以及k_sem_cnt_t類型的count。而pend_obj有點(diǎn)類似于面向?qū)ο蟮睦^承,繼承一些屬性,里面有描述內(nèi)核資源的類型(如信號(hào)量、隊(duì)列、互斥量等,同時(shí)還有一個(gè)等待列表list)。而count則是一個(gè)簡(jiǎn)單的變量(它是16位的無(wú)符號(hào)整數(shù)),表示信號(hào)量的值。

typedef struct k_sem_st {
    pend_obj_t      pend_obj;
    k_sem_cnt_t     count;
} k_sem_t;

與信號(hào)量相關(guān)的宏定義

tos_config.h中,使能信號(hào)量的宏定義是TOS_CFG_SEM_EN

#define TOS_CFG_SEM_EN              1u

信號(hào)量實(shí)現(xiàn)

TencentOS tiny 中實(shí)現(xiàn)信號(hào)量非常簡(jiǎn)單,核心代碼僅僅只有125行,可以說(shuō)是非常少了。

創(chuàng)建信號(hào)量

系統(tǒng)中每個(gè)信號(hào)量都有對(duì)應(yīng)的信號(hào)量控制塊,信號(hào)量控制塊中包含了信號(hào)量的所有信息,比如它的等待列表、它的資源類型,以及它的信號(hào)量值,那么可以想象一下,創(chuàng)建信號(hào)量的本質(zhì)是不是就是對(duì)信號(hào)量控制塊進(jìn)行初始化呢?很顯然就是這樣子的。因?yàn)樵诤罄m(xù)對(duì)信號(hào)量的操作都是通過(guò)信號(hào)量控制塊來(lái)操作的,如果控制塊沒(méi)有信息,那怎么能操作嘛~

創(chuàng)建信號(hào)量函數(shù)是tos_sem_create(),傳入兩個(gè)參數(shù),一個(gè)是信號(hào)量控制塊的指針*sem,另一個(gè)是信號(hào)量的初始值init_count,該值是非負(fù)整數(shù)即可,但主要不能超過(guò)65535。

實(shí)際上就是調(diào)用pend_object_init()函數(shù)將信號(hào)量控制塊中的sem->pend_obj成員變量進(jìn)行初始化,它的資源類型被標(biāo)識(shí)為PEND_TYPE_SEM。然后將sem->count成員變量設(shè)置為傳遞進(jìn)來(lái)的信號(hào)量的初始值init_count。

__API__ k_err_t tos_sem_create(k_sem_t *sem, k_sem_cnt_t init_count)
{
    TOS_PTR_SANITY_CHECK(sem);

    pend_object_init(&sem->pend_obj, PEND_TYPE_SEM);
    sem->count = init_count;
	
    return K_ERR_NONE;
}

銷(xiāo)毀信號(hào)量

信號(hào)量銷(xiāo)毀函數(shù)是根據(jù)信號(hào)量控制塊直接銷(xiāo)毀的,銷(xiāo)毀之后信號(hào)量的所有信息都會(huì)被清除,而且不能再次使用這個(gè)信號(hào)量,當(dāng)信號(hào)量被銷(xiāo)毀時(shí),其等待列表中存在任務(wù),系統(tǒng)有必要將這些等待這些任務(wù)喚醒,并告知任務(wù)信號(hào)量已經(jīng)被銷(xiāo)毀了PEND_STATE_DESTROY。然后產(chǎn)生一次任務(wù)調(diào)度以切換到最高優(yōu)先級(jí)任務(wù)執(zhí)行。

TencentOS tiny 對(duì)信號(hào)量銷(xiāo)毀的處理流程如下:

  1. 調(diào)用pend_is_nopending()函數(shù)判斷一下是否有任務(wù)在等待信號(hào)量

  2. 如果有則調(diào)用pend_wakeup_all()函數(shù)將這些任務(wù)喚醒,并且告知等待任務(wù)信號(hào)量已經(jīng)被銷(xiāo)毀了(即設(shè)置任務(wù)控制塊中的等待狀態(tài)成員變量pend_statePEND_STATE_DESTROY)。

  3. 調(diào)用pend_object_deinit()函數(shù)將信號(hào)量控制塊中的內(nèi)容清除,最主要的是將控制塊中的資源類型設(shè)置為PEND_TYPE_NONE,這樣子就無(wú)法使用這個(gè)信號(hào)量了。

  4. 進(jìn)行任務(wù)調(diào)度knl_sched()

注意:如果信號(hào)量控制塊的RAM是由編譯器靜態(tài)分配的,所以即使是銷(xiāo)毀了信號(hào)量,這個(gè)內(nèi)存也是沒(méi)辦法釋放的。當(dāng)然你也可以使用動(dòng)態(tài)內(nèi)存為信號(hào)量控制塊分配內(nèi)存,只不過(guò)在銷(xiāo)毀后要將這個(gè)內(nèi)存釋放掉,避免內(nèi)存泄漏。

__API__ k_err_t tos_sem_destroy(k_sem_t *sem)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_PTR_SANITY_CHECK(sem);

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!pend_object_verify(&sem->pend_obj, PEND_TYPE_SEM)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    TOS_CPU_INT_DISABLE();

    if (!pend_is_nopending(&sem->pend_obj)) {
        pend_wakeup_all(&sem->pend_obj, PEND_STATE_DESTROY);
    }

    pend_object_deinit(&sem->pend_obj);

    TOS_CPU_INT_ENABLE();
    knl_sched();

    return K_ERR_NONE;
}

獲取信號(hào)量

tos_sem_pend()函數(shù)用于獲取信號(hào)量,當(dāng)信號(hào)量有效的時(shí)候,任務(wù)才能獲取信號(hào)量。任務(wù)獲取了某個(gè)信號(hào)量時(shí),該信號(hào)量的可用個(gè)數(shù)減一,當(dāng)它為0的時(shí)候,獲取信號(hào)量的任務(wù)會(huì)進(jìn)入阻塞態(tài),阻塞時(shí)間timeout由用戶指定,在指定時(shí)間還無(wú)法獲取到信號(hào)量時(shí),將發(fā)送超時(shí),等待任務(wù)將自動(dòng)恢復(fù)為就緒態(tài)。

獲取信號(hào)量的過(guò)程如下:

  1. 首先檢測(cè)傳入的參數(shù)是否正確。

  2. 判斷信號(hào)量控制塊中的count成員變量是否大于0,大于0表示存在可用信號(hào)量,將count成員變量的值減1,任務(wù)獲取成功后返回K_ERR_NONE。

  3. 如果不存在信號(hào)量則可能會(huì)阻塞當(dāng)前獲取的任務(wù),看一下用戶指定的阻塞時(shí)間timeout是否為不阻塞TOS_TIME_NOWAIT,如果不阻塞則直接返回K_ERR_PEND_NOWAIT錯(cuò)誤代碼。

  4. 如果調(diào)度器被鎖了knl_is_sched_locked(),則無(wú)法進(jìn)行等待操作,返回錯(cuò)誤代碼K_ERR_PEND_SCHED_LOCKED,畢竟需要切換任務(wù),調(diào)度器被鎖則無(wú)法切換任務(wù)。

  5. 調(diào)用pend_task_block()函數(shù)將任務(wù)阻塞,該函數(shù)實(shí)際上就是將任務(wù)從就緒列表中移除k_rdyq.task_list_head[task_prio],并且插入到等待列表中object->list,如果等待的時(shí)間不是永久等待TOS_TIME_FOREVER,還會(huì)將任務(wù)插入時(shí)間列表中k_tick_list,阻塞時(shí)間為timeout,然后進(jìn)行一次任務(wù)調(diào)度knl_sched()。

  6. 當(dāng)程序能行到pend_state2errno()時(shí),則表示任務(wù)等獲取到信號(hào)量,又或者等待發(fā)生了超時(shí),那么就調(diào)用pend_state2errno()函數(shù)獲取一下任務(wù)的等待狀態(tài),看一下是哪種情況導(dǎo)致任務(wù)恢復(fù)運(yùn)行,并且將結(jié)果返回給調(diào)用獲取信號(hào)量的任務(wù)。

注意:當(dāng)獲取信號(hào)量的任務(wù)能從阻塞中恢復(fù)運(yùn)行,也不一定是獲取到信號(hào)量,也可能是發(fā)生了超時(shí),因此在寫(xiě)程序的時(shí)候必須要判斷一下獲取的信號(hào)量狀態(tài),如果是K_ERR_NONE則表示獲取成功!

__API__ k_err_t tos_sem_pend(k_sem_t *sem, k_tick_t timeout)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_PTR_SANITY_CHECK(sem);
    TOS_IN_IRQ_CHECK();

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!pend_object_verify(&sem->pend_obj, PEND_TYPE_SEM)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    TOS_CPU_INT_DISABLE();

    if (sem->count > (k_sem_cnt_t)0u) {
        --sem->count;
        TOS_CPU_INT_ENABLE();
        return K_ERR_NONE;
    }

    if (timeout == TOS_TIME_NOWAIT) { // no wait, return immediately
        TOS_CPU_INT_ENABLE();
        return K_ERR_PEND_NOWAIT;
    }

    if (knl_is_sched_locked()) {
        TOS_CPU_INT_ENABLE();
        return K_ERR_PEND_SCHED_LOCKED;
    }

    pend_task_block(k_curr_task, &sem->pend_obj, timeout);

    TOS_CPU_INT_ENABLE();
    knl_sched();

    return pend_state2errno(k_curr_task->pend_state);
}

釋放信號(hào)量

任務(wù)或者中斷服務(wù)程序都可以釋放信號(hào)量(post),釋放信號(hào)量的本質(zhì)就是將信號(hào)量控制塊的count成員變量的值加1,表示信號(hào)量有效,不過(guò)如果有任務(wù)在等待這個(gè)信號(hào)量時(shí),信號(hào)量控制塊的count成員變量的值是不會(huì)改變的,因?yàn)橐獑拘训却蝿?wù),而喚醒等待任務(wù)的本質(zhì)就是等待任務(wù)獲取到信號(hào)量,信號(hào)量控制塊的count成員變量的值要減1,這一來(lái)一回中,信號(hào)量控制塊的count成員變量的值是不會(huì)改變的。

TencentOS tiny 中可以只讓等待中的一個(gè)任務(wù)獲取到信號(hào)量,也可以讓所有等待任務(wù)都獲取到信號(hào)量。分別對(duì)應(yīng)的API是tos_sem_post()tos_sem_post_all()。順便提一點(diǎn),tos_sem_post_all()的設(shè)計(jì)模式其實(shí)是觀察者模式,當(dāng)一個(gè)觀察的對(duì)象改變后,那么所有的觀察者都會(huì)知道它改變了,具體可以看看《大話設(shè)計(jì)模式》這本書(shū)。

TencentOS tiny 中設(shè)計(jì)的很好的地方就是簡(jiǎn)單與低耦合,這兩個(gè)api接口本質(zhì)上都是調(diào)用sem_do_post()函數(shù)去釋放信號(hào)量,只是通過(guò)opt參數(shù)不同選擇不同的處理方法。

sem_do_post()函數(shù)中的處理也是非常簡(jiǎn)單明了的,其執(zhí)行思路如下:

  1. 首先判斷一下信號(hào)量是否溢出了,因?yàn)橐粋€(gè)整數(shù)始終都會(huì)溢出的,總不能一直釋放信號(hào)量讓count成員變量的值加1吧,因此必須要判斷一下是否溢出,如果sem->count 的值為 (k_sem_cnt_t)-1,則表示已經(jīng)溢出,無(wú)法繼續(xù)釋放信號(hào)量,返回錯(cuò)誤代碼K_ERR_SEM_OVERFLOW。

  2. 調(diào)用pend_is_nopending()函數(shù)判斷一下是否有任務(wù)在等待信號(hào)量,如果沒(méi)有則將count成員變量的值加1,返回K_ERR_NONE表示釋放信號(hào)量成功,因?yàn)榇藭r(shí)沒(méi)有喚醒任務(wù)也就無(wú)需任務(wù)調(diào)度,直接返回即可。

  3. 如果有任務(wù)在等待信號(hào)量,則count成員變量的值無(wú)需加1,直接調(diào)用pend_wakeup喚醒對(duì)應(yīng)的任務(wù)即可,喚醒任務(wù)則是根據(jù)opt參數(shù)進(jìn)行喚醒,可以喚醒等待中的一個(gè)任務(wù)或者是所有任務(wù)。

  4. 進(jìn)行一次任務(wù)調(diào)度knl_sched()。

__API__ k_err_t tos_sem_post(k_sem_t *sem)
{
    TOS_PTR_SANITY_CHECK(sem);

    return sem_do_post(sem, OPT_POST_ONE);
}

__API__ k_err_t tos_sem_post_all(k_sem_t *sem)
{
    TOS_PTR_SANITY_CHECK(sem);

    return sem_do_post(sem, OPT_POST_ALL);
}

__STATIC__ k_err_t sem_do_post(k_sem_t *sem, opt_post_t opt)
{
    TOS_CPU_CPSR_ALLOC();

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!pend_object_verify(&sem->pend_obj, PEND_TYPE_SEM)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    TOS_CPU_INT_DISABLE();

    if (sem->count == (k_sem_cnt_t)-1) {
        TOS_CPU_INT_ENABLE();
        return K_ERR_SEM_OVERFLOW;
    }

    if (pend_is_nopending(&sem->pend_obj)) {
        ++sem->count;
        TOS_CPU_INT_ENABLE();
        return K_ERR_NONE;
    }

    pend_wakeup(&sem->pend_obj, PEND_STATE_POST, opt);

    TOS_CPU_INT_ENABLE();
    knl_sched();

    return K_ERR_NONE;
}

關(guān)于為什么判斷sem->count(k_sem_cnt_t)-1就代表溢出呢?我在C語(yǔ)言中舉了個(gè)簡(jiǎn)單的例子:

#include <stdio.h>

int main()
{
    unsigned int a = ~0;
    if(a == (unsigned int)0XFFFFFFFF)
    {
        printf("OK\n");
    }
    if(a == (unsigned int)-1)
    {
        printf("OK\n");
    }
    
   printf("unsigned int a = %d \n",a);
   
   return 0;
}

輸出:
OK
OK
unsigned int a = -1

到此,關(guān)于“TencentOS  tiny信號(hào)量的原理以及創(chuàng)建、銷(xiāo)毀、獲取信號(hào)量的方法”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI