Real-time operating system Freertos pit study notes: (4): Critical section protection, lists and list items

Preface

Without further ado, let’s look directly at the main issues to be explored:
insert image description here
insert image description here

1. Critical section code protection

1. What is a critical section?

Example: pandas is a tool based on NumPy that was created to solve data analysis tasks.
The figure says that the code in the critical section cannot be interrupted. It cannot be interrupted by interrupts while it is running, nor can it switch to other tasks due to delays in non-blocking tasks.
For example, when the IIC communicates, the software simulates the IIC and there will be a delay of 4-8us, which shows that the communication timing is very important, and once it is interrupted, it will have an impact.
If I write a project, task one needs to use IIC communication to measure the acceleration value of MPU6050, and task two needs to report data every 0.5 seconds. Then my task one will be interrupted by the scheduling of task two, and I can still return to the interruption point afterwards. , but for IIC communication with extremely high timing requirements, errors will definitely occur.
So how can we prevent the critical section program from being interrupted?

2. How to prevent critical section code from being interrupted by interrupts

insert image description here
The protection of critical section code is essentially to turn off and on interrupts.
In the third section of interrupt management, we talked about the function of switching interrupts:
insert image description here
this is different from the switching interrupt function of critical section protection, but the essence is the same.

3.Look at an example

In fact, the example is very simple, and as mentioned before, when writing a start task, to create a task, you must first enter the critical section and then exit the critical section.

The taskENTER_CRITICAL() macro is used to enter the critical section. It will set the task priority to the highest and disable all interrupts to ensure that the code in the critical section will not be interrupted by other tasks or interrupts.

The taskEXIT_CRITICAL() macro is used to exit the critical section. It will restore the task priority to its original value and allow interrupts so that other tasks or interrupts can continue to execute.

Why do this? I wrote in the second section. For example, if you don’t do this, you create a task1 and a task2. When task1 has just been created and task2 has not yet been created, task1 will be executed first because of its high priority. In case task1 is not Sharing the time slice, wouldn't it mean that task2 will not be run at all? In order to prevent this kind of task from preempting the current task in advance, the critical section code protection function must be used, so that after tasks 1 and 2 are initialized, they will run honestly according to the priority.

2. Suspend and resume the task scheduler

1 Introduction

After creating a task, you always need to open the task scheduler to switch tasks:

vTaskStartScheduler();

Then, the task scheduler will also have suspend and resume states.

2. API functions for task scheduler suspension and recovery

insert image description here

First, you need to know the difference between interrupts and task (scheduling) switching:

① Triggering method: Interrupts are triggered by external events, such as hardware device input, timer overflow, etc.; task switching is triggered by the scheduler within the operating system, which determines which task to switch to based on a certain scheduling strategy.
②Execution environment: The interrupt is executed in the interrupt context. It will suspend the currently running task, save the interrupt scene, execute the interrupt service routine, and finally restore the scene and return to the original task. Task switching is performed in the task context. It saves the context of the current task and switches to the context of another task to continue execution.
③Scheduling overhead: The scheduling overhead of interrupts is smaller than that of task switching, because the interrupt service routine is usually very short and only needs to save and restore the scene; while task switching requires saving and restoring multiple registers and stacks, so the overhead is relatively large.
④Scheduling priority: The priority of interrupts is usually higher than the priority of tasks, because interrupts need to respond to external events as soon as possible; while the priority of tasks is determined by the scheduler within the operating system and is allocated according to the importance and urgency of the task. priority.

In layman's terms, I created two tasks, both of which flash the LED every 0.5 seconds. At the same time, I initialized an external interrupt + button. Pressing it will cause task1 to hang. Then, the scheduling between two tasks belongs to task scheduling, while external interrupts, timer overflow interrupts, serial port reception interrupts, etc. are all interrupts and have higher priority.

Suspend the task scheduler, calling this function does not require turning off interrupts

