Abstract: This paper analyzes the hongmeng light kernel queue module source code, grasp the differences in queue use.

This article is shared from huawei cloud community hongmeng light kernel M core source code analysis series thirteen message Queue, author: Zhushy.

A Queue is a data structure commonly used for inter-task communication. Tasks can read messages from the queue. When the queue is empty, the reading task is suspended. When a new message is in the queue, the pending read task wakes up and processes the new message. Tasks can also write messages to a queue, suspending the write task when the queue is full. When there are idle message nodes in the queue, the pending write task is awakened and the message is written. If the timeout of the read queue and the write queue is set to 0, the task will not be suspended and the interface will return directly. This is non-blocking mode. Message queues provide an asynchronous processing mechanism that allows a message to be placed on a queue but not processed immediately. Queues also serve to buffer messages.

This article through the analysis of hongmeng light kernel queue module source code, grasp the differences in queue use. The source code covered in this article, such as the OpenHarmonyLiteOS-M kernel, is available at the open source site gitee.com/openharmony… To obtain.

Next, let’s look at the queue structure, queue initialization, and the source code for common queue operations.

1, queue structure definition and common macro definition

1.1 Definition of queue structure

In the file kernel\include\los_queue.h, define the queue control block structure as LosQueueCB, the structure source code is as follows. QueueState OS_QUEUE_UNUSED or OS_QUEUE_INUSED. For other structure members, see the comments.

typedef struct { UINT8 *queue; / UINT16 queueState; / UINT16 queueState; / UINT16 queueLen; / UINT16 queueLen; / UINT16 queueSize; / UINT16 queueSize; / UINT16 queueID; / UINT16 queueID; / UINT16 queueHead; / UINT16 queueHead; / UINT16 queueTail; / UINT16 queueTail; / UINT16 readWriteableCnt[OS_READWRITE_LEN]; / / UINT16 readWriteableCnt[OS_READWRITE_LEN]; */ LOS_DL_LIST readWriteList[OS_READWRITE_LEN]; /**< 2 - > block, 0: read, 1: write */ LOS_DL_LIST memList; /**< memory node bidirectlist */} LosQueueCB;Copy the code

1.2 Common queue macros

The number of queues supported by the system is defined by the macro LOSCFG_BASE_IPC_QUEUE_LIMIT, each queue queueID is of queueID type, and the value is [0,LOSCFG_BASE_IPC_QUEUE_LIMIT). Indicates the number of each queue in the queue pool.

(1) Obtain the queue control block corresponding to the queue number QueueID from the queue pool. ⑵ Obtain the memory address of the queue control block from readWriteList[OS_QUEUE_WRITE].

⑴ #define queue_handle () ((LosQueueCB *) g_queue) + (LosQueueCB *)) ⑵ #define queue_list (PTR) LOS_DL_LIST_ENTRY(ptr, LosQueueCB, readWriteList[OS_QUEUE_WRITE])Copy the code

In addition, queues provide important enumerations and macros related to queue read message operations. Enumerates QueueReadWrite to distinguish between reads and writes to a queue, enumerates QueueHeadTail to distinguish between the first and last of a queue, and enumerates QueuePointOrNot to distinguish between using a value or a pointer when reading or writing messages.

The operation type of a queue is a 3-bit number. See the OS_QUEUE_OPERATE_TYPE macro. The higher bit indicates the read/write value or the address of the read/write pointer, the middle bit indicates the start or end of the queue, and the lower bit indicates the read or write operation. Enumerations and macros are defined as follows:

typedef enum {
    OS_QUEUE_READ,
    OS_QUEUE_WRITE
} QueueReadWrite;


typedef enum {
    OS_QUEUE_HEAD,
    OS_QUEUE_TAIL
} QueueHeadTail;


typedef enum {
    OS_QUEUE_NOT_POINT,
    OS_QUEUE_POINT
} QueuePointOrNot;


#define OS_QUEUE_OPERATE_TYPE(ReadOrWrite, HeadOrTail, PointOrNot)  \
                (((UINT32)(PointOrNot) << 2) | ((UINT32)(HeadOrTail) << 1) | (ReadOrWrite))
#define OS_QUEUE_READ_WRITE_GET(type) ((type) & (0x01))
#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) & (0x03))
#define OS_QUEUE_IS_POINT(type)    ((type) & (0x04))
#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)
#define OS_READWRITE_LEN           2
Copy the code

2. Initialize the queue

