Read the ucosII source code again (Shao Beibei): communication and synchronization between tasks - message queue

Introduction to Message Queuing

A message queue is another communication mechanism in µC/OS-II, which enables one task or interrupt service routine to send a variable defined by a pointer to another task.
Depending on the specific application, the data structure variables pointed to by each pointer are also different.
In order to use the message queue function of µC/OS-II, you need to set the OS_Q_EN constant to 1 in the OS_CFG.H file, and use the constant OS_MAX_QS to determine the maximum number of message queues supported by µC/OS-II.

Before a message queue can be used, the message queue must be established.
This can be done by calling the OSQCreate() function (see Section 6.07.01) and defining the number of units (messages) in the message queue.
µC/OS-II provides seven functions to operate on message queues: OSQCreate(), OSQPend(), OSQPost(), OSQPostFront(), OSQAccept(), OSQFlush() and OSQQuery() functions.

Figure F6.7 shows the relationship between tasks, interrupt service routines, and message queues.
Among them, the notation of message queue is much like multiple mailboxes.
In fact, we can think of a message queue as an array of multiple mailboxes,
but they share a list of waiting tasks.
The data structure pointed to by each pointer is application specific.
N represents the total number of units in the message queue.
When calling OSQPend() or OSQAccept(), calling OSQPost() or OSQPostFront() N times will fill up the message queue.
As can be seen from Figure F6.7, a task or interrupt service routine can call the OSQPost(), OSQPostFront(), OSQFlush() or OSQAccept() functions.
However, only tasks can call the OSQPend() and OSQQuery() functions.
write picture description here
Figure F6.7 The relationship between tasks, interrupt service routines, and message queues—Figure 6.7

Figure F6.8 shows the various data structures required to implement a message queue.
The event control block is also required to record the waiting task list [F6.8(1)], and the event control block can make the operations of multiple message queues and semaphore operations and mailbox operations the same code.
When a message queue is created, a queue control block (OS_Q structure, see OS_Q.C file) is also created and linked to the corresponding event control block through the .OSEventPtr field in OS_EVENT [F6.8(2)] . Before establishing a message queue, a pointer array containing the same number as the maximum number of messages in the message queue must be defined [F6.8(3)].
The starting address of the array and the number of elements in the array are passed as parameters to the OSQCreate() function.
In fact, if the memory occupies a contiguous address space, there is no need to use a pointer array structure.
The constant OS_MAX_QS in the file OS_CFG.H defines the maximum number of message queues that can be used in µC/OS-II. The minimum value should be 2. µC/OS-II establishes a linked list of free queue control blocks during initialization, as shown in Figure F6.9.
write picture description here
Figure F6.8 Data structure for message queue - Figure 6.8
write picture description here
Figure F6.9 Free queue control block linked list - Figure 6.9 The
queue control block is a data structure used to maintain message queue information, it contains the following fields . Here, a [.] is still added before each variable to indicate that they are a field in the data structure.

OS_Q structure:

typedef struct os_q {                   /* QUEUE CONTROL BLOCK                                         */
    struct os_q   *OSQPtr;              /* Link to next queue control block in list of free blocks     */
    void         **OSQStart;            /* Pointer to start of queue data                              */
    void         **OSQEnd;              /* Pointer to end   of queue data                              */
    void         **OSQIn;               /* Pointer to where next message will be inserted  in   the Q  */
    void         **OSQOut;              /* Pointer to where next message will be extracted from the Q  */
    INT16U         OSQSize;             /* Size of queue (maximum number of entries)                   */
    INT16U         OSQEntries;          /* Current number of entries in the queue                      */
} OS_Q;

.OSQPtr

Link all queue control blocks in the free queue control block. Once the message queue is established, this field is no longer useful.

.OSQStart

is a pointer to the starting address of an array of pointers to the message queue. User applications must define this array before using message queues.

.OSQEnd

is a pointer to the next address of the message queue end location. This pointer makes the message queue form a circular buffer.

.OSQIn

is a pointer to the position in the message queue where the next message is inserted. When .OSQIn and .OSQEnd are equal, .OSQIn is adjusted to point to the start of the message queue.

.OSQOut

is a pointer to the location in the message queue from which to fetch the next message. When .OSQOut and .OSQEnd are equal, .OSQOut is adjusted to point to the start of the message queue.

.OSQSize

is the total number of units in the message queue. This value is determined by the user application when the message queue is established. In µC/OS-II, this value can be up to 65,535.

