Understanding of uCOS message queue related functions

OSQCreate()

Create a message queue function . There are four entry parameters: message queue pointer; message queue name; message queue size (cannot be 0); return error type.

Function process : ① First, perform security check, illegal call check, parameter check, etc. Parameter checking generally checks whether the entry parameters are illegal, such as the creation queue is empty, the message queue size is 0, etc. If it exists, return; return and stop running. ②Then assign initial values ​​to the elements in the message queue structure in turn. Because before OSQCreate() creates a message queue, it only defines a message queue structure, and then the compiler allocates a memory for this structure , which requires a series of checks and assignments through OSQCreate(). First mark the created data structure type as message queue; mark the name of the message queue; initialize the message list; initialize the waiting list; if debugging codes and variables are enabled, add the queue to the message queue two-way debugging linked list. ③If the creation is successful, the number of message queues is increased by 1, the critical section is exited, and no error is returned.

Type identifier : Mark the created object data structure as a message queue, that is, assign a value to the type parameter of the message queue. It is performed through (OS_OBJ_TYPE)CPU_TYPE_CREATE('Q', 'U', 'E', 'U'). The CPU_TYPE_CREATE() type function has four parameters, all of which are 8-bit character variables. This function is shifted, for example, the first parameter is shifted by 0 bits, the second parameter is shifted by 8 bits... and so on, and then passed OS_OBJ_TYPE is forced to be converted into a 32-bit integer variable to achieve the purpose of a unique type identifier.

Initialize the message list structure : OS_MsgQInit(&p_q->MsgQ, max_qty). Because a message queue is newly defined, only one memory area is allocated to it. During initialization, only its name, size, and data structure identifier are given to it. Because there is no message in other memory areas, the contents should be empty or initialized to 0. Therefore, when initializing the message list structure, only the size of the message queue (the maximum number of messages allowed in the queue) will be assigned. The remaining pointers to messages will be NULL because there are no messages, the current number of messages and the number of messages in the queue. Peak values ​​are all assigned a value of 0.

Initialize the waiting (blocking) list structure : OS_PendListInit(&p_q->PendList). There are no waiting tasks in the waiting list at the beginning, so the pointer is assigned NULL and the variable is assigned 0. ( Because the waiting list is contained in the message queue structure, each message queue has its own waiting list ).

}

