LiteOS內核源碼分析:消息隊列Queue

摘要:本文通過分析LiteOS隊列模塊的源碼,掌握隊列使用上的差異。

本文分享自華爲雲社區《LiteOS內核源碼分析系列十 消息隊列Queue》,原文作者:zhushy 。

隊列(Queue)是一種常用於任務間通信的數據結構。任務能夠從隊列裏面讀取消息,當隊列中的消息爲空時,掛起讀取任務;當隊列中有新消息時,掛起的讀取任務被喚醒並處理新消息。任務也能夠往隊列裏寫入消息,當隊列已經寫滿消息時,掛起寫入任務;當隊列中有空閒消息節點時,掛起的寫入任務被喚醒並寫入消息。如果將讀隊列和寫隊列的超時時間設置爲0,則不會掛起任務,接口會直接返回,這就是非阻塞模式。消息隊列提供了異步處理機制,允許將一個消息放入隊列,但不立即處理。同時隊列還有緩衝消息的作用。

本文通過分析LiteOS隊列模塊的源碼,掌握隊列使用上的差異。LiteOS隊列模塊的源代碼, 均可以在LiteOS開源站點https://gitee.com/LiteOS/LiteOS 獲取。 隊列源代碼、開發文檔,示例程序代碼如下:

  • LiteOS內核隊列源代碼

包括隊列的私有頭文件kernel\base\include\los_queue_pri.h、頭文件kernel\include\los_queue.h、C源代碼文件kernel\base\los_queue.c。

  • 開發指南文檔–隊列

在線文檔 https://gitee.com/LiteOS/LiteOS/blob/master/doc/LiteOS_Kernel_Developer_Guide.md#%E9%98%9F%E5%88%97

接下來,我們看下隊列的結構體,隊列初始化,隊列常用操作的源代碼。

1、隊列結構體定義和常用宏定義

1.1 隊列結構體定義

在文件kernel\base\include\los_queue_pri.h定義的隊列控制塊結構體爲LosQueueCB,結構體源代碼如下。隊列狀態.queueState取值OS_QUEUE_UNUSED、OS_QUEUE_INUSED,隊列內存類型.queueMemType取值OS_QUEUE_ALLOC_STATIC、OS_QUEUE_ALLOC_DYNAMIC,分別表示計用戶分配隊列內存空間或系統自動分配。

typedef struct {
    UINT8 *queueHandle; /**< 隊列內存空間的指針 */
    UINT8 queueState; /**< 隊列的使用狀態 */
    UINT8 queueMemType; /**< 隊列內存類型,用戶分配隊列內存空間或系統自動分配 */
    UINT16 queueLen; /**< 隊列長度,消息數量 */
    UINT16 queueSize; /**< 消息節點大小 */
    UINT32 queueId; /**< 隊列編號 */
    UINT16 queueHead; /**< 消息頭節點位置 */
    UINT16 queueTail; /**< 消息尾節點位置 */
    UINT16 readWriteableCnt[OS_QUEUE_N_RW]; /**< 2維數組,可讀、可寫的消息數量, 0:可讀, 1:可寫 */
    LOS_DL_LIST readWriteList[OS_QUEUE_N_RW]; /**< 2維鏈表數組,阻塞讀、寫任務的雙向鏈表, 0:讀鏈表, 1:寫鏈表 */
    LOS_DL_LIST memList; /**< 內存節點雙向鏈表,兼容CMSIS時使用 */
} LosQueueCB;

1.2 隊列常用宏定義

系統支持創建多少隊列是根據開發板情況使用宏LOSCFG_BASE_IPC_QUEUE_LIMIT定義的,每一個隊列queueId是UINT32類型的。隊列queueId由2部分組成:count和index,分別處於高16位和低16位。創建隊列,使用後刪除時,隊列回收到隊列池時,隊列queueId的高16位即count值會加1,這樣可以用來表示該隊列被創建刪除的次數。index取值爲[0,LOSCFG_BASE_IPC_QUEUE_LIMIT),表示隊列池中各個隊列的編號。

⑴處的宏用來分割count和index的位數,⑵處的宏在隊列被刪除時用來更新隊列編號queueId,可以看出高16位爲count和低16位爲index。⑶處獲取隊列queueId的低16位。⑷根據隊列queueId獲取對應的隊列被創建刪除的次數count。⑸處從隊列池中獲取指定隊列queueId對應的隊列控制塊。