Queues are enabled in the kernel by default and can be disabled by the LOSCFG_BASE_IPC_QUEUE macro. In the case of queue, OsQueueInit() is called in kernel\ SRC \los_init.c to initialize the queue module when the system starts. Next, let’s examine the queue initialization code.

⑴ Apply for memory for the queue. If the application fails, an error is returned. ⑵ Initialize the bidirectional circular list g_freeQueueList to maintain unused queues. The ⑶ loop initializes each queue, specifying the index queueID for each queue node, and inserts the queue node into g_freeQueueList, a two-way list with no queues. ReadWriteList [OS_QUEUE_WRITE] readWriteList[OS_QUEUE_WRITE] readWriteList[OS_QUEUE_WRITE]

LITE_OS_SEC_TEXT_INIT UINT32 OsQueueInit(VOID) { LosQueueCB *queueNode = NULL; UINT16 index; if (LOSCFG_BASE_IPC_QUEUE_LIMIT == 0) { return LOS_ERRNO_QUEUE_MAXNUM_ZERO; } ⑴ g_allQueue = (LosQueueCB *)LOS_MemAlloc(m_aucSysMem0, LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB)); if (g_allQueue == NULL) { return LOS_ERRNO_QUEUE_NO_MEMORY; } (VOID)memset_s(g_allQueue, LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB), 0, LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB)); 2 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]); } return LOS_OK; }Copy the code

3. Common operations on queues

3.1 Queue Creation

The queue creation function is LOS_QueueCreate(). Let’s take a look at the parameters: queueName is the name of the queue and is not actually used. Len is the number of messages in the queue, queueID is the queue number, and flags are reserved and unused. MaxMsgSize is the maximum size of each message in the queue.

Let’s examine the code that creates the queue. (1) Verify the parameters. The queue code cannot be empty, the length of the queue message cannot be too large, and the number and size of the queue message cannot be 0. (2) calculate the actual maximum sizeof the message (msgSize), that is, maxMsgSize+ sizeof(UINT32) the maximum sizeof the message plus 4 bytes. The last 4 bytes of the message are used to save the actual length of the message. The function LOS_MemAlloc() on ⑶ is then called to dynamically allocate memory to the queue and returns an error code if the memory allocation fails.

(4) Determine whether g_freeQueueList is empty. If no queue can be used, release the memory claimed in the previous paragraph. At ⑸ if g_freeQueueList is not empty, get the first available queue node. Then remove g_freeQueueList from the two-way list. Then call GET_QUEUE_LIST to get LosQueueCB*queueCB and initialize the created queue. ReadWriteableCnt [OS_QUEUE_READ] The value is 0, the value is 0, and the value is 0. Number of writable messages readWriteableCnt[OS_QUEUE_WRITE] specifies the length of the queue, len, queueHead and queueTail, and queueTail is 0.

⑹ Initialize the bidirectional linked list. ReadWriteList [OS_QUEUE_READ] allows any read tasks blocked on the list to be suspended. Initialize a bidirectional list. ReadWriteList [OS_QUEUE_WRITE]. Any write task that blocks on this queue will hang on the list. Initialize the bidirectional linked list. memList. ⑺ is assigned to the output parameter *queueID. Subsequent programs use this queue number to perform other operations on the queue.

LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueCreate(CHAR *queueName, UINT16 len, UINT32 *queueID, UINT32 flags, UINT16 maxMsgSize) { LosQueueCB *queueCB = NULL; UINT32 intSave; LOS_DL_LIST *unusedQueue = NULL; UINT8 *queue = NULL; UINT16 msgSize; (VOID)queueName; (VOID)flags; ⑴ if (queueID == NULL) {return LOS_ERRNO_QUEUE_CREAT_PTR_NULL; } if (maxMsgSize > (OS_NULL_SHORT - sizeof(UINT32))) { return LOS_ERRNO_QUEUE_SIZE_TOO_BIG; } if ((len == 0) || (maxMsgSize == 0)) { return LOS_ERRNO_QUEUE_PARA_ISZERO; } ⑵ ⑵ maxMsgSize = maxMsgSize + sizeof(UINT32); /* Memory allocation is time-consuming, to shorten the time of disable interrupt, */ ⑶ queue = (UINT8 *)LOS_MemAlloc(m_aucSysMem0, len * msgSize); if (queue == NULL) { return LOS_ERRNO_QUEUE_CREATE_NO_MEMORY; } intSave = LOS_IntLock(); ⑷ if (LOS_ListEmpty(&g_freeQueueList)) {LOS_IntRestore(intSave); (VOID)LOS_MemFree(m_aucSysMem0, queue); 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->queue = queue; queueCB->queueState = OS_QUEUE_INUSED; queueCB->readWriteableCnt[OS_QUEUE_READ] = 0; queueCB->readWriteableCnt[OS_QUEUE_WRITE] = len; queueCB->queueHead = 0; queueCB->queueTail = 0; [6] LOS_ListInit (& queueCB - > readWriteList [OS_QUEUE_READ]); LOS_ListInit(&queueCB->readWriteList[OS_QUEUE_WRITE]); LOS_ListInit(&queueCB->memList); LOS_IntRestore(intSave); Once * queueID = queueCB - > queueID; OsHookCall(LOS_HOOK_TYPE_QUEUE_CREATE, queueCB); return LOS_OK; }Copy the code

3.2 Deleting a Queue

We can use the UINT32queueID function LOS_QueueDelete(UINT32queueID) to delete the queue.

(1) Check whether the queue queueID exceeds LOSCFG_BASE_IPC_QUEUE_LIMIT. If so, an error code is returned. If the queue number is ok, obtain the queue control block LosQueueCB*queueCB. (2) If the queue to be deleted is not in use, jump to QUEUE_END for processing. ⑶ If the queue block read, block write task list is not empty, or memory node list is not empty, it is not allowed to delete, jump to the error label for processing. (4) check whether the number of readable and writable queues is wrong.

Set the. QueueState to OS_QUEUE_UNUSED and insert g_freeQueueList into the void of the UINT8*queue pointer at ⑸. Next, the function LOS_MemFree() will be called ⑺ to free the queue memory space.

LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueDelete(UINT32 queueID) { LosQueueCB *queueCB = NULL; UINT8 *queue = NULL; UINT32 intSave; UINT32 ret; ⑴ if (queueID >= LOSCFG_BASE_IPC_QUEUE_LIMIT) {return LOS_ERRNO_QUEUE_NOT_FOUND; } intSave = LOS_IntLock(); queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueID); ⑵ if (queueState == OS_QUEUE_UNUSED) {ret = LOS_ERRNO_QUEUE_NOT_CREATE; goto QUEUE_END; } (3) 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; (4) if ((queueCB->readWriteableCnt[OS_QUEUE_WRITE] + queueCB->readWriteableCnt[OS_QUEUE_READ])! = queueCB->queueLen) { ret = LOS_ERRNO_QUEUE_IN_TSKWRITE; goto QUEUE_END; } ⑸ ⑸ queue = queue ->queue; ⑹ UINT8 *)NULL; queueCB->queueState = OS_QUEUE_UNUSED; LOS_ListAdd(&g_freeQueueList, &queueCB->readWriteList[OS_QUEUE_WRITE]); LOS_IntRestore(intSave); OsHookCall(LOS_HOOK_TYPE_QUEUE_DELETE, queueCB); ⑺ ret = LOS_MemFree(m_aucSysMem0, (VOID *)queue); return ret; QUEUE_END: LOS_IntRestore(intSave); return ret; }Copy the code

Let’s take a look at queue reads and writes, there are two points to note:

  • Reading and writing at the head and back of the line

Only the first read of the queue is supported, not the last read, otherwise it is not a queue. In addition to the normal write messages at the end of the queue, a queue-jumping mechanism is provided to support write messages from the beginning of the queue.

  • Queue message data content

There are two types of messages that can be written to the queue, that is, by address and by value (with copy) are supported. As to which type is written, the corresponding type is read by the paired.

The categories of queue read interfaces are summarized as follows:

3.3 Queue Reading

We know that there are two queue-reading methods, LOS_QueueRead() for reading by pointer address and LOS_QueueReadCopy() for reading by message number. Let’s start with LOS_QueueRead(), which takes four arguments: queue number queueID, address of the buffer where the messages are read *bufferAddr, size of the buffer where the messages are read, TimeOut of waiting for read queue messages timeOut. The code is as follows. Let’s analyze the code.

(1) Check the passed parameters. The queue number cannot exceed the limit, the passed pointer cannot be empty, and the buffer size cannot be 0. If timeout is not zero, the queue cannot be read in an interrupt. The operation type at ⑵ indicates that the queue first reads the message pointer, and then calls the function OsQueueOperate() to further operate the queue.