.OSQEntries

is the current number of messages in the message queue. When the message queue is empty, the value is 0. When the message queue is full, this value is the same as the .OSQSize value. When the message queue is just created, the value is 0.

The most fundamental part of a message queue is a circular buffer, as shown in Figure F6.10.
Each of these cells contains a pointer.
When the queue is not full, .OSQIn [F6.10(1)] points to the next address unit to store the message.
If the queue is full (.OSQEntries and .OSQSize are equal), .OSQIn [F6.10(3)] points to the same unit as .OSQOut.
If a new pointer to a message is inserted into the unit pointed to by .OSQIn, a FIFO (First-In-First-Out) queue is formed.
Conversely, a LIFO queue (Last-In-First-Out) [F6.10(2)] is formed if a new pointer is inserted at the cell next to the cell pointed to by .OSQOut.
When .OSQEntries and .OSQSize are equal, the queue is full. The message pointer is always taken from the location pointed to by .OSQOut [F6.10(4)].
The pointers .OSQStart and .OSQEnd [F6.10(5)] define the head and tail of the message pointer array, so that when .OSQIn and .OSQOut reach the edge of the queue, bounds checking and necessary pointer adjustment are performed to realize the loop function.
write picture description here
Figure F6.10 The message queue is a circular buffer consisting of pointers - Figure 6.10

Create a message queue, OSQCreate()

Listing L6.21 is the source code for the OSQCreate() function.
The function requires an array of pointers to hold pointers to individual messages. The array of pointers must be declared to be of type void.
OSQCreate() first obtains an event control block from the idle event control block list (see Figure F6.3) [L6.21(1)], and adjusts the pointers of the remaining idle event control block lists accordingly, so that It points to the next idle event control block [L6.21(2)].
Next, the OSQCreate() function takes a queue control block from the list of free queue control blocks [L6.21(3)].

If a free queue control block is available, it is initialized [L6.21(4)].
The function then sets the type of the event control block to OS_EVENT_TYPE_Q [L6.21(5)] and makes its .OSEventPtr pointer point to the queue control block [L6.21(6)].
OSQCreate() also calls the OSEventWaitListInit() function to initialize the waiting task list of the event control block [see Section 6.01, Initializing an Event Control Block, OSEventWaitListInit()] [L6.21(7)].
Because the message queue is being initialized at this time, obviously its waiting task list is empty.
Finally, OSQCreate() returns a pointer to the event control block to its calling function [L6.21(9)]. This pointer will be used when calling message queue processing functions such as OSQPend(), OSQPost(), OSQPostFront(), OSQFlush(), OSQAccept() and OSQQuery().
Therefore, the pointer can be thought of as a handle to the corresponding message queue.
It is worth noting that if there is no free event control block at this time, the OSQCreate() function will return a NULL pointer.
If there is no queue control block available, in order not to waste event control block resources, the OSQCreate() function will return the just acquired event control block to the list of free event control blocks [L6.21(8)].
In addition, message queues cannot be deleted once they are created. Just imagine, if a task is waiting for a message in a message queue, and then delete the message queue, it will be very dangerous.

OS_EVENT *OSQCreate (void **start, INT16U size)
{
    OS_EVENT *pevent;
    OS_Q     *pq;


    OS_ENTER_CRITICAL();
    pevent = OSEventFreeList;                                           (1)
    if (OSEventFreeList != (OS_EVENT *)0) {
        OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;     (2)
    }
    OS_EXIT_CRITICAL();
    if (pevent != (OS_EVENT *)0) {
        OS_ENTER_CRITICAL();
        pq = OSQFreeList;                                               (3)
        if (OSQFreeList != (OS_Q *)0) {
            OSQFreeList = OSQFreeList->OSQPtr;
        }
        OS_EXIT_CRITICAL();
        if (pq != (OS_Q *)0) {
            pq->OSQStart        = start;                                 (4)
            pq->OSQEnd          = &start[size];
            pq->OSQIn           = start;
            pq->OSQOut          = start;
            pq->OSQSize         = size;
            pq->OSQEntries      = 0;
            pevent->OSEventType = OS_EVENT_TYPE_Q;                     (5)
            pevent->OSEventPtr  = pq;                                   (6)
            OSEventWaitListInit(pevent);                                 (7)
        } else {
            OS_ENTER_CRITICAL();
            pevent->OSEventPtr = (void *)OSEventFreeList;               (8)
            OSEventFreeList    = pevent;
            OS_EXIT_CRITICAL();
            pevent = (OS_EVENT *)0;
        }
    }
    return (pevent);                                                     (9)
}