What this sentence means is: it only prevents resource competition between tasks, and interrupts can still be responded to directly. The method of suspending the scheduler is suitable when the critical section is located between tasks; it does not need to delay interrupts, but can also ensure the safety of the critical section.
How does it protect critical section programs? It's very simple. Ban the task scheduler so that tasks that may occupy program resources will not preempt them at all. But there is also a disadvantage, that is, external interrupts may still interrupt the critical section program.
It depends on the specific form and location of your critical section program to decide whether to use switch interrupt or ban task scheduler.

;Summary diagram of critical section code protection and task scheduler suspension:
insert image description here

3. Lists and list items

Lists have been talked about in various places before, such as ready task lists, task pending lists, etc. So what is the arrival list? This concept is very important. If you only focus on external implementation, then understand it briefly, but if you want to take a deeper look at how the freertos kernel source code is implemented, the list is the top priority! It is very helpful to understand the operating mechanism of freertos.

1. Definition of lists and list items

insert image description here

(1) List: The list is a data structure in FreeRTOS. It is conceptually similar to a linked list. The list is used to track tasks in FreeRTOS . In terms of data structure, a linked list is a common linear data structure used to store a series of data elements. Each element in the linked list is represented by a node (Node), and each node contains a data element and a pointer to the next node. Through pointers, various nodes can be connected to form a chain structure.

(2) List items: List items are items stored in the list.

(3) Schematic diagram:
insert image description here
The list items are the nodes in the list. It is a circular list, a two-way circular linked list.
For example:
insert image description here
three people form a circular linked list structure. We know that the linked list uses pointers to point to nodes. Here, for example, Xiao Ming is a node, his body is the data element, and his right hand is a small node pointing to the next node. Black's pointer, his left hand is the pointer pointing to the previous node Xiaohong.
Suppose I suddenly want to insert a little girl between Xiao Ming and Xiao Hong, then just let Xiao Mei's left and right hands connect with Xiao Ming and Xiao Hong, and if I want to delete a node, the same goes for it.

So why is this list structure suitable for freertos?

(1) Characteristics of lists: The addresses between list items are discontinuous and are artificially connected together. The number of list items is determined by the number added later and can be changed at any time. I can add and delete nodes at will. Each node represents a task and contains the status, priority, stack and other information of the task. By traversing the linked list, you can quickly access all tasks and select the next task to be executed based on the scheduling algorithm.
(2) Dynamically allocate memory: The linked list can dynamically allocate memory as needed, and can flexibly add or delete tasks without knowing the size of the task list in advance.
(3) Efficient insertion and deletion: The insertion and deletion operations of the linked list only need to modify the pointer, and the time complexity is O(1).
(4) High space utilization: The linked list only needs to store the pointer and status information of the task, and does not need to reserve space like an array, so the space utilization is higher.

2. List related structures

insert image description here
list.c is the configuration of the list in the freertos source code.

①List structure

insert image description here
Here is the explanation:
(1) In this structure, contains two macros,

listFIRST_LIST_INTEGRITY_CHECK_VALUE
listSECOND_LIST_INTEGRITY_CHECK_VALUE

These two macros are known constants. FreeRTOS checks the values ​​of these two constants
to determine whether the data in the list has been damaged during the running of the program. This function is generally used for debugging and is not enabled by default. In fact, it’s not important, you don’t have to watch it.

(2)

volatile UBaseType_t uxNumberOfItems;

Member uxNumberOfItems, used to record the number of list items in the list (excluding xListEnd)

(3) The member pxIndex is used to point to a certain list item in the list, and is generally used to traverse all list items in the list

ListItem_t * configLIST_VOLATILE pxIndex;

(4) The member variable xListEnd is a mini list item, ranked at the end

MiniListItem_t xListEnd;

In addition, look at this structure diagram:
insert image description here
the list item is the place in the list used to store data. In the list.h file, there are related structure definitions for the list item. Each list item represents a task.