{

 OSQDel(): 

Message queue delete function . The queue deletion function deletes directly based on the queue structure (queue handle). After deletion, all information in the message queue will be cleared by the system, and the message queue cannot be used again. If you want to use the message queue delete function, you must configure the OS_CFG_Q_DEL_EN macro definition to 1.

Function implementation process :

There are three entry parameters for the OSQDel() function: message queue pointer; options; and return error type. The return value is the number of tasks in the waiting list.

① The function first performs security check, parameter check, etc. In the parameter check, not only the pointer to the operation object (message queue) is checked, but the option parameter is also checked, mainly to determine whether the option exceeds expectations. Then check whether the data type of the operation object is the message queue type. ② Then operate according to the option classification: treat the option OS_OPT_DEL_NO_PEND, which means that the queue needs to be deleted when there are no tasks waiting for the queue, so use if-else to judge. If there are no tasks, call the function OS_QClr() to clear the contents of the queue, and the number of message queues is reduced by 1 ; If there is a task, let the error type be "There is a task waiting for the queue" and exit. ③ Treat the option OS_OPT_DEL_ALWAYS, which means that it will be deleted regardless of whether there are tasks waiting for the message queue. Therefore, based on the number of tasks, use a loop to first delete the tasks in the waiting list (by calling OS_PendObjDel()), and then call the function OS_QClr() to clear the contents of the queue. Note: If debugging code and variables are enabled, the message queue needs to be removed from the debugging list before deleting the message queue.

Note: Generally, the message list is deleted when there are no tasks waiting for it, that is, the message queue is deleted under the OS_OPT_DEL_NO_PEND option .

- Call OS_PendObjDel() :

Resume tasks blocked on kernel objects (such as semaphores) from the blocked state. This function is just encapsulated for deleting a task from the waiting list. In order to delete all tasks in the waiting list, a while loop should be used

This function has three parameters : a pointer to the task control block; a timestamp; a pointer to the os_pend_obj target waiting structure. In this function, it refers to the type of the deleted object. Because the types of deleted objects include message queues, semaphores, mutexes, events, etc., the structure elements of each object are different, so pointer variables need to be cast. Pointer variables parse the memory pointed to according to the pointer type, so it can be judged that the layout of the elements of these object structures is similar, but there are more or less elements in the end, otherwise the memory cannot be parsed according to the type after pointer coercion .

Function implementation process : Since you want to delete tasks from the waiting list, you must classify operations according to the status of the task, and use multi-branch judgment statements switch and case. For states that have nothing to do with waiting, such as ready, delayed, and suspended states, they will exit without deletion; this is just a check to avoid users calling this function to delete tasks that are not in the waiting blocked state. The waiting task status is divided into OS_TASK_STATE_PEND, OS_TASK_STATE_PEND_TIMEOUT, OS_TASK_STATE_PEND_SUSPENDED, OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED.

The first two are task waiting indefinitely and task timeout waiting. If the task is waiting for multiple semaphores or message queues, the task is forced to wait for a certain object; the task's message field (pointer to the message) and message size are reset. ;Then call OS_PendListRemove() to remove the task from the waiting list. If it is in the waiting timeout state, remove the task from the time base list; then add it to the ready list; and finally modify the status and mark of the task.

The latter two states are the task being suspended while waiting and the task being suspended while waiting for timeout. In fact, it is similar to the above two steps, except that after being deleted from the waiting list, the status of the first two tasks changes to the ready state, and the status of the last two tasks changes to the pending state. So you can actually bring out the public parts and optimize the code.

}