Waiting for a message in a message queue, OSQPend()

Listing L6.22 is the source code for the OSQPend() function.
The OSQPend() function first checks whether the event control block was created by the OSQCreate() function [L6.22(1)],
then the function checks whether there are messages available in the message queue (ie, whether .OSQEntries is greater than 0) [L6. 22(2)].
If there is, the OSQPend() function copies the pointer to the message into the msg variable, and lets the .OSQOut pointer point to the next unit in the queue [L6.22(3)], then decrements the number of valid messages in the queue by 1 [ L6.22(4)].
Because the message queue is a circular buffer, the OSQPend() function needs to check whether .OSQOut exceeds the last element in the queue [L6.22(5)]. When this out of bounds occurs, .OSQOut is readjusted to point to the start of the queue [L6.22(6)]. This is what we expect when calling the OSQPend() function, and is the fastest path to execute the OSQPend() function.
Listing L6.22 Waiting for a message in a message queue

void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
    void  *msg;
    OS_Q  *pq;


    OS_ENTER_CRITICAL();
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {                    (1)
        OS_EXIT_CRITICAL();
        *err = OS_ERR_EVENT_TYPE;
        return ((void *)0);
    }
    pq = pevent->OSEventPtr;
    if (pq->OSQEntries != 0) {                                      (2)
        msg = *pq->OSQOut++;                                          (3)
        pq->OSQEntries--;                                            (4)
        if (pq->OSQOut == pq->OSQEnd) {                            (5)
            pq->OSQOut = pq->OSQStart;                              (6)
        }
        OS_EXIT_CRITICAL();
        *err = OS_NO_ERR;
    } else if (OSIntNesting > 0) {                                  (7)
        OS_EXIT_CRITICAL();
        *err = OS_ERR_PEND_ISR;
    } else {
        OSTCBCur->OSTCBStat    |= OS_STAT_Q;                          (8)
        OSTCBCur->OSTCBDly      = timeout;
        OSEventTaskWait(pevent);
        OS_EXIT_CRITICAL();
        OSSched();                                                  (9)
        OS_ENTER_CRITICAL();
        if ((msg = OSTCBCur->OSTCBMsg) != (void *)0) {             (10)
            OSTCBCur->OSTCBMsg      = (void *)0;
            OSTCBCur->OSTCBStat     = OS_STAT_RDY;
            OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;                 (11)
            OS_EXIT_CRITICAL();
            *err                    = OS_NO_ERR;
        } else if (OSTCBCur->OSTCBStat & OS_STAT_Q) {               (12)
            OSEventTO(pevent);                                     (13)
            OS_EXIT_CRITICAL();
            msg                     = (void *)0;                     (14)
            *err                    = OS_TIMEOUT;
        } else {
            msg = *pq->OSQOut++;                                     (15)
            pq->OSQEntries--;
            if (pq->OSQOut == pq->OSQEnd) {
                pq->OSQOut = pq->OSQStart;
            }
            OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;                 (16)
            OS_EXIT_CRITICAL();
            *err = OS_NO_ERR;
        }
    }
    return (msg);                                                   (17)
}

If there is no message in the message queue at this time (.OSEventEntries is 0), the OSQPend() function checks whether its caller is an ISR [L6.22(7)]. Like the OSSemPend() and OSMboxPend() functions, OSQPend() cannot be called from an ISR because ISR cannot wait. However, if there is a message in the message queue, even calling the OSQPend() function from the interrupt service routine is successful.
If there are no messages in the message queue, the task calling the OSQPend() function is suspended [L6.22(8)].
OSSched() returns [L6.22(9)] when other tasks send messages to the message queue or the waiting time expires and the task becomes the highest priority task.

At this point, OSQPend() checks to see if a message has been placed into the task's task control block [L6.22(10)].
If there is, then the function call is successful, delete the pointer to the message queue in the task control block of the task [L6.22(17)], and return the corresponding message to the calling function [L6.22(17)] .
In the OSQPend() function, you can know whether to wait for the timeout by checking the .OSTCBStat field in the task control block of the task.
If its corresponding OS_STAT_Q bit is set, the task wait has timed out [L6.22(12)]. At this time, the task can be deleted from the waiting task list of the message queue by calling the function OSEventTo() [L6.22(13)]. At this time, because there is no message in the message queue, the returned pointer is NULL [L6.22(14)].
If the OS_STAT_Q bit in the task control block flags is not set to 1, a task has sent a message. The OSQPend() function dequeues the message [L6.22(15)]. Then, delete the pointer to the event control block in the task control of the task [L6.22(16)].