LITE_OS_SEC_TEXT UINT32 LOS_QueueRead(UINT32 queueID, VOID *bufferAddr, UINT32 bufferSize, UINT32 timeOut) { UINT32 ret; UINT32 operateType; (1) ret = OsQueueReadParameterCheck (queueID, bufferAddr, & bufferSize, timeOut); if (ret ! = LOS_OK) { return ret; } os_queuE_operatetype = OS_QUEUE_OPERATE_TYPE(OS_QUEUE_READ, OS_QUEUE_HEAD, OS_QUEUE_POINT); OsHookCall(LOS_HOOK_TYPE_QUEUE_READ, (LosQueueCB *)GET_QUEUE_HANDLE(queueID)); return OsQueueOperate(queueID, operateType, bufferAddr, &bufferSize, timeOut); }Copy the code

Let’s take a closer look at the OsQueueOperate() function, which is a generic wrapper that calls both reads and writes. Let’s take the example of a read queue. (1) The operation type of obtaining the queue is read operation. The function OsQueueOperateParamCheck() is called to verify the parameter, verify that the queue is in use, and verify the size of read and write messages. (3) Returns an error code if the number of reads is 0, and if it cannot be read, if the wait is zero. If the current lock task is scheduled, function execution is skipped. Otherwise, the read message that executes the ⑷ to put the current task on the queue blocks the queue, and then triggers the task scheduling, and the subsequent code is temporarily not executed. If the number of reads is not 0, when reading can continue, execute the ⑹ code to subtract 1 from the number of reads, and then continue to execute the code read queue at place.

Wait until the read queue blocks and times out, or the queue can read, then continue with the code at ⑸. If a timeout occurs and the queue cannot read, change the task state and exit function execution. If the queue is ready to be read, continue executing the queue at ⑺. After the queue is successfully read, if any tasks are blocked in the write queue, the client retrives the first task in the blocked list, resumedTask, and then invokes the wake function OsSchedTaskWake() to put the task to be resumed into the ready queue, triggering a schedule. If there are no blocking tasks, the number of writable tasks is increased by one.

UINT32 OsQueueOperate(UINT32 queueID, UINT32 operateType, VOID *bufferAddr, UINT32 *bufferSize, UINT32 timeOut) { LosQueueCB *queueCB = NULL; LosTaskCB *resumedTask = NULL; UINT32 ret; ⑴ UINT32 readWrite = OS_QUEUE_READ_WRITE_GET(operateType); UINT32 readWriteTmp = ! readWrite; UINT32 intSave = LOS_IntLock(); queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueID); ⑵ ret = OsQueueOperateParamCheck(queueCB, operateType, bufferSize); if (ret ! = LOS_OK) { goto QUEUE_END; If (timeOut == LOS_NO_WAIT) {ret = OS_QUEUE_IS_READ(operateType)?  LOS_ERRNO_QUEUE_ISEMPTY : LOS_ERRNO_QUEUE_ISFULL; goto QUEUE_END; } if (g_losTaskLock) { ret = LOS_ERRNO_QUEUE_PEND_IN_LOCK; goto QUEUE_END; } LosTaskCB *runTsk = (LosTaskCB *)g_losTask.runTask; (4) OsSchedTaskWait (& queueCB - > readWriteList [readWrite], the timeOut); LOS_IntRestore(intSave); LOS_Schedule(); intSave = LOS_IntLock(); ⑸ if (runTsk->taskStatus & OS_TASK_STATUS_TIMEOUT) {runTsk->taskStatus &= ~OS_TASK_STATUS_TIMEOUT; ret = LOS_ERRNO_QUEUE_TIMEOUT; goto QUEUE_END; } else {⑹ queueCB->readWriteableCnt[readWrite]--; } count (); count (); Being the if (! LOS_ListEmpty(&queueCB->readWriteList[readWriteTmp])) { resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&queueCB->readWriteList[readWriteTmp])); OsSchedTaskWake(resumedTask); LOS_IntRestore(intSave); LOS_Schedule(); return LOS_OK; } else {longqueuecb ->readWriteableCnt[readWriteTmp]++; } QUEUE_END: LOS_IntRestore(intSave); return ret; }Copy the code

Let’s take a look at how the OsQueueBufferOperate() function reads the queue. ⑴ The switch-case statement in the ⑴ obtains the operation location based on the operation type. In the case of the ⑵ header read, the read position queuePosition is obtained first. Then, if the current head node position. QueueHead + 1 equals the length of the queue, the head node position. Set queueHead to 0, otherwise + 1. If queueHead = 0 and queueHead = 0 and queueHead = 0 and queueHead = 1 and queueHead = 1 and queueHead = 1 and queueHead = 1 and queueHead = 1 and queueHead = 1 and queueHead = 1 and queueHead = 1 and queueHead = 1 and queueHead = 1 and queueHead = 1 Then, get queuePosition to write to. In the case of the tail write of ⑷, first obtain the write position queuePosition. Then, if the current tail node location. QueueTail + 1 equals the queue message length, the tail node location. QueueTail is set to 0, otherwise + 1.