⑴    #define QUEUE_SPLIT_BIT        16

⑵    #define SET_QUEUE_ID(count, index)    (((count) << QUEUE_SPLIT_BIT) | (index))

⑶    #define GET_QUEUE_INDEX(queueId)        ((queueId) & ((1U << QUEUE_SPLIT_BIT) - 1))

⑷    #define GET_QUEUE_COUNT(queueId)        ((queueId) >> QUEUE_SPLIT_BIT)

⑸    #define GET_QUEUE_HANDLE(queueId)       (((LosQueueCB *)g_allQueue) + GET_QUEUE_INDEX(queueId))

另外,隊列中還提供了比較重要的隊列讀取消息操作相關的枚舉和宏。隊列的讀取消息操作類型和隊首還是隊尾寫入,讀取還是寫入有關係,所以操作類型使用2比特的數字來表示,高1位表示隊首還是隊尾,低1位表示讀取還是寫入。定義如下:

typedef enum {
    OS_QUEUE_READ = 0,
    OS_QUEUE_WRITE = 1,
    OS_QUEUE_N_RW = 2
} QueueReadWrite;

typedef enum {
    OS_QUEUE_HEAD = 0,
    OS_QUEUE_TAIL = 1
} QueueHeadTail;

#define OS_QUEUE_OPERATE_TYPE(ReadOrWrite, HeadOrTail) (((UINT32)(HeadOrTail) << 1) | (ReadOrWrite))
#define OS_QUEUE_READ_WRITE_GET(type) ((type) & 0x01U)
#define OS_QUEUE_READ_HEAD     (OS_QUEUE_READ | (OS_QUEUE_HEAD << 1))
#define OS_QUEUE_READ_TAIL     (OS_QUEUE_READ | (OS_QUEUE_TAIL << 1))
#define OS_QUEUE_WRITE_HEAD    (OS_QUEUE_WRITE | (OS_QUEUE_HEAD << 1))
#define OS_QUEUE_WRITE_TAIL    (OS_QUEUE_WRITE | (OS_QUEUE_TAIL << 1))
#define OS_QUEUE_OPERATE_GET(type) ((type) & 0x03U)
#define OS_QUEUE_IS_READ(type) (OS_QUEUE_READ_WRITE_GET(type) == OS_QUEUE_READ)
#define OS_QUEUE_IS_WRITE(type) (OS_QUEUE_READ_WRITE_GET(type) == OS_QUEUE_WRITE)

2、隊列初始化

隊列在內核中默認開啓,用戶可以通過宏LOSCFG_BASE_IPC_QUEUE進行關閉。開啓隊列的情況下,在系統啓動時,在kernel\init\los_init.c中調用OsQueueInit()進行隊列模塊初始化。下面,我們分析下隊列初始化的代碼。

⑴爲隊列申請內存,如果申請失敗,則返回錯誤。⑵初始化雙向循環鏈表g_freeQueueList,維護未使用的隊列。⑶循環每一個隊列進行初始化,爲每一個隊列節點指定索引queueId,並把隊列節點插入未使用隊列雙向鏈表g_freeQueueList。代碼上可以看出,掛在未使用隊列雙向鏈表上的節點是每個隊列控制塊的寫阻塞任務鏈表點.readWriteList[OS_QUEUE_WRITE]。⑷如果開啓了隊列調測開關,則調用函數OsQueueDbgInitHook()進行初始化。

LITE_OS_SEC_TEXT_INIT UINT32 OsQueueInit(VOID)
{
    LosQueueCB *queueNode = NULL;
    UINT32 index;
    UINT32 size;

    size = LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB);
⑴  g_allQueue = (LosQueueCB *)LOS_MemAlloc(m_aucSysMem0, size);
    if (g_allQueue == NULL) {
        return LOS_ERRNO_QUEUE_NO_MEMORY;
    }
    (VOID)memset_s(g_allQueue, size, 0, size);
⑵  LOS_ListInit(&g_freeQueueList);
⑶  for (index = 0; index < LOSCFG_BASE_IPC_QUEUE_LIMIT; index++) {
        queueNode = ((LosQueueCB *)g_allQueue) + index;
        queueNode->queueId = index;
        LOS_ListTailInsert(&g_freeQueueList, &queueNode->readWriteList[OS_QUEUE_WRITE]);
    }

⑷  if (OsQueueDbgInitHook() != LOS_OK) {
        return LOS_ERRNO_QUEUE_NO_MEMORY;
    }
    return LOS_OK;
}