Send a message to the message queue (FIFO), OSQPost()

Listing L6.23 is the source code for the OSQPost() function.
After confirming that the event control block is a message queue [L6.23(1)], the
OSQPost() function checks whether any tasks are waiting for a message in the message queue [L6.23(2)].
When the .OSEventGrp field of the event control block is non-zero, it means that there are tasks in the waiting task list of the message queue. At this point, the OSEventTaskRdy() function [see Section 6.02 to bring a task to the ready state, OSEventTaskRdy()] takes the highest priority task from the list [L6.23(3)] and puts it in the ready state. Then call the function OSSched() [L6.23(4)] to schedule tasks. If the priority of the task fetched above is also the highest among the tasks that are ready in the entire system, and the OSQPost() function is not called by the interrupt service subroutine, the task switching is performed, and the highest priority task is executed. Otherwise, the OSSched() function returns directly, and the task that called the OSQPost() function continues to execute.
Listing L6.23 Sends a message to the message queue

INT8U OSQPost (OS_EVENT *pevent, void *msg)
{
    OS_Q   *pq;


    OS_ENTER_CRITICAL();
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {                   (1)
        OS_EXIT_CRITICAL();
        return (OS_ERR_EVENT_TYPE);
    }
    if (pevent->OSEventGrp) {                                       (2)
        OSEventTaskRdy(pevent, msg, OS_STAT_Q);                   (3)
        OS_EXIT_CRITICAL();
        OSSched();                                                           (4)
        return (OS_NO_ERR);
    } else {
        pq = pevent->OSEventPtr;
        if (pq->OSQEntries >= pq->OSQSize) {                         (5)
            OS_EXIT_CRITICAL();
            return (OS_Q_FULL);
        } else {
            *pq->OSQIn++ = msg;                                   (6)
            pq->OSQEntries++;
            if (pq->OSQIn == pq->OSQEnd) {
                pq->OSQIn = pq->OSQStart;
            }
            OS_EXIT_CRITICAL();
        }
        return (OS_NO_ERR);
    }
}

If no tasks are waiting for a message in the message queue and the message queue is not full [L6.23(5)], a pointer to the message is inserted into the message queue [L6.23(6)]. In this way, the next task that calls the OSQPend() function can get the message immediately. Note that if the message queue is full at this point, the message will be lost because it cannot be inserted into the message queue.
In addition, if the OSQPost() function is called by an interrupt service routine, even if a higher priority task is generated, no task switching will occur when the OSSched() function is called. This action cannot be performed until the outermost interrupt service routine of the interrupt nest calls the OSIntExit() function (see Section 3.09, Interrupts in µC/OS-II).

Send a message to the message queue (last in first out LIFO), OSQPostFront()

The OSQPostFront() function is basically the same as OSQPost(), except that when inserting a new message into the message queue, .OSQOut is used as the pointer to the next unit to insert the message, instead of .OSQIn.
Program listing L6.24 is its source code.
It is worth noting that the .OSQOut pointer points to the unit where the message pointer has been inserted, so before inserting a new message pointer, the .OSQOut pointer must be moved forward by one unit in the message queue. If the current unit pointed to by the .OSQOut pointer is the first unit in the queue [L6.24(1)], then moving forward will cause out of bounds, and it is necessary to point the pointer to the end of the queue [L6.24( 2)].
Since .OSQEnd points to the unit next to the last unit in the message queue, .OSQOut must be adjusted to point to the valid range of the queue [L6.24(3)]. Because the message fetched by the QSQPend() function was just inserted by the OSQPend() function, the OSQPostFront() function implements a LIFO queue.
Listing L6.24 sends a message to the message queue (LIFO):

