实时操作系统Freertos开坑学习笔记:(四):临界段保护、列表与列表项

前言

废话不多说,直接看主要要探究的问题:
在这里插入图片描述
在这里插入图片描述

一、临界段代码保护

1.什么是临界段?

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
图里面说,临界区的代码是不能被打断的,它运行时不能被中断打断,也不能由于非阻塞任务延时而切换到其他任务去。
比如说IIC进行通信时,软件模拟IIC会有一个4-8us的延时,说明通信时序非常重要,这里一旦被打断就会有影响。
如果我写项目,任务一需要利用IIC通信测量MPU6050加速度值,任务二需要每0.5秒上报一次数据,那我的任务一会被任务二的调度而打断,即使事后仍能回到打断处,但是对于时序要求极高的IIC通信而言,肯定会造成出错。
那应该怎样才能使临界区程序不被打断呢?

2.临界段代码不被中断打断的方法

在这里插入图片描述
对临界区代码的保护本质上就是关中断、开中断
在第三节中断管理中,谈到了开关中断的函数:
在这里插入图片描述
这与临界段保护的开关中断函数不一样,但是本质是一样的。

3.看一个实例

其实实例很简单,而且之前也提到过,就是写开始任务时,要创建任务,必须先进入临界区,在退出临界区。

taskENTER_CRITICAL() 宏用于进入临界区,它会将任务优先级设置为最高,禁止所有中断,保证在临界区的代码不会被其他任务或中断打断。

taskEXIT_CRITICAL() 宏用于退出临界区,它会将任务优先级恢复到原来的值,并允许中断,让其他任务或中断可以继续执行。

为什么这么做呢?第二节我写过,比如如果不这样做,你创建一个task1和task2,在刚创建好task1并且还没创建好task2时,task1就会因为优先级高而先被执行了,万一task1不分享时间片,那岂不是task2根本就不会被运行?为了防止这种任务提前抢占当前任务的情况,那就得用临界区代码保护功能,使得task1和2全被初始化后,在按照优先级老老实实运行。

二、任务调度器的挂起和恢复

1.前言

当创建好任务后,总是需要开启任务调度器来进行任务的切换:

vTaskStartScheduler();

那么,任务调度器同样也会有挂起和恢复的状态。

2.任务调度器挂起和恢复的API函数

在这里插入图片描述

首先要知道中断与任务(调度)切换的区别:

①触发方式:中断是由外部事件触发的,如硬件设备的输入、定时器溢出等;而任务切换是由操作系统内部的调度器触发的,它根据一定的调度策略来决定切换到哪个任务。
②执行环境:中断是在中断上下文中执行的,它会暂停当前运行的任务,保存中断现场,执行中断服务程序,最后恢复现场返回到原来的任务。而任务切换是在任务上下文中执行的,它会保存当前任务的上下文,切换到另一个任务的上下文继续执行。
③调度开销:中断的调度开销比任务切换要小,因为中断服务程序通常很短,只需要保存和恢复现场即可;而任务切换需要保存和恢复多个寄存器和堆栈,因此开销相对较大。
④调度优先级:中断的优先级通常比任务的优先级高,因为中断需要尽快响应外部事件;而任务的优先级则由操作系统内部的调度器决定,根据任务的重要性和紧急程度来分配优先级。

通俗地说,我创建了两个task,均是每隔0.5s闪烁一次led,同时,我又初始化了一个外部中断+按键,按下会使得task1挂起。那么,两个task之间的调度属于任务调度,而外部中断、定时器溢出中断、串口接收中断等等均属于中断,优先级更高。

挂起任务调度器, 调用此函数不需要关闭中断

这句话的意思是说:它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应。挂起调度器的方式,适用于临界区位于任务与任务之间;既不用去延时中断,又可以做到临界区的安全。
它是怎么保护临界区程序的呢?很简单,把任务调度器ban了,使得可能占据程序资源的任务根本不会来抢占了。但是也有缺点,就是外部中断照样有可能打断临界区程序。
这就要看你的临界区程序具体的形式和所处的位置来决定采用开关中断或者ban任务调度器。

;临界区代码保护和任务调度器挂起的总结图:
在这里插入图片描述

三、列表与列表项

前面各种地方都在说列表,什么就绪任务列表、任务挂起列表等等,那列表到达是个什么东西呢?这个概念是非常重要的,如果只看重外部实现,那浅浅了解,但是要深入去看freertos内核源码是怎么实现的,列表是重中之重!对于理解freertos的运行机制很有帮助。