3、隊列常用操作

3.1 隊列創建

創建隊列的函數有2個:LOS_QueueCreateStatic()和LOS_QueueCreate(),二者的區別是存放隊列消息的內存空間是用戶創建還是系統自動創建。前者需要開啓宏LOSCFG_QUEUE_STATIC_ALLOCATION時纔可用,使用用戶分配隊列內存空間,後者在創建隊列時動態創建隊列內存空間。我們看看2個函數的傳參:queueName是隊列名稱,實際上並沒有使用。len是隊列消息的長度,queueId是隊列編號,flags保留未使用maxMsgSize是隊列中每條消息的最大大小。對於靜態創建隊列,還有2個參數來表示用戶創建的隊列內存空間的地址指針queueMem和內存大小memSize。

我們分析下創建隊列的代碼。先看靜態創建,調用⑴處的函數OsQueueCreateParameterCheck()對參數進行校驗,⑵對傳入的內存空間進行驗證,確保足夠大放得下隊列的消息,其中maxMsgSize + sizeof(UINT32)表示消息最大大小,另外再加4個字節,在消息的最後4個字節用來保存消息的實際長度。然後調用⑶處函數OsQueueCreateInternal()完成隊列創建。再看下動態創建隊列的代碼,⑷處對隊列動態申請內存,然後調用函數OsQueueCreateInternal()完成隊列創建。2個函數調用同樣的內部創建隊列函數OsQueueCreateInternal(),下文繼續分析。

#ifdef LOSCFG_QUEUE_STATIC_ALLOCATION
LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueCreateStatic(CHAR *queueName,
                                                   UINT16 len,
                                                   UINT32 *queueId,
                                                   UINT32 flags,
                                                   UINT16 maxMsgSize,
                                                   VOID *queueMem,
                                                   UINT16 memSize)
{
    UINT32 ret;
    UINT16 msgSize;
    (VOID)queueName;
    (VOID)flags;

⑴  ret = OsQueueCreateParameterCheck(len, queueId, maxMsgSize);
    if (ret != LOS_OK) {
        return ret;
    }

    if (queueMem == NULL) {
        return LOS_ERRNO_QUEUE_CREAT_PTR_NULL;
    }

⑵  msgSize = maxMsgSize + sizeof(UINT32);
    if (memSize < ((UINT32)msgSize * len)) {
        return LOS_ERRNO_QUEUE_CREATE_NO_MEMORY;
    }

⑶  return OsQueueCreateInternal(len, queueId, msgSize, queueMem, OS_QUEUE_ALLOC_STATIC);
}
#endif

LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueCreate(CHAR *queueName, UINT16 len, UINT32 *queueId,
                                             UINT32 flags, UINT16 maxMsgSize)
{
    UINT32 ret;
    UINT8 *queueMem = NULL;
    UINT16 msgSize;
    (VOID)queueName;
    (VOID)flags;

    ret = OsQueueCreateParameterCheck(len, queueId, maxMsgSize);
    if (ret != LOS_OK) {
        return ret;
    }

    msgSize = maxMsgSize + sizeof(UINT32);
⑷  queueMem = (UINT8 *)LOS_MemAlloc(m_aucSysMem1, (UINT32)len * msgSize);
    if (queueMem == NULL) {
        return LOS_ERRNO_QUEUE_CREATE_NO_MEMORY;
    }

    ret = OsQueueCreateInternal(len, queueId, msgSize, queueMem, OS_QUEUE_ALLOC_DYNAMIC);
    if (ret != LOS_OK) {
        (VOID)LOS_MemFree(m_aucSysMem1, queueMem);
        return ret;
    }

    return LOS_OK;
}

我們看看創建隊列的內部函數OsQueueCreateInternal()。⑴判斷g_freeQueueList是否爲空,如果沒有可以使用的隊列,調用函數OsQueueCheckHook()做些調測相關的檢測,這個函數需要開啓調測開關,後續系列專門分析。⑵處如果g_freeQueueList不爲空,則獲取第一個可用的隊列節點,接着從雙向鏈表g_freeQueueList中刪除,然後調用宏GET_QUEUE_LIST獲取LosQueueCB *queueCB,初始化創建的隊列信息,包含隊列的長度.queueLen、消息大小.queueSize,隊列內存空間.queueHandle,消息狀態.queueState,內存類型.queueMemType,可讀的數量.readWriteableCnt[OS_QUEUE_READ]爲0,可寫的數量readWriteableCnt[OS_QUEUE_WRITE]爲隊列消息長度len,隊列頭位置.queueHead和尾位置.queueTail爲0。