②List item structure

The structure members of the list item are as follows:
insert image description here
1. The member variable xItemValue is the value of the list item. This value is mostly used to sort the list items in the list in ascending order.
2. The member variables pxNext and pxPrevious are used to point to the next list item in the list respectively. A list item and the previous list item
3. The member variable pxOwner is used to point to the object containing the list item (usually a task control block)
4. The member variable pxContainer is used to point to the list where the list item is located. With this variable, you can know the task What status is it currently in?

③Mini list item structure

insert image description here

3. The relationship between lists and list items

insert image description here
The initial state of the list: **In FreeRTOS, the initial state of the list is empty, that is, there are no list items in the list. At this time, the head and tail of the list are pointers to the same special mark xListEnd, and the number of list items is 0. **As shown in the picture:
insert image description here

Inserting two list items: Suppose we want to insert two list items A and B into the list . The insertion operation generally includes the following steps:

(1) Create list items: First, you need to create two list items A and B, which are defined with the ListItem_t structure type respectively. You can use the pvPortMalloc() function to dynamically allocate memory on the heap.

(2) Initialize list items: For each list item, you need to set its xItemValue value, pvOwner pointer, pxNext pointer and pxPrevious pointer and other information. Among them, xItemValue can be understood as the priority of the list item, pvOwner points to the owner of the list item, and pxNext and pxPrevious point to the next and previous list items of the list item.

(3) Insert list item: Insert list item A into the head of the list, and insert list item B into the tail of the list. The insertion operation requires modifying the pxNext and pxPrevious pointers of the before and after list items to point to the new list items. At the same time, the head and tail pointers of the list need to be modified to point to the new head and tail after insertion.
insert image description here

(4) Update the number of list items: Each time a list item is inserted or deleted, the number of list items uxNumberOfItems needs to be updated.

After inserting two list items, the status of the list is as follows:

+------------------------------------------------+
|                   xListEnd                      |
|------------------------------------------------|
| pxPrevious = &B |             | pxPrevious = &A |
|------------------------------------------------|
| pxNext = &A     |             | pxNext = &B     |
|------------------------------------------------|
| xItemValue = 0  |             | xItemValue = 1  |
|------------------------------------------------|
|    pvOwner      |             |    pvOwner      |
+------------------------------------------------+
                           |
                           v
                      +--------+
                      |  List  |
                      +--------+
                   pxIndex = &A
             uxNumberOfItems = 2

Among them, xListEnd represents the end of the list, A and B are list items respectively, List represents the list, pxIndex represents the pointer used to traverse the list items, uxNumberOfItems represents the number of list items in the list, pxNext and pxPrevious represent the next and next items of the list respectively. Previous list item.

4. List related API functions

insert image description here

①Initialization list

insert image description here
The picture makes it very clear that it is the configuration of the list structure. When initialized, there is only xListEnd in the list, so pxIndex points to xListEnd. The value of xListEnd is initialized to the maximum value, which is used to rank last when the list items are sorted in ascending order. When initialized, there is only xListEnd in the list, so the previous and next list items are xListEnd itself. When initialized, the number of list items in the list is 0 (excluding xListEnd).

②Initialize list items

insert image description here
The function parameter is the pointer pxItem pointing to the list item to be initialized, and the function has no return value.

The function of the function is to set both the pxNext and pxPrevious pointers of the list item to NULL, and to set both the xItemValue and pvOwner values ​​to 0, indicating that the list item is initialized to be empty.

This function is often used when inserting new list items, because the list item needs to be initialized to empty before inserting it. For example, you can initialize a list item using the following code:

ListItem_t xItem;
vListInitialiseItem( &xItem );

In this way, the pxNext and pxPrevious pointers of xItem can be set to NULL, and the xItemValue and pvOwner values ​​​​are set to 0, indicating that the list items are initialized to be empty. The xItem can then be inserted into the list, such as at the head or tail of the list by calling the vListInsert() function.