1.列表和列表项的定义

在这里插入图片描述

(1)列表:列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS中的任务。数据结构上说,链表(Linked List)是一种常见的线性数据结构,用于存储一系列数据元素。链表中的每个元素由一个节点(Node)表示,每个节点包含一个数据元素和指向下一个节点的指针。通过指针,可以将各个节点连接起来,形成一个链式结构。

(2)列表项:列表项就是存放在列表中的项目。

(3)示意图:
在这里插入图片描述
列表项就是列表中的节点。它是一个环形的列表,是一个双向的环形链表。
举个例子:
在这里插入图片描述
三个人组成了一个环形的链表结构,我们知道链表是用指针指向节点的,这里比如小明是一个节点,它的身体就是数据元素,而他的右手就是一个指向下一位节点小黑的指针,他的左手就是指向上一位节点小红的指针。
假设我这时突然要在小明和小红之间插进来一个小美,那就让小美的左右手跟小明、小红拉上即可,而要删除一个节点,同理。

所以这种列表结构为什么适合freertos呢?

(1)列表的特点:列表项间的地址非连续的,是人为的连接到一起的。列表项的数目是由后期添加的个数决定的,随时可以改变。我可以随意添加和删除其中的节点,每个节点表示一个任务,包含了任务的状态、优先级、堆栈等信息。通过遍历链表,可以快速访问所有的任务,并根据调度算法选择下一个要执行的任务。
(2)动态分配内存:链表可以根据需要动态分配内存,可以灵活地增加或删除任务,不需要事先知道任务列表的大小。
(3)高效的插入和删除:链表的插入和删除操作只需要修改指针,时间复杂度为 O(1)。
(4)空间利用率高:链表只需要存储任务的指针和状态信息,不需要像数组那样预留空间,因此空间利用率更高。

2.列表相关结构体

在这里插入图片描述
list.c就是freertos源码中对于列表的配置。

①列表结构体

在这里插入图片描述
这里进行解释:
(1)在该结构体中, 包含了两个宏,

listFIRST_LIST_INTEGRITY_CHECK_VALUE
listSECOND_LIST_INTEGRITY_CHECK_VALUE

这两个宏是确定的已知常量, FreeRTOS通过检查这两个常量的值,
来判断列表的数据在程序运行过程中,是否遭到破坏 ,该功能一般用于调试, 默认是不开启的,其实不重要,可以不看。

(2)

volatile UBaseType_t uxNumberOfItems;

成员uxNumberOfItems,用于记录列表中列表项的个数(不包含 xListEnd)

(3)成员 pxIndex 用于指向列表中的某个列表项,一般用于遍历列表中的所有列表项

ListItem_t * configLIST_VOLATILE pxIndex;

(4)成员变量 xListEnd 是一个迷你列表项,排在最末尾

MiniListItem_t xListEnd;

另外,看这个结构图:
在这里插入图片描述
列表项是列表中用于存放数据的地方,在 list.h 文件中,有列表项的相关结构体定义。每一个列表项代表一个任务。

②列表项结构体

而列表项的结构成员如下:
在这里插入图片描述
1、成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序
2、成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项
3、成员变量 pxOwner 用于指向包含列表项的对象(通常是任务控制块)
4、成员变量 pxContainer 用于指向列表项所在列表,有了这个变量,就可以知道任务目前属于哪个状态的了。

③迷你列表项结构体

在这里插入图片描述

3.列表和列表项的关系

在这里插入图片描述
列表的初始状态:**在 FreeRTOS 中,列表的初始状态是空的,即列表中没有任何列表项。此时,列表的头部和尾部都是指向同一个特殊的标记 xListEnd 的指针,并且列表项数量为 0。**如图:
在这里插入图片描述

插入两个列表项:假设我们要向列表中插入两个列表项 A 和 B。插入操作一般包括以下几个步骤:

(1) 创建列表项:首先需要创建两个列表项 A 和 B,分别用 ListItem_t 结构体类型定义,可以使用 pvPortMalloc() 函数在堆上动态分配内存。

(2) 初始化列表项:对于每个列表项,需要设置其 xItemValue 值、pvOwner 指针、pxNext 指针和 pxPrevious 指针等信息。其中,xItemValue 可以理解为列表项的优先级,pvOwner 指向该列表项的拥有者,pxNext 和 pxPrevious 指向该列表项的下一个和上一个列表项。