⑶初始化雙向鏈表.readWriteList[OS_QUEUE_READ],阻塞在這個隊列上的讀消息任務會掛在這個鏈表上。初始化雙向鏈表.readWriteList[OS_QUEUE_WRITE],阻塞在這個隊列上的寫消息任務會掛在這個鏈表上。初始化雙向鏈表.memList,這個開啓兼容CMSIS宏LOSCFG_COMPAT_CMSIS時,纔會使用到。⑷賦值給輸出參數*queueId,後續程序使用這個隊列編號對隊列進行其他操作。

LITE_OS_SEC_TEXT_INIT STATIC UINT32 OsQueueCreateInternal(UINT16 len, UINT32 *queueId, UINT16 msgSize,
                                                          UINT8 *queue, UINT8 queueMemType)
{
    LosQueueCB *queueCB = NULL;
    LOS_DL_LIST *unusedQueue = NULL;
    UINT32 intSave;

    SCHEDULER_LOCK(intSave);
⑴  if (LOS_ListEmpty(&g_freeQueueList)) {
        SCHEDULER_UNLOCK(intSave);
        OsQueueCheckHook();
        return LOS_ERRNO_QUEUE_CB_UNAVAILABLE;
    }

⑵  unusedQueue = LOS_DL_LIST_FIRST(&g_freeQueueList);
    LOS_ListDelete(unusedQueue);
    queueCB = GET_QUEUE_LIST(unusedQueue);
    queueCB->queueLen = len;
    queueCB->queueSize = msgSize;
    queueCB->queueHandle = queue;
    queueCB->queueState = OS_QUEUE_INUSED;
    queueCB->queueMemType = queueMemType;
    queueCB->readWriteableCnt[OS_QUEUE_READ] = 0;
    queueCB->readWriteableCnt[OS_QUEUE_WRITE] = len;
    queueCB->queueHead = 0;
    queueCB->queueTail = 0;
⑶  LOS_ListInit(&queueCB->readWriteList[OS_QUEUE_READ]);
    LOS_ListInit(&queueCB->readWriteList[OS_QUEUE_WRITE]);
    LOS_ListInit(&queueCB->memList);

    OsQueueDbgUpdateHook(queueCB->queueId, OsCurrTaskGet()->taskEntry);
    SCHEDULER_UNLOCK(intSave);

⑷  *queueId = queueCB->queueId;

    LOS_TRACE(QUEUE_CREATE, *queueId, len, msgSize - sizeof(UINT32), (UINTPTR)queue, queueMemType);
    return LOS_OK;
}

3.2 隊列刪除

我們可以使用函數LOS_QueueDelete(UINT32 queueId)來刪除隊列,下面通過分析源碼看看如何刪除隊列的。