INT8U OSQPostFront (OS_EVENT *pevent, void *msg)
{
    OS_Q   *pq;


    OS_ENTER_CRITICAL();
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {
        OS_EXIT_CRITICAL();
        return (OS_ERR_EVENT_TYPE);
    }
    if (pevent->OSEventGrp) {
        OSEventTaskRdy(pevent, msg, OS_STAT_Q);
        OS_EXIT_CRITICAL();
        OSSched();
        return (OS_NO_ERR);
    } else {
        pq = pevent->OSEventPtr;
        if (pq->OSQEntries >= pq->OSQSize) {
            OS_EXIT_CRITICAL();
            return (OS_Q_FULL);
        } else {
            if (pq->OSQOut == pq->OSQStart) {                  (1)
                pq->OSQOut = pq->OSQEnd;                        (2)
            }
            pq->OSQOut--;                                      (3)
            *pq->OSQOut = msg;
            pq->OSQEntries++;
            OS_EXIT_CRITICAL();
        }
        return (OS_NO_ERR);
    }
}

Get a message from a message queue without waiting, OSQAccept()

If you try to get a message from the message queue and the message queue is empty at this time, you can directly return to the calling function without letting the calling task wait. This operation can be done by calling the OSQAccept() function. Listing L6.25 is the source code for this function. The OSQAccept() function first checks to see if the event control block pointed to by pevent was created by the OSQCreate() function [L6.25(1)], then it checks to see if there is a message in the current message queue [L6.25(2)]. If there is at least one message in the message queue, then fetch the message from the unit pointed to by .OSQOut [L6.25(3)]. The calling function of the OSQAccept() function needs to check the pointer returned by OSQAccept(). If the pointer is NULL, the message queue is empty and no messages can be found in it [L6.25(4)]. Otherwise, a message has been successfully obtained from the message queue. When the interrupt service routine wants to fetch messages from the message queue, it must use the OSQAccept() function instead of the OSQPend() function.
Listing L6.25 Fetch a message from the message queue without waiting

void *OSQAccept (OS_EVENT *pevent)
{
    void  *msg;
    OS_Q  *pq;


    OS_ENTER_CRITICAL();
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {            (1)
        OS_EXIT_CRITICAL();
        return ((void *)0);
    }
    pq = pevent->OSEventPtr;
    if (pq->OSQEntries != 0) {                              (2)
        msg = *pq->OSQOut++;                                  (3)
        pq->OSQEntries--;
        if (pq->OSQOut == pq->OSQEnd) {
            pq->OSQOut = pq->OSQStart;
        }
    } else {
        msg = (void *)0;                                      (4)
    }
    OS_EXIT_CRITICAL();
    return (msg);
}

Empty a message queue, OSQFlush()

The OSQFlush() function allows the user to delete all messages in a message queue and start over. Listing L6.26 is the source code for this function. Like the other previous functions, this function first checks whether the pevent pointer is executing a message queue [L6.26(1)], and then resets the queue's insert and remove pointers so that they both point to the queue's start unit, and at the same time, Set the number of messages in the queue to 0 [L6.26(2)]. Here, there is no check to see if the queue's waiting list is empty, because .OSQEntries must be 0 as long as the waiting list is not empty. The only difference is that the pointers .OSQIn and .OSQOut can point to any unit in the message queue at this time, not necessarily the starting unit.
Listing L6.26 Clears the message queue

INT8U OSQFlush (OS_EVENT *pevent)
{
    OS_Q  *pq;


    OS_ENTER_CRITICAL();
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {              (1)
        OS_EXIT_CRITICAL();
        return (OS_ERR_EVENT_TYPE);
    }
    pq             = pevent->OSEventPtr;
    pq->OSQIn      = pq->OSQStart;                            (2)
    pq->OSQOut     = pq->OSQStart;
    pq->OSQEntries = 0;
    OS_EXIT_CRITICAL();
    return (OS_NO_ERR);
}

Query the status of a message queue, OSQQuery()

The OSQQuery() function allows the user to query the current state of a message queue. Listing L6.27 is the source code for this function. OSQQuery() takes two parameters: one is the pointer pevent to the message queue. It is returned by the OSQCreate() function when a message queue is established; the other is the pointer pdata that points to the OS_Q_DATA (see uCOS_II.H) data structure. This structure contains information about the message queue. This data structure variable must be defined before calling the OSQQuery() function. The OS_Q_DATA structure contains the following fields:
.OSMsg If there is a message in the message queue, it contains the contents of the queue unit pointed to by the pointer .OSQOut. If the queue is empty, .OSMsg contains a NULL pointer.
.OSNMsgs is the number of messages in the message queue (a copy of .OSQEntries).
.OSQSize is the total capacity of the message queue.
OSEventTbl[] and .OSEventGrp are the waiting task list of the message queue. Through them, the calling function of OSQQuery() can get the total number of tasks waiting for messages in the message queue.
The OSQQuery() function first checks that the event control block pointed to by the pevent pointer is a message queue [L6.27(1)], and then copies the waiting task list [L6.27(2)]. If there is a message in the message queue [L6.27(3)], the contents of the queue unit pointed to by .OSQOut are copied to the OS_Q_DATA structure [L6.27(4)], otherwise, a NULL pointer is copied [L6. 27(5)]. Finally, replicate the number of messages in the message queue and the size of the message queue [L6.27(6)].
Program Listing L6.27 Status of Program Message Queue