③List item insertion function (ascending insertion)

insert image description here
Inserting in ascending order means sorting the list items according to their xItemValue value and inserting new list items into the correct position. **The larger the value of the list item, the later the insertion order. **Ascending insertion can be achieved by calling the vListInsert() function, for example:

ListItem_t xItemA, xItemB;
xItemA.xItemValue = 1;
xItemB.xItemValue = 2;
vListInitialiseItem( &xItemA );
vListInsert( &xList, &xItemA );
vListInsert( &xList, &xItemB );

In this example, two list items xItemA and xItemB are first created, their xItemValue values ​​are set to 1 and 2 respectively, and the two list items are initialized using the vListInitialiseItem() function. Then, xItemA is inserted into the list, followed by xItemB. Since the xItemValue value of xItemB is larger than xItemA, xItemB will be inserted after xItemA, and the order in the final list is A->B.

④List item insertion function (insert at the end)

insert image description here
Note: The function vListInsertEnd() inserts the list item to be inserted in front of the list item pointed to by the pxIndex pointer in the list ; what you look at is the pxIndex pointer, and don’t look at anything else. Now explain it in detail:
(1) Its parameters are the pointer pxList pointing to the list to be inserted and the list item to be inserted.
(2) Example 1:
insert image description here
First, the index pointer points to the last list item. At this time, list item 2 with a value of 30 is inserted.
Then draw a hierarchy diagram:
insert image description here
the list item to be inserted is at the previous position of the list item pointed to by the index pointer.
(3) Example 2:
insert image description here
At this time, the index pointer points to list item 1 with a value of 40, then:
insert image description here

⑤Remove list item function

insert image description here
The function is to remove the specified list item from the list and return the number of deleted list items. This function is implemented by pointing the pxNext pointer of the pxPrevious pointer of the list item to be deleted to the pxNext pointer of the list item to be deleted, and the pxPrevious pointer of the pxNext pointer of the list item to be deleted points to the pxPrevious pointer of the list item to be deleted.

5. Practical routines for inserting and deleting list items

Purpose:
insert image description here
In fact, you only need to look at the core task code here:

List_t          TestList;           /* 定义测试列表 */
ListItem_t      ListItem1;          /* 定义测试列表项1 */
ListItem_t      ListItem2;          /* 定义测试列表项2 */
ListItem_t      ListItem3;          /* 定义测试列表项3 */