⑴處判斷隊列queueId是否超過LOSCFG_BASE_IPC_queue_LIMIT,如果超過則返回錯誤碼。如果隊列編號沒有問題,獲取隊列控制塊LosQueueCB *queueCB。⑵處判斷要刪除的隊列queueId是否匹配,或者要刪除的隊列處於未使用狀態,則跳轉到錯誤標籤QUEUE_END`進行處理。⑶如果隊列的阻塞讀、阻塞寫任務列表不爲空,或內存節點鏈表不爲空,則不允許刪除,跳轉到錯誤標籤進行處理。⑷處檢驗隊列的可讀、可寫數量是否出錯。

⑸處獲取隊列的內存空間,如果是動態創建的內存,接下來會需要調用⑺處函數LOS_MemFree()釋放。⑹處把.queueState設置爲未使用OS_QUEUE_UNUSED,設置隊列編號.queueId,並把隊列節點插入未使用隊列雙向鏈表g_freeQueueList。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueDelete(UINT32 queueId)
{
    LosQueueCB *queueCB = NULL;
    UINT8 *queue = NULL;
    UINT32 intSave;
    UINT32 ret = LOS_OK;

⑴  if (GET_QUEUE_INDEX(queueId) >= LOSCFG_BASE_IPC_QUEUE_LIMIT) {
        return LOS_ERRNO_QUEUE_NOT_FOUND;
    }

    queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueId);

    LOS_TRACE(QUEUE_DELETE, queueId, queueCB->queueState, queueCB->readWriteableCnt[OS_QUEUE_READ]);

    SCHEDULER_LOCK(intSave);
⑵  if ((queueCB->queueId != queueId) || (queueCB->queueState == OS_QUEUE_UNUSED)) {
        ret = LOS_ERRNO_QUEUE_NOT_CREATE;
        goto QUEUE_END;
    }

⑶  if (!LOS_ListEmpty(&queueCB->readWriteList[OS_QUEUE_READ])) {
        ret = LOS_ERRNO_QUEUE_IN_TSKUSE;
        goto QUEUE_END;
    }

    if (!LOS_ListEmpty(&queueCB->readWriteList[OS_QUEUE_WRITE])) {
        ret = LOS_ERRNO_QUEUE_IN_TSKUSE;
        goto QUEUE_END;
    }

    if (!LOS_ListEmpty(&queueCB->memList)) {
        ret = LOS_ERRNO_QUEUE_IN_TSKUSE;
        goto QUEUE_END;
    }

⑷  if ((queueCB->readWriteableCnt[OS_QUEUE_WRITE] + queueCB->readWriteableCnt[OS_QUEUE_READ]) !=
        queueCB->queueLen) {
        ret = LOS_ERRNO_QUEUE_IN_TSKWRITE;
        goto QUEUE_END;
    }

⑸  queue = queueCB->queueHandle;
⑹  queueCB->queueHandle = NULL;
    queueCB->queueState = OS_QUEUE_UNUSED;
    queueCB->queueId = SET_QUEUE_ID(GET_QUEUE_COUNT(queueCB->queueId) + 1, GET_QUEUE_INDEX(queueCB->queueId));
    OsQueueDbgUpdateHook(queueCB->queueId, NULL);

    LOS_ListTailInsert(&g_freeQueueList, &queueCB->readWriteList[OS_QUEUE_WRITE]);
    SCHEDULER_UNLOCK(intSave);
    if (queueCB->queueMemType == OS_QUEUE_ALLOC_DYNAMIC) {
⑺      ret = LOS_MemFree(m_aucSysMem1, (VOID *)queue);
    }
    return ret;

QUEUE_END:
    SCHEDULER_UNLOCK(intSave);
    return ret;
}

下面就來看看隊列的讀寫,有2點需要注意:

  • (1)隊首、隊尾的讀寫

只支持隊首讀取,否則就不算隊列了。除了正常的隊尾寫消息外,還提供插隊機制,支持從隊首寫入。

  • (2)隊列消息數據內容

往隊列中寫入的消息的類型有2中,即支持按地址寫入和按值寫入(帶拷貝)。按哪種類型寫入,就需要配對的按相應的類型去讀取。

隊列讀取接口的類別,歸納如下:

3.3 隊列讀取

我們知道有2個隊列讀取方法,按引用讀取的函數LOS_QueueRead()把消息地址值bufferAddr作爲數值,進一步調用按值讀取的函數LOS_QueueReadCopy()。

LITE_OS_SEC_TEXT UINT32 LOS_QueueRead(UINT32 queueId, VOID *bufferAddr, UINT32 bufferSize, UINT32 timeout)
{
    return LOS_QueueReadCopy(queueId, bufferAddr, &bufferSize, timeout);
}

再來看看函數LOS_QueueReadCopy()。⑴處校驗傳入參數,隊列編號不能超出限制,傳入的指針不能爲空。如果timeout不爲零會阻塞時,不能在中斷中讀取隊列。⑵處操作類型表示隊首讀取,然後調用函數OsQueueOperate()進一步操作隊列。

LITE_OS_SEC_TEXT UINT32 LOS_QueueReadCopy(UINT32 queueId,
                                          VOID *bufferAddr,
                                          UINT32 *bufferSize,
                                          UINT32 timeout)
{
    UINT32 ret;
    UINT32 operateType;

⑴  ret = OsQueueReadParameterCheck(queueId, bufferAddr, bufferSize, timeout);
    if (ret != LOS_OK) {
        return ret;
    }

⑵  operateType = OS_QUEUE_OPERATE_TYPE(OS_QUEUE_READ, OS_QUEUE_HEAD);
    return OsQueueOperate(queueId, operateType, bufferAddr, bufferSize, timeout);
}

我們進一步分析下函數OsQueueOperate(),這是是比較通用的封裝,讀取,寫入都會調用這個函數,我們以讀取隊列爲例分析這個函數。⑴處獲取隊列的操作類型,爲讀取操作。⑵處先調用函數OsQueueOperateParamCheck()進行參數校驗,校驗隊列是使用中的隊列,並對讀寫消息大小進行校驗。⑶處如果可讀數量爲0,無法讀取時,如果是零等待則返回錯誤碼。如果當前鎖任務調度,跳出函數執行。否則,執行⑷把當前任務放入隊列的讀取消息阻塞隊列,然後觸發任務調度,後續的代碼暫時不再執行。如果可讀的數量不爲0,可以繼續讀取時,執行⑹處代碼把可讀數量減1,然後繼續執行⑺處代碼讀取隊列。

等讀取隊列阻塞超時,或者隊列可以讀取後,繼續執行⑸處的代碼。如果是發生超時,隊列還不能讀取,更改任務狀態,跳出函數執行。如果隊列可以讀取了,繼續執行⑺處代碼讀取隊列。⑻處在成功讀取隊列後,如果有任務阻塞在寫入隊列,則獲取阻塞鏈表中的第一個任務resumedTask,然後調用喚醒函數OsTaskWake()把待恢復的任務放入就緒隊列,觸發一次任務調度。如果無阻塞任務,則把可寫入的數量加1。

UINT32 OsQueueOperate(UINT32 queueId, UINT32 operateType, VOID *bufferAddr, UINT32 *bufferSize, UINT32 timeout)
{
    LosQueueCB *queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueId);
    LosTaskCB *resumedTask = NULL;
    UINT32 ret;
⑴  UINT32 readWrite = OS_QUEUE_READ_WRITE_GET(operateType);
    UINT32 intSave;

    LOS_TRACE(QUEUE_RW, queueId, queueCB->queueSize, *bufferSize, operateType,
        queueCB->readWriteableCnt[OS_QUEUE_READ], queueCB->readWriteableCnt[OS_QUEUE_WRITE], timeout);

    SCHEDULER_LOCK(intSave);
⑵  ret = OsQueueOperateParamCheck(queueCB, queueId, operateType, bufferSize);
    if (ret != LOS_OK) {
        goto QUEUE_END;
    }

⑶  if (queueCB->readWriteableCnt[readWrite] == 0) {
        if (timeout == LOS_NO_WAIT) {
            ret = OS_QUEUE_IS_READ(operateType) ? LOS_ERRNO_QUEUE_ISEMPTY : LOS_ERRNO_QUEUE_ISFULL;
            goto QUEUE_END;
        }

        if (!OsPreemptableInSched()) {
            ret = LOS_ERRNO_QUEUE_PEND_IN_LOCK;
            goto QUEUE_END;
        }

⑷      OsTaskWait(&queueCB->readWriteList[readWrite], OS_TASK_STATUS_PEND, timeout);

        OsSchedResched();
        SCHEDULER_UNLOCK(intSave);
        SCHEDULER_LOCK(intSave);

⑸      if (OsCurrTaskGet()->taskStatus & OS_TASK_STATUS_TIMEOUT) {
            OsCurrTaskGet()->taskStatus &= ~OS_TASK_STATUS_TIMEOUT;
            ret = LOS_ERRNO_QUEUE_TIMEOUT;
            goto QUEUE_END;
        }
    } else {
⑹       queueCB->readWriteableCnt[readWrite]--;
    }

⑺   OsQueueBufferOperate(queueCB, operateType, bufferAddr, bufferSize);

⑻  if (!LOS_ListEmpty(&queueCB->readWriteList[!readWrite])) {
        resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&queueCB->readWriteList[!readWrite]));
        OsTaskWake(resumedTask, OS_TASK_STATUS_PEND);
        SCHEDULER_UNLOCK(intSave);
        LOS_MpSchedule(OS_MP_CPU_ALL);
        LOS_Schedule();
        return LOS_OK;
    } else {
⑼      queueCB->readWriteableCnt[!readWrite]++;
    }

QUEUE_END:
    SCHEDULER_UNLOCK(intSave);
    return ret;
}

我們再繼續看下函數OsQueueBufferOperate()是具體如何讀取隊列的。⑴處switch-case語句獲取操作位置。對於⑵頭部讀取的情況,先獲取讀取位置queuePosition。然後,如果當前頭節點位置.queueHead加1等於隊列消息長度,頭節點位置.queueHead設置爲0,否則加1。對於⑶頭部寫入的情況,如果當前頭節點位置.queueHead加1等於隊列消息長度,頭節點位置.queueHead設置爲隊列消息長度減1,否則頭節點位置.queueHead減1即可。然後,獲取要寫入的位置queuePosition。對於⑷尾部寫入的情況,先獲取寫入位置queuePosition。然後,如果當前尾節點位置.queueTail加1等於隊列消息長度,尾節點位置.queueTail設置爲0,否則加1。

⑸處基於獲取的隊列讀取位置獲取隊列消息節點queueNode。我們看下⑹處如何讀消息,每個消息節點的後4個字節保存的是消息的長度,首先獲取消息的長度msgDataSize,然後執行⑺處代碼把消息內容讀取到bufferAddr。再看看⑻處如何寫入隊列消息,首先把消息內容寫入到queueNode,然後執行⑼再把消息長度的內容寫入到queueNode + queueCB->queueSize - sizeof(UINT32),就是每個消息節點的後4字節。

STATIC VOID OsQueueBufferOperate(LosQueueCB *queueCB, UINT32 operateType, VOID *bufferAddr, UINT32 *bufferSize)
{
    UINT8 *queueNode = NULL;
    UINT32 msgDataSize;
    UINT16 queuePosition;

    /* get the queue position */
⑴  switch (OS_QUEUE_OPERATE_GET(operateType)) {
        case OS_QUEUE_READ_HEAD:
            queuePosition = queueCB->queueHead;
⑵          ((queueCB->queueHead + 1) == queueCB->queueLen) ? (queueCB->queueHead = 0) : (queueCB->queueHead++);
            break;
        case OS_QUEUE_WRITE_HEAD:
⑶          (queueCB->queueHead == 0) ? (queueCB->queueHead = queueCB->queueLen - 1) : (--queueCB->queueHead);
            queuePosition = queueCB->queueHead;
            break;
        case OS_QUEUE_WRITE_TAIL:
⑷          queuePosition = queueCB->queueTail;
            ((queueCB->queueTail + 1) == queueCB->queueLen) ? (queueCB->queueTail = 0) : (queueCB->queueTail++);
            break;
        default:  /* read tail, reserved. */
            PRINT_ERR("invalid queue operate type!\n");
            return;
    }

⑸  queueNode = &(queueCB->queueHandle[(queuePosition * (queueCB->queueSize))]);

    if (OS_QUEUE_IS_READ(operateType)) {
⑹      if (memcpy_s(&msgDataSize, sizeof(UINT32), queueNode + queueCB->queueSize - sizeof(UINT32),
                     sizeof(UINT32)) != EOK) {
            PRINT_ERR("get msgdatasize failed\n");
            return;
        }
⑺      if (memcpy_s(bufferAddr, *bufferSize, queueNode, msgDataSize) != EOK) {
            PRINT_ERR("copy message to buffer failed\n");
            return;
        }

        *bufferSize = msgDataSize;
    } else {
⑻      if (memcpy_s(queueNode, queueCB->queueSize, bufferAddr, *bufferSize) != EOK) {
            PRINT_ERR("store message failed\n");
            return;
        }
⑼      if (memcpy_s(queueNode + queueCB->queueSize - sizeof(UINT32), sizeof(UINT32), bufferSize,
                     sizeof(UINT32)) != EOK) {
            PRINT_ERR("store message size failed\n");
            return;
        }
    }
}

3.4 隊列寫入

我們知道,有4個隊列寫入方法,2個隊尾寫入,2個隊首寫入,分別包含按引用地址寫入消息和按數值寫入消息。LOS_QueueWrite()會調用LOS_QueueWriteCopy(),LOS_QueueWriteHead()會調用LOS_QueueWriteHeadCopy(),然後指定不同的操作類型後,會進一步調用前文已經分析過的函數OsQueueOperate()。

小結

本文帶領大家一起剖析了LiteOS隊列模塊的源代碼,包含隊列的結構體、隊列池初始化、隊列創建刪除、讀寫消息等。感謝閱讀,如有任何問題、建議,都可以留言給我們: https://gitee.com/LiteOS/LiteOS/issues 。爲了更容易找到LiteOS代碼倉,建議訪問 https://gitee.com/LiteOS/LiteOS ,關注Watch、點贊Star、並Fork到自己賬戶下,謝謝。

 

點擊關注,第一時間瞭解華爲雲新鮮技術~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章