Obtain the queue message node queueNode at ⑸ based on the queue read position obtained. If the message is read and written according to the pointer, directly read the data of the message node and write it to the buffer *(UINT32*)bufferAddr corresponding to the pointer, or directly write the data of the buffer *(UINT32*)bufferAddr corresponding to the pointer to the message node. Let’s look at how to read and write the message according to the number of data, with the code used to read the data message. The last four bytes of each message node hold the length of the message. The message length msgDataSize is first obtained and the message content is read to bufferAddr. ⑻ The queue message is written first to the queueNode and then to the queueNode + queueCB-> queuesize-sizeof (UINT32), which is the last 4 bytes of each message node.

static INLINE VOID OsQueueBufferOperate(LosQueueCB *queueCB, UINT32 operateType, VOID *bufferAddr, UINT32 *bufferSize) { UINT8 *queueNode = NULL; UINT32 msgDataSize; UINT16 queuePosion; errno_t rc; /* switch (OS_QUEUE_OPERATE_GET(operateType)) {case OS_QUEUE_READ_HEAD: 2 queuePosion = queueCB - > queueHead; ((queueCB->queueHead + 1) == queueCB->queueLen) ? (queueCB->queueHead = 0) : (queueCB->queueHead++); break; Case OS_QUEUE_WRITE_HEAD: ⑶ (queueHead ->queueHead == 0)? (queueCB->queueHead = (queueCB->queueLen - 1)) : (--queueCB->queueHead); queuePosion = queueCB->queueHead; break; Case OS_QUEUE_WRITE_TAIL: ⑷ (4) ((queueCB->queueTail + 1) == queueCB->queueLen) ? (queueCB->queueTail = 0) : (queueCB->queueTail++); break; default: PRINT_ERR("invalid queue operate type! \n"); return; } ⑸ ⑸ check (⑸ queuePosion * (queueSize ->queueSize))); ⑹ if (OS_QUEUE_IS_POINT(operateType)) {if (OS_QUEUE_IS_READ(operateType)) {*(UINT32 *)bufferAddr = *(UINT32 *)(VOID) *)queueNode; } else { *(UINT32 *)(VOID *)queueNode = *(UINT32 *)bufferAddr; // Change to pp when calling OsQueueOperate}} else {if (OS_QUEUE_IS_READ(operateType)) {msgDataSize = *((UINT32) *)(UINTPTR)((queueNode + queueCB->queueSize) - sizeof(UINT32))); rc = memcpy_s((VOID *)bufferAddr, *bufferSize, (VOID *)queueNode, msgDataSize); if (rc ! = EOK) { PRINT_ERR("%s[%d] memcpy failed, error type = %u\n", __FUNCTION__, __LINE__, rc); return; } *bufferSize = msgDataSize; } else {count *((UINT32 *)(UINTPTR)((queueNode + queueSize ->queueSize) -sizeof (UINT32)) = *bufferSize; rc = memcpy_s((VOID *)queueNode, queueCB->queueSize, (VOID *)bufferAddr, *bufferSize); if (rc ! = EOK) { PRINT_ERR("%s[%d] memcpy failed, error type = %u\n", __FUNCTION__, __LINE__, rc); return; }}}}Copy the code

3.4 Queue Write

As we know, there are four queue write methods, two last-queue write methods and two first-queue write methods, which respectively contain write messages by pointer address and write messages by value. LOS_QueueWrite() will call LOS_QueueWriteCopy(), and LOS_QueueWriteHead() will call LOS_QueueWriteHeadCopy(). The function OsQueueOperate(), which was analyzed earlier, is further called.

summary

This article leads you to analyze the hongmeng light kernel of the queue module source code, including queue structure, queue pool initialization, queue creation and deletion, read and write messages. Thanks for reading. If you have any questions or suggestions, please leave a message at gitee.com/openharmony… . To make it easier to find the Hongmunlightweight kernel repository, you are advised to visit gitee.com/openharmony… , pay attention to Watch, like Star, and Fork into your account, thank you.

Click follow to learn about the fresh technologies of Huawei Cloud