/* 任务二,列表项的插入和删除实验 */
void task2( void * pvParameters )
{
    
    
    vListInitialise(&TestList);         /* 初始化列表 */
    vListInitialiseItem(&ListItem1);    /* 初始化列表项1 */
    vListInitialiseItem(&ListItem2);    /* 初始化列表项2 */
    vListInitialiseItem(&ListItem3);    /* 初始化列表项3 */
    ListItem1.xItemValue = 40;
    ListItem2.xItemValue = 60;
    ListItem3.xItemValue = 50;

    /* 第二步:打印列表和其他列表项的地址 */
    printf("/**************第二步:打印列表和列表项的地址**************/\r\n");
    printf("项目\t\t\t地址\r\n");
    printf("TestList\t\t0x%p\t\r\n", &TestList);
    printf("TestList->pxIndex\t0x%p\t\r\n", TestList.pxIndex);
    printf("TestList->xListEnd\t0x%p\t\r\n", (&TestList.xListEnd));
    printf("ListItem1\t\t0x%p\t\r\n", &ListItem1);
    printf("ListItem2\t\t0x%p\t\r\n", &ListItem2);
    printf("ListItem3\t\t0x%p\t\r\n", &ListItem3);
    printf("/**************************结束***************************/\r\n");
    
    printf("\r\n/*****************第三步:列表项1插入列表******************/\r\n");
    vListInsert((List_t*    )&TestList,         /* 列表 */
                (ListItem_t*)&ListItem1);       /* 列表项 */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("/**************************结束***************************/\r\n");
    
    /* 第四步:列表项2插入列表 */
    printf("\r\n/*****************第四步:列表项2插入列表******************/\r\n");
    vListInsert((List_t*    )&TestList,         /* 列表 */
                (ListItem_t*)&ListItem2);       /* 列表项 */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
    printf("/**************************结束***************************/\r\n");
    
    /* 第五步:列表项3插入列表 */
    printf("\r\n/*****************第五步:列表项3插入列表******************/\r\n");
    vListInsert((List_t*    )&TestList,         /* 列表 */
                (ListItem_t*)&ListItem3);       /* 列表项 */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
    printf("/**************************结束***************************/\r\n");
    
    /* 第六步:移除列表项2 */
    printf("\r\n/*******************第六步:移除列表项2********************/\r\n");
    uxListRemove((ListItem_t*   )&ListItem2);   /* 移除列表项 */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
    printf("/**************************结束***************************/\r\n");
    
    /* 第七步:列表末尾添加列表项2 */
    printf("\r\n/****************第七步:列表末尾添加列表项2****************/\r\n");
    TestList.pxIndex = &ListItem1;
    vListInsertEnd((List_t*     )&TestList,     /* 列表 */
                   (ListItem_t* )&ListItem2);   /* 列表项 */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->pxIndex\t\t0x%p\r\n", TestList.pxIndex);
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
    printf("/************************实验结束***************************/\r\n");
    while(1)
    {
    
    
        vTaskDelay(1000);
    }
}

This is a sample task that uses FreeRTOS lists to implement the insertion and deletion of list items. This task initializes the list, list items and sets the value of the list item, then inserts the list item into the list, prints the address of the list before and after inserting the list item and the address of the list item, and finally deletes the specified list item from the list.

The specific operations are as follows:

Initialize lists and list items: Use the vListInitialise() and vListInitialiseItem() functions to initialize lists and list items respectively.

Print the addresses of the list and other list items: Use the printf() function to print the addresses of the list and other list items for comparison when inserting list items later.

Insert list item 1 into the list: Use the vListInsert() function to insert list item 1 into the list, and print the addresses of the list before and after the list item is inserted and the address of the list item for comparison.

Insert list item 2 into the list: Use the vListInsert() function to insert list item 2 into the list, and print the addresses of the list before and after the list item is inserted and the address of the list item for comparison.

Insert list item 3 into the list: Use the vListInsert() function to insert list item 3 into the list, and print the addresses of the list before and after the list item is inserted and the address of the list item for comparison.

Remove list item 2: Use the uxListRemove() function to remove list item 2 from the list, and print the list after removing the list item and the address of the list item for comparison.

Add list item 2 to the end of the list: Use the vListInsertEnd() function to insert list item 2 to the end of the list, and print the addresses of the list and list items before and after the list item is inserted for comparison.

Look at the running results:
The first is the second step: note that the content in the list is empty at this time, only the last list item.
insert image description here
Then the third step is to insert list item 1 in ascending order:
insert image description here
just like holding hands, the next of list item 1 points to the last list item, and the previous of the last list item points to the previous one, which is list item 1. The subsequent ideas are similar. I won’t take a screenshot of the results here.

Finally, let’s summarize the functions of lists and list items:

The main functions of lists and list items are as follows:

Task scheduling: The task scheduler in FreeRTOS uses lists and list items to implement task scheduling. Each task has a list item, and the task scheduler determines the next task to be executed based on the priority and status of the list item.

Event notification: The event notification mechanism in FreeRTOS is also implemented using lists and list items. Each event has a list item. When the event occurs, the list item corresponding to the event can be inserted into a waiting list, waiting to be awakened.

Storing data: Lists can also be used to store data. For example, the message queue in FreeRTOS uses lists to store messages.

Guess you like

Origin blog.csdn.net/qq_53092944/article/details/132583699