{

OSQPost() : Message queue sending function .

Tasks or interrupt service routines can send messages to the message queue. When sending a message, if the queue is not full, it means that the running information is enqueued; uCOS will take out a message from the message pool and mount it at the end of the message queue (FIFO sending mode); if it is LIFO sending mode, the message will be hung Load it to the head of the message queue; then point the MsgPtr member variable in the message to the message to be sent (because the message pool does not specify the message point when initializing, the use of pointers is not only to point to messages of variable length, but also to , flexibly points to the message the user wants to send), if the system has a task blocked in the message queue, then when the message queue is sent, the task will be unblocked.

Function entry parameters : message queue pointer; message pointer; message size; options; return error type. (The reason why the message pointer is of type void* is because the message can be char*, int*, etc.)

Function implementation process : First, security check and parameter check are performed. Parameter check includes option check. The main check is whether the options are in compliance with expectations. Only if they are in compliance with expectations, proceed with the following operations.

The expected options are: OS_OPT_POST_FIFO /* (Default) Send in FIFO mode*/

OS_OPT_POST_LIFO /*Send messages in LIFO mode*/

OS_OPT_POST_1 /*Publish the message to the highest priority waiting task*/ (this option is not checked)

OS_OPT_POST_ALL /*Broadcast messages to all waiting tasks*/

OS_OPT_POST_NO_SCHED /*Send message but do not schedule tasks*/

Then perform a type check to check whether the object is of message queue type. Then send the message. What should be noted here is: the interrupt service routine sends messages. Because there is interrupt delayed release , the information originally released in the interrupt becomes task-level release. Therefore, if interrupt delayed release is enabled and the function that sends the message is called in the interrupt, it should not be sent immediately at this time. message, but places the sending of the message in the designated publishing task. At this time, the system publishes the message to the rental message queue, waits until the interrupt publishing task wakes up, and then sends the message (calling OS_IntQPost()). If it is not an interruption to delay publishing, just call the OS_QPost() function directly to send the message.

- Call OS_QPost() :

This function is called in OSQPost() and is mainly a function that is encapsulated to implement the message sending function . This function is an internal function of uC/OS-III and user's application should not call it. When sending a message, we just call OSQPost(). This function has six entry parameters: message queue pointer; message pointer; message size; options; timestamp when the message was published; return error type. Although the function is of type void, it does not return anything. But because there is a pointer variable in the entry parameter to save the returned error information, it can be considered as the exit parameter of the function.

Here you need to be familiar with the operation of the message queue : a task or interrupt service function sends a message to the message queue. If the message queue is not full, FIFO or LIFO will be added to the queue. If it is full, an error will be returned; but if there is a task waiting for the task or interrupt service function to send a message To the message queue, the message will not be enqueued ( because there is no need to enter the queue and immediately dequeue ) , and the message will be sent directly to the highest priority task in the waiting list. If OS_OPT_POST_ALL is selected, then the message must be broadcast Sent to all tasks in the waiting list. (important)

Function procedure : This function is indeed written according to how the message queue operates. First get the waiting list of the queue, and then use the if statement to make a judgment. ① If there is no task waiting for messages in the queue, insert the message sent by the task or interrupt service program into the message queue according to LIFO and FIFO, and call the function OS_MsgQPut(). ② If there are tasks waiting for messages in the queue, then according to the option and OS_OPT_POST_ALL, determine whether the message needs to be sent to all tasks in the waiting list. If necessary, get the number of tasks in the waiting list and send them one by one; if not, get the head task in the waiting list, because the waiting list is a task arranged according to priority, and send the message to him; send the message to the waiting list The function OS_Post() is called when the task is completed. ③ Finally, determine whether OS_OPT_POST_NO_SCHED is selected for the option. If "Do not schedule tasks after publishing" is not selected, call OSSched() for task switching. ( This is optional scheduling??? Doesn’t it mean that if the task status changes, this function must be called??? ) ( Explanation , when the task calls this function to send the message to the message queue, the task's own status does not change and is not suspended. Or delayed blocking, the task execution is not interrupted, so there is no need to call the switching function. Therefore, it is optional. It is equivalent to the task switching function switching to the task running, but the function of sending the message to the message queue is called in the task , the task is not interrupted, does not enter the blocking state, and there is no need to switch tasks to improve CPU utilization. The task should be allowed to finish executing or enter the blocking state before switching) (The reason why it is optional is that there may be a task This message is urgently needed (and the task just got the message in the waiting list to unblock it) in order to reduce the task waiting time, so the switch is made. In particular, when releasing the semaphore, this option is more important. Because if the semaphore is used as a synchronization or protection Resources are used and switched immediately, which can reduce synchronization delays ortime for priority inversion ).

--Call OS_MsgQPut() :

This function is encapsulated and used to enter messages into the message list of the message queue.

Six entry parameters : message list pointer; message pointer; message size; options; timestamp when the message is published; return error type.

Function process : ① In addition to the security check, it is also necessary to determine whether the message queue is full by checking the number of messages in the message list, and then determine whether there are available messages in the message pool. ② After checking, get a message from the message pool. (Getting a message from the message pool means taking a message. What is taken is the first message in the message pool. After saving the first message, make the pointer of the management message pool point to the second message; the number of available messages in the message pool Subtract 1; add 1 to the number of messages used in the message pool; update the historical record of the maximum number of messages used. If the maximum number of messages used is less than the number of messages used, then NbrUsedMax = NbrUsed) ③ Insert the obtained message into the message list of the message queue Medium (divided into three situations: if it is an empty queue, then the inserted node is both the head pointer and the tail pointer of the queue. If not, it is FIFO mode, that is, the node is inserted into the tail of the queue. According to "the node pointer changes According to the "save first" principle, first save the node pointer at the end of the queue in the message list, and then modify the pointer pointer. If it is LIFO mode, insert the node into the head of the queue, and modify the OutPtr and message pointer of the message list). ④ Update the maximum number of historical messages in the message list. ⑤ The message taken out is an initialized message. You need to assign a value to the message, add message content, message size, and timestamp when sending the message. Finally, no error message is returned.

Findings : The uCOS linked list has a structure that manages nodes connected together with pointers, and the management structure usually has variables for the number of linked list nodes, sometimes more than one, including the maximum number and the current node. Quantity etc. For example, in the message pool, the messages in the message pool are structures that are connected through pointers into a single linked list. Then there is a global structure that manages the message pool. This structure has two pointers, one pointing to the header message of the message pool. A pointer to the tail message in the message pool, and a variable indicating the number of messages .

Summary : The messages in the message pool are empty after initialization and are just strung into a linked list to facilitate message management and access; when the task calls the function OSQPost() to send a message to the message queue, the message is taken out from the message queue and inserted into the message pool. When entering the message list of the message queue, you need to assign a value to the message (message content, message size, sending timestamp). When a task sends a message, it is not stored in the message pool first and then assigned to the message list; instead, the message initialized to NULL (0) is taken directly from the message pool, inserted into the message list first, and then assigned to the message .

--Call OS_Post ( ) :

This function is encapsulated, and the task sends a message to the message queue. If there is a task waiting for a message in the message queue, it will not enter the queue and directly send the message to the task in the waiting list of the message queue.

Five entry parameters : kernel object type pointer (here, the message queue pointer is forced to be converted to OS_PEND_OBJ); task control block (referring to the tasks waiting in the message queue); pointer to the message; message size; timestamp.

Function process : For functions involving tasks, be sure to check the status of the task and classify operations according to the status of the task. ① Use switch--case multi-branch selection statement to judge tasks, and only operate on the four waiting states OS_TASK_STATE_PEND and OS_TASK_STATE_PEND_TIMEOUT, OS_TASK_STATE_PEND_SUSPENDED (suspended by waiting indefinitely) and OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED (suspended by waiting for timeout). ② The task is in an indefinite waiting state or a limited waiting state: If the task is waiting for multiple semaphores or message queues, call the OS_Post1() function to mark which kernel object is being released (released). If not, save the message pointer, message size and timestamp to the task control block (equivalent to the task getting the message !!! ) ; remove the task from the waiting list and call the function OS_PendListRemove(); if debugging code is enabled and Variable, call OS_PendDbgNameRemove() to remove the debugging name of the kernel object; note that if it is a timeout wait, because the delay time is set, the task will be sent to the time base list, and it must be removed here too; finally, insert the task into ready In the list, modify the task status bit to ready, clear the waiting flag, and mark it as no longer waiting. ③ For the state of indefinite/limited waiting and being suspended, the steps are not much different from ②. The message needs to be sent to the task. After the message is sent successfully, the task is removed from the waiting list. If there is a delay or the like, start from Removed from the time base list, only the status of the last modified task is pending .

Summary of the message queue sending function : If a task wants to send a message to the message queue, just call the function OSQPost(). However, in order to optimize the program structure, this function encapsulates many small functions to complete the function together. The calling relationship of OSQPost() is as shown below.

  }

{

OSQPend() : message queue acquisition function 

When a task attempts to obtain a message from the queue, the user can specify a blocking timeout. The task can obtain the message only when there is a message in the message queue. During this time, if the queue is empty, the task will remain blocked waiting for the queue message to be available. When other tasks or interrupt service routines write data into the queue they are waiting for, the task will automatically change from the blocking state to the ready state. When the waiting time of a task exceeds the user-specified blocking time, even if there is no valid message in the queue, the task will automatically change from the blocking state to the ready state. Of course, you can not set the waiting time and let it wait until the message is received. Just set the timeout parameter to 0.

Pay attention to the reading process : the task obtains the message from the message queue. If there is a message in the message list of the message queue, the task does not enter the waiting list of the message queue and obtains the message directly; if there is no message in the message list, the task needs to be inserted. Go to the waiting list and wait for messages .

Six function parameters : message queue pointer (which queue to read messages from); waiting time (unit is clock beat); options; return message size; get the timestamp when waiting for the message; return error type.

Function returns : pointer to the message.

Function process : ① Security check, illegal call check in interrupt, parameter check, and object type check. Note that illegal call checks in security checks and interrupts are disabled by default. The security check only provides function entry, and you need to write your own program to implement it. Parameter checks include whether the message queue is empty and whether the pointer used to save the returned message size is empty (not defined). The option check here does not require any special operation. The options OS_OPT_PEND_BLOCKING and OS_OPT_PEND_NON_BLOCKING mean whether to prevent the queue from being empty. ② Call OS_MsgQGet() to get the message from the message queue. If the acquisition is successful, it will return directly without inserting the task into the waiting list. ③ If there is no message in the message list, the task needs to be sent to the waiting list, and the task needs to enter waiting blocking. If the user option is OS_OPT_PEND_NON_BLOCKING at this time, the error "Waiting for eager blocking" will be returned and return. If you choose a blocking task, determine whether the scheduler is locked by other functions. The scheduler being locked means that other functions are not allowed to operate the task, so you need to return the error "scheduler is locked" and return . If not, the function calls OS_CRITICAL_ENTER_CPU_EXIT() to lock the scheduler, because it will take time to add the task to the time base and waiting list, and other tasks are not allowed to obtain CPU control during this period, interrupting the process. conduct. Then call the function OS_Pend() to insert the task into the waiting and time base list (to see if the wait times out). ⑤ Call OSSched() to perform task switching, because the task is blocked in waiting, which is equivalent to delayed blocking. In order to efficiently utilize the CPU, control should be given to other tasks during the period of waiting for messages. ⑥Almost thought that after switching, you should perform other tasks, and you should not write code later? ? ? mistake.Think about task blocking delay. After the task calls the delay function, in addition to setting the delay time of the task, the delay blocking function will also switch tasks. However, when the task time is up, it will re-enter the ready list and wait for its turn. level task, return to the scene to perform the task. Therefore, after switching, you must consider whether the task still needs to perform any operations, because the site will be protected. When switching to a task of this priority again, execution will continue from the location where the previous switch was disconnected. So what else needs to be done after switching here ? Think about it, when the OS switches to the task again and executes it from here, does it mean that the task has been removed from the time base list and waiting list? Only after being removed can it re-enter the ready list and be rescheduled; So does it mean that the task has received the message, or the waiting time has passed and the message has not been obtained, and it automatically changes to the ready state. Then should we judge what information is returned based on the task status? Moreover, because the function has a return value, you should at least write a return xx or the like after switching, so that no error will be reported . ⑦ If the control block status is normal: OS_STATUS_PEND_OK, the message in the message list in the message queue should be given to the task, including the returned message size and timestamp. The status is waiting to be terminated: OS_STATUS_PEND_ABORT, then the return message should be empty to obtain the timestamp and error type when the waiting is terminated. The status is waiting timeout: OS_STATUS_PEND_TIMEOUT. The return content is empty. Get the timestamp and error type of waiting timeout. The kernel object whose status is waiting is deleted: OS_STATUS_PEND_DEL, the return content is empty, and the timestamp and error type of the object being deleted are obtained. Other statuses are not as expected, the returned content is empty, and the returned status is an illegal error type.

- Call OS_MsgQGet() :

Call OS_MsgQGet() to get the message from the message queue. If the acquisition is successful, it will return directly without inserting the task into the waiting list.

4 entry parameters : pointer to message list; pointer to return message size; return timestamp pointer; return error type pointer.

Return value : Pointer to the message.

Function implementation process : There are two situations: one is that there is no message in the message list of the message queue, then an empty message is returned; one is that there is a message, then the message is returned and the pointer of the message queue is updated. What needs to be noted here is that the message is taken out of the message pool and inserted into the message list of the message queue. Then after the task reads the message from the message queue, the message should be returned to the message pool for reuse (insert the header of the message pool ) ( The reason why the message does not need to be cleared when returning to the message pool is because when the task calls the function OSQPost() to send the message to the message queue, and when the message is retrieved from the message pool again, it will be assigned to the message, regardless of whether the previous message was empty or empty. The content will be updated again ). ① First perform a security check. There is no need for parameter checking here because the function is called in OSQPend(), and these parameters have been checked in OSQPend(). ② If there is no message in the message list in the message queue, an empty message and size will be returned. The error type is no message in the queue and the timestamp will be cleared to 0. ③ If there is a message in the message queue, dequeue the message in the message list and modify the pointer to the head of the queue (dequeue pointer) of the message list management structure. Note that regardless of FIFO or LIFO mode, dequeued messages are deleted from the head of the queue. After the message is dequeued, pay attention to whether there are any messages in the queue. If there are no messages, you need to clear the pointer to the end of the queue (enqueue pointer) of the message list management structure and the number of messages; if there are still messages, clear the Just reduce the number of messages by 1. ④ Release the message back to the message pool. When a task fetches messages from the message pool, it starts from the first one pointed to by the management message pool structure. When releasing the message back to the message pool, it should also be inserted into the back of the management message pool structure. Removal and insertion start from scratch . So here we modify the pointers according to this idea, which is a singly linked list insertion operation, modifying two pointers, and pay attention to the order in which the pointers are modified. The number of available messages in the message pool is increased by 1, and the number of used messages is decreased by 1.

- Call OS_Pend() :

If there is no message in the message list, call the function OS_Pend() to remove the task from the ready list and insert it into the waiting list and (time base list (to see if it times out)).

4 entry parameters : pointer to the OS_PEND_DATA structure; pointer to the OS_PEND_OBJ kernel object (here the pointer to the message queue is forced to type conversion); task waiting status (the value assigned here is that the task is waiting in the message queue); waiting beat number timeout .

Function process : ① TCB contains two waiting state variables. Therefore, the assignment PendOn indicates which kernel object is being waited for; the status of PendStatus waiting includes whether it is normal, terminated, timed out, etc. ② Call OS_TaskBlock() to block the current task from running, which is to delete it from the ready list. If the parameter timeout is not 0, insert the task into the time base list; and modify the task status to waiting or timeout waiting. ③ The waiting list inserted into the message list is divided into two situations: one is that the waiting message queue exists. Then get the waiting list of the message queue. Note that the data structure in the waiting list is of type OS_PEND_DATA. This is a structure. Before inserting, you need to define one first, so there will be "pointing to the OS_PEND_DATA structure" in the entry parameter of the function. "Pointer to the body", the system needs this structure ( only vaguely aware of the need, currently do not know what exactly these elements are needed for???? ) , the elements in this structure mainly include the waiting task TCB, pointing to the task content and size pointer, a pointer to the waiting object, two pointers forming a doubly linked list, etc. Therefore, the task TCB cannot be simply inserted into the message list, and such a structure needs to be defined. Because OS_Pend() is an internal function, it is called here by OSQPend(). The local variable pend_data of the OS_PEND_DATA type has been defined in OSQPend(). This variable is used as the entry parameter of OS_Pend(). Call OS_PendDataInit() to initialize the structure. (Generally, for structures with a large number of elements, a function is written to initialize the structure), and then OS_PendListInsertPrio() is called to insert the OS_PEND_DATA structure into the waiting list according to priority. ④ The second case is that the message queue does not exist. It is necessary to change the pointer PendDataTblPtr in the TCB used to point to the waiting list interrupt element to NULL ((OS_PEND_DATA *)0), and clear the task's waiting field data PendDataTblEntries.

OS_Pend() should also be called in other functions, because before the message queue acquisition function OSQPend() calls this function, it has been checked whether the message queue is empty. Under the condition that it is not empty and there is no message in the message list, this function is called. The function puts the task into the waiting list.

     

Guess you like

Origin blog.csdn.net/m0_43443861/article/details/125960684