(3) 插入列表项:将列表项 A 插入列表的头部,将列表项 B 插入列表的尾部。插入操作需要修改前后列表项的 pxNext 和 pxPrevious 指针,将其指向新的列表项。同时,需要修改列表的头部和尾部指针,将其指向插入后的新的头部和尾部。
在这里插入图片描述

(4) 更新列表项数量:每次插入或删除列表项后,需要更新列表中的列表项数量 uxNumberOfItems。

插入两个列表项后,列表的状态如下:

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

其中,xListEnd 表示列表的结尾,A 和 B 分别为列表项,List 表示列表,pxIndex 表示用于遍历列表项的指针,uxNumberOfItems 表示列表中的列表项数量,pxNext 和 pxPrevious 分别表示列表项的下一个和上一个列表项。

4.列表相关API函数

在这里插入图片描述

①初始化列表

在这里插入图片描述
图中说的很清楚,就是对列表结构体的配置。初始化时,列表中只有 xListEnd,因此 pxIndex 指向 xListEnd。xListEnd 的值初始化为最大值,用于列表项升序排序时,排在最后。初始化时,列表中只有 xListEnd,因此上一个和下一个列表项都为 xListEnd 本身。初始化时,列表中的列表项数量为 0(不包含 xListEnd)。

②初始化列表项

在这里插入图片描述
函数参数为指向要初始化的列表项的指针 pxItem,函数没有返回值。

函数的作用是将列表项的 pxNext 和 pxPrevious 指针都设置为 NULL,将 xItemValue 和 pvOwner 值都设置为 0,表示将列表项初始化为空。

该函数在插入新的列表项时经常使用,因为在插入列表项之前需要先将其初始化为空。例如,可以使用以下代码初始化列表项:

ListItem_t xItem;
vListInitialiseItem( &xItem );

这样就可以将 xItem 的 pxNext 和 pxPrevious 指针都设置为 NULL,将 xItemValue 和 pvOwner 值都设置为 0,表示将列表项初始化为空。然后可以将 xItem 插入到列表中,如通过调用 vListInsert() 函数将其插入到列表的头部或尾部。

③列表项插入函数(升序插入)

在这里插入图片描述
升序插入是指按照列表项的 xItemValue 值进行排序,将新的列表项插入到正确的位置。**列表项数值越大,插入的顺序就越靠后。**升序插入可以通过调用 vListInsert() 函数实现,例如:

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

在此例子中,先创建了两个列表项 xItemA 和 xItemB,分别设置其 xItemValue 值为 1 和 2,并使用 vListInitialiseItem() 函数初始化这两个列表项。然后,将 xItemA 插入到列表中,接着将 xItemB 插入到列表中。由于 xItemB 的 xItemValue 值比 xItemA 大,因此会将 xItemB 插入到 xItemA 的后面,最终列表中的顺序为 A->B。

④列表项插入函数(末尾插入)

在这里插入图片描述
注意:函数vListInsertEnd(),是将待插入的列表项插入到列表 pxIndex 指针指向的列表项前面;看的就是pxIndex 指针,其他不要看。现在详细地进行讲解:
(1)它的参数为指向要插入列表的指针 pxList和待插入列表项。
(2)例一:
在这里插入图片描述
首先,index指针指向末尾列表项,这时插入值为30的列表项2,
那么画一个层次图:
在这里插入图片描述
要插入的列表项是在index指针指向的列表项的前一个地方。
(3)例二:
在这里插入图片描述
这时index指针指向值为40的列表项1,那么:
在这里插入图片描述

⑤移除列表项函数

在这里插入图片描述
函数的作用是从列表中删除指定的列表项,并返回删除的列表项数量。该函数是通过将要删除的列表项的 pxPrevious 指针的 pxNext 指向要删除的列表项的 pxNext 指针,将要删除的列表项的 pxNext 指针的 pxPrevious 指向要删除的列表项的 pxPrevious 指针来实现的。

5.列表项的插入和删除实战例程

目的:
在这里插入图片描述
这里其实只用看核心任务代码就行:

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);}
}

这是一个使用 FreeRTOS 列表实现列表项的插入和删除的示例任务。该任务通过初始化列表、列表项和设置列表项的值,然后将列表项插入到列表中,并打印插入列表项前后的列表和列表项的地址,最后从列表中删除指定的列表项。

具体操作如下:

初始化列表和列表项:使用 vListInitialise() 和 vListInitialiseItem() 函数分别初始化列表和列表项。

打印列表和其他列表项的地址:使用 printf() 函数打印列表和其他列表项的地址,以便后面插入列表项时进行对比。