INT8U OSQQuery (OS_EVENT *pevent, OS_Q_DATA *pdata)
{
    OS_Q   *pq;
    INT8U   i;
    INT8U  *psrc;
    INT8U  *pdest;


    OS_ENTER_CRITICAL();
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {               (1)
        OS_EXIT_CRITICAL();
        return (OS_ERR_EVENT_TYPE);
    }
    pdata->OSEventGrp = pevent->OSEventGrp;                   (2)
    psrc              = &pevent->OSEventTbl[0];
    pdest             = &pdata->OSEventTbl[0];
    for (i = 0; i < OS_EVENT_TBL_SIZE; i++) {
        *pdest++ = *psrc++;
    }
    pq = (OS_Q *)pevent->OSEventPtr;
    if (pq->OSQEntries > 0) {                                   (3)
        pdata->OSMsg = pq->OSQOut;                             (4)
    } else {
        pdata->OSMsg = (void *)0;                               (5)
    }
    pdata->OSNMsgs = pq->OSQEntries;                             (6)
    pdata->OSQSize = pq->OSQSize;
    OS_EXIT_CRITICAL();
    return (OS_NO_ERR);
}

The message queue uses:

Reading the value of an analog quantity using a message queue

In the control system, the value of the analog quantity is often read frequently. At this time, you can first create a timed task OSTimeDly() [see Section 5.00, Delay a task, OSTimeDly()], and give the desired sampling period. Then, as shown in Figure F6.11, let the A/D sampling task wait for messages from a message queue. The longest waiting time for this program is the sampling period. When no other task sends messages to the message queue, the A/D sampling task exits the waiting state and executes because of the waiting timeout. This mimics the functionality of the OSTimeDly() function.
Perhaps, readers will ask, since the OSTimeDly() function can do this work, why use a message queue? This is because, with the help of the message queue, we can let other tasks send messages to the message queue to terminate the A/D sampling task waiting for the message, so that it immediately performs an A/D sampling. In addition, we can also inform the A/D sampling program which channel to sample through the message queue, tell it to increase the sampling frequency, etc., thus making our application more intelligent. In other words, we can tell the A/D sampling program, "Now read the input value of channel 3!" After that, the sampling task will restart waiting for messages in the message queue, ready to start a new scanning process.
write picture description here

Use a message queue as a counting semaphore

When the message queue is initialized, multiple pointers in the message queue can be set to non-NULL values ​​(such as void* 1) to implement the function of counting semaphores. Here, the number of pointers initialized to a non-NULL value is the number of resources available. Tasks in the system can request a "semaphore" through OSQPend(), and then release the "semaphore" by calling OSQPost(), as shown in Listing L6.28. If only counting semaphores and message queues are used in the system, using this method can effectively save code space. At this time, set OS_SEM_EN to 0, you can not use the semaphore, but only use the message queue.
It is worth noting that this approach introduces a large number of pointer variables for shared resources. That is, RAM space is sacrificed to save code space. In addition, operations on message queues are slower than operations on semaphores, so this method is very inefficient when there are many semaphores synchronized with counting semaphores.
Listing L6.28 uses the message queue as a counting semaphore

OS_EVENT *QSem;
void     *QMsgTbl[N_RESOURCES]


void main (void)
{
    OSInit();
    .
    .
    QSem = OSQCreate(&QMsgTbl[0], N_RESOURCES);
    for (i = 0; i < N_RESOURCES; i++) {
        OSQPost(Qsem, (void *)1);
    }
    .
    .
    OSTaskCreate(Task1, .., .., ..);
    .
    .
    OSStart();
}


void Task1 (void *pdata)
{
    INT8U err;


    for (;;) {
        OSQPend(&QSem, 0, &err);         /* 得到对资源的访问权  */
        .
        .    /* 任务获得信号量,对资源进行访问  */
        .
        OSMQPost(QSem, (void*)1);       /* 释放对资源的访问权 */
    }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325433334&siteId=291194637