列表项1插入列表:使用 vListInsert() 函数将列表项1插入到列表中,打印插入列表项前后的列表和列表项的地址,以便对比。

列表项2插入列表:使用 vListInsert() 函数将列表项2插入到列表中,打印插入列表项前后的列表和列表项的地址,以便对比。

列表项3插入列表:使用 vListInsert() 函数将列表项3插入到列表中,打印插入列表项前后的列表和列表项的地址,以便对比。

移除列表项2:使用 uxListRemove() 函数将列表项2从列表中移除,打印移除列表项后的列表和列表项的地址,以便对比。

列表末尾添加列表项2:使用 vListInsertEnd() 函数将列表项2插入到列表的末尾,打印插入列表项前后的列表和列表项的地址,以便对比。

看运行结果:
首先是第二步:注意这时列表里面内容为空,只有末尾列表项。
在这里插入图片描述
然后第三步升序插入列表项1:
在这里插入图片描述
就像手牵手一样,列表项1的next指向末尾列表项,末尾列表项的previous指向前一个即列表项1.后续的思路都差不多。这里我不把结果截图出来了。

最后总结一下列表和列表项的作用:

列表和列表项的主要作用如下:

任务调度:FreeRTOS 中的任务调度器使用列表和列表项来实现任务的调度。每个任务都有一个列表项,任务调度器根据列表项的优先级和状态来决定下一个要执行的任务。

事件通知:FreeRTOS 中的事件通知机制也使用列表和列表项来实现。每个事件都有一个列表项,当事件发生时,可以将该事件对应的列表项插入到一个等待列表中,等待被唤醒。

存储数据:列表也可以用于存储数据,例如 FreeRTOS 中的消息队列就是使用列表来存储消息的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/121930.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

bash: cmake: command not found...+++++++lsb_release: command not found

一 .bash: cmake: command not found… centos中安装那个cmake。 1、问题 [rootPC3 home]# cmake bash: cmake: command not found... Similar command is: make当前系统: [rootPC3 home]# lsb_release -a LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx…

蝶形运算法

蝶形运算法是一种基于FFT(Fast Fourier Transform)算法的计算方法,其基本思想是将长度为N的DFT分解成若干个长度为N/2的DFT计算,并通过不断的合并操作得到最终的结果。该算法也称为“蝴蝶算法”,因为它的计算过程中需要…

[Android 四大组件] --- Activity

1 Activity是什么 ​​Activity​​是一个Android的应用组件,它提供屏幕进行交互。每个Activity都会获得一个用于绘制其用户界面的窗口,窗口可以充满哦屏幕也可以小于屏幕并浮动在其他窗口之上。 一个应用通常是由多个彼此松散联系的Activity组成&…

vue3学习笔记

语句直接写在<script setup></script>内 1.父组件向子组件传值 子组件&#xff08;名字cs.vue&#xff09;&#xff1a; <template><h1 >{{ msg }}</h1> </template><script setup> defineProps({msg: String }) </script>父…

LeetCode--HOT100题(47)

目录 题目描述&#xff1a;105. 从前序与中序遍历序列构造二叉树&#xff08;中等&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;105. 从前序与中序遍历序列构造二叉树&#xff08;中等&#xff09; 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preo…

【AI测试】python文字图像识别tesseract

[AI测试]python文字图像识别tesseract github官网&#xff1a;https://github.com/tesseract-ocr/tesseract python版本&#xff1a;https://github.com/madmaze/pytesseract OCR&#xff0c;即Optical Character Recognition&#xff0c;光学字符识别&#xff0c;是指通过扫…

工程制造领域:企业IT架构

一、IT组织规划架构图 1.1 IT服务保证梯队与指导思想 二、整体业务规划架构图 三、数据化项目规划架构图 四、应用系统集成架构图

CSS Flex布局

前言 Flex布局&#xff08;弹性盒子布局&#xff09; 是一种用于在容器中进行灵活和自适应布局的CSS布局模型。通过使用Flex布局&#xff0c;可以更方便地实现各种不同尺寸和比例的布局&#xff0c;使元素在容器内自动调整空间分配。 目录 容器属性 &#x1f341;display属性 &…

数据结构与算法复杂度介绍

目录 一、基本概念 二、时间复杂度 【2.1】时间复杂度概念 【2.2】大O的渐进表示法 【2.3】举例时间复杂度计算 三、空间复杂度 一、基本概念 数据结构&#xff1a;相互之间存在一种或者多种特定关系的数据元素的集合。在逻辑上可以分为线性结构&#xff0c;散列结构、树…

单片机电子元器件-按键

电子元器件 按键上有 四个引脚 1 2 、 3 4 按下之后 导通 1 3 、 2 4 初始导通 通常按键开关为机械弹性开关&#xff0c;开关在闭合不会马上稳定的接通&#xff0c;会有一连串的抖动 抖动时间的长短有机械特性来决定的&#xff0c;一般为5ms 到10 ms 。 消抖的分类 硬件消…

【大数据Hive】hive 加载数据常用方案使用详解

目录 一、前言 二、load 命令使用 2.1 load 概述 2.1.1 load 语法规则 2.1.2 load语法规则重要参数说明 2.2 load 数据加载操作演示 2.2.1 前置准备 2.2.2 加载本地数据 2.2.3 HDFS加载数据 2.2.4 从HDFS加载数据到分区表中并指定分区 2.3 hive3.0 load 命令新特性 …

vue项目配置MongoDB的增删改查操作

在Vue中配置MongoDB的增删改查操作&#xff0c;需要先安装mongoose模块来连接MongoDB数据库。 1. 在Vue项目的根目录中&#xff0c;使用命令行安装mongoose模块&#xff1a; npm install mongoose --save 2. 找到启动node的app.js文件&#xff08;我这里是在server文件中&…

Java开发之Redis(面试篇 持续更新)

文章目录 前言一、redis使用场景1. 知识分布2. 缓存穿透① 问题引入② 举例说明③ 解决方案④ 实战面试 3. 缓存击穿① 问题引入② 举例说明③ 解决方案④ 实战面试 4. 缓存雪崩① 问题引入② 举例说明③ 解决方案④ 实战面试 5. 缓存-双写一致性① 问题引入② 举例说明③ 解决…

Linux执行命令

命令格式 主命令 选项 参数&#xff08;操作对象&#xff09;例如&#xff1a; 修改主机名 hostname set-hostname 新名称显示/目录下的文件的详细信息 ls -l /命令 内置命令&#xff08;builtin&#xff09;&#xff1a;shell程序自带的命令。 外部命令&#xff1a;有独立…

多通道振弦数据记录仪应用桥梁安全监测的关键要点

多通道振弦数据记录仪应用桥梁安全监测的关键要点 随着近年来桥梁建设和维护的不断推进&#xff0c;桥梁安全监测越来越成为公共关注的焦点。多通道振弦数据记录仪因其高效、准确的数据采集和处理能力&#xff0c;已经成为桥梁安全监测中不可或缺的设备。本文将从以下几个方面…

【JavaEE】_HTML

目录 1.HTML结构 2. HTML常用标签 2.1 注释标签 2.2 标题标签&#xff1a;h1~h6 2.3 段落标签&#xff1a;p 2.4 换行标签&#xff1a;br 2.5 格式化标签 2.6 图片标签&#xff1a;img 2.7 超链接标签&#xff1a;a 2.8 表格标签 2.9 列表标签 2.10 表单标签 2.10…

PPO算法

PPO算法 全称Proximal Policy Optimization&#xff0c;是TRPO(Trust Region Policy Optimization)算法的继承与简化&#xff0c;大大降低了实现难度。原论文 算法大致流程 首先&#xff0c;使用已有的策略采样 N N N条轨迹&#xff0c;使用这些轨迹上的数据估计优势函数 A ^ …

TCP 和 UDP 的区别、TCP 是如何保证可靠传输的?

先来介绍一些osi七层模型 分为应用层、表示层、会话层、运输层、网络层、链路层、物理层。 应用层(数据)&#xff1a;确定进程之间通信的性质以及满足用户需要以及提供网络和用户应用&#xff0c;为应用程序提供服务&#xff0c;DNS&#xff0c;HTTP&#xff0c;HTTPS&#xf…

【LeetCode】剑指 Offer <二刷>(7)

目录 题目&#xff1a;剑指 Offer 14- I. 剪绳子 - 力扣&#xff08;LeetCode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 题目&#xff1a;剑指 Offer 14- II. 剪绳子 II - 力扣&#xff08;…

Java“牵手”唯品会商品详情数据,唯品会商品详情API接口,唯品会API接口申请指南

唯品会平台商品详情接口是开放平台提供的一种API接口&#xff0c;通过调用API接口&#xff0c;开发者可以获取唯品会商品的标题、价格、库存、月销量、总销量、库存、详情描述、图片等详细信息 。 获取商品详情接口API是一种用于获取电商平台上商品详情数据的接口&#xff0c;…