【RTOS学习】FreeRTOS中的链表 | 堆的管理

🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
图

目录

  • 🥩FreeRTOS中的链表
    • 🥞初始化
    • 🥞尾部插入
    • 🥞按顺序插入
    • 🥞删除
  • 🥩堆的管理
    • 🥞heap_1.c
    • 🥞heap_2.c
    • 🥞heap_4.c
    • 🥞heap_5.c
  • 🥩总结

🥩FreeRTOS中的链表

链表是FreeRTOS的核心结构,它让系统的功能正常运行,本喵下面来解释一下FreeRTOS中的链表结构以及操作。

图
如上图所示是FreeRTOS源码中的链表的定义List_t,这是一个链表头,重要的成员变量有三个:

  • volatile UBaseType_t uxNumberOfItems:表示链表中包含的节点个数。
  • ListItem_t * configLIST_VOLATILE pxIndex:这是一个指针变量,表示当前正在操作的节点。
  • MiniListItem_t xListEnd:这是一个结构体变量,从这个变量可以找到链表中所有节点。

tu
如上图所示MinListItem_t结构体定义,它也有三个重要的成员变量:

  • configLIST_VOLATILE TickType_t xItemValue:这是一个值,需要排序的时候才有意义,后面会见识到。
  • struct xLIST_ITEM * configLIST_VOLATILE pxNext:这是一个指针变量,该变量指向链表中的第一个链表项。
  • struct xLIST_ITEM * configLIST_VOLATILE pxPrevious:这也是一个指针变量,该变量指向链表中的最后一个链表项。

tu
如上图所示是链表项ListItem_t的定义,它包含五个重要的成员变量:

  • configLIST_VOLATILE TickType_t xItemValue:该值作为链表项中的一个数值,在排序时才会起作用。
  • struct xLIST_ITEM * configLIST_VOLATILE pxNext:该指针变量指向下一个链表项。
  • struct xLIST_ITEM * configLIST_VOLATILE pxPrevious:该指针变量指向前一个链表项。
  • void * pvOwner:这也是一个指针变量,指向该链表项所属的容器。以后本喵会介绍。
  • struct xLIST * configLIST_VOLATILE pxContainer:这也是一个指针变量,指向该链表项所在的List_t类型链表头。
  • 从链表头以及链表项的定义中就可以看出,FreeRTOS中的链表是一个带头双向循环链表。

tu
如上图所示便是FreeRTOS中链表的链接关系,链表头结构体成员xListEnd中的pxNext指向第一个链表项的起始地址,链表项中的pxNext指向下一个链表项的起始地址,如此下去,最后一个链表项中的pxNext指向链表头中的xListEnd

  • 此时链表头和链表项中的所有pxNext构成一个环路。

链表头中的结构体成员xListEnd中的pxPrevious指向最后一个链表项的起始地址,该链表项中的pxPrevious指向前一个链表项,如此下去,第一个链表项中的pxPrevious指向链表头中的xListEnd

  • 此时链表头和链表项中的所有pxPrevious构成另一个环路。

tu
如上图所示,由于真正的指向关系不直观,所以本喵后面使用这样的示意图来表示这个链表,要记住无论是pxNext还是pxPrevious指向的都是链表项的起始地址。

🥞初始化

图
如上图所示链表初始化函数vListInitialise,在该函数中对传入的链表pxList主要进行了四步操作:

  1. 让链表头中当前操作节点指针pxIndex指向自己的xListEnd
  2. xListEnd中的xItemValue = portMAX_DELAY,用该值来区别这是链表尾而不是链表项。
  3. xListEnd中的pxNextpxPrevious都指向自己的xListEnd,初步形成两个环路。
  4. uxNumberOfItems = 0表示当前链表中没有链表项,是一个空链表。

此时一个初步的链表就形成了,操作的都是链表头自己。

图
如上图所示链表项初始化函数vListInitialiseItem,在该函数中,仅是将链表项中的pxContainer设置为NULL,因为此时并不知道该链表项属于哪个链表头。

🥞尾部插入

tu
如上图所示插入链表尾部的函数vListInsertEnd,在该函数中,总体进行了四步操作:

  1. 从链表头中获得当前正常操作的链表项pxIndex
  2. 将新的链表项插入到pxIndexpxIndex->pxPrevious之间。
  3. 让新链表项的pxContainer = pxList,让其找到所属的链表。
  4. 将链表头中记录链表项个数的unNumberOfItems加一。

图
如上图所示便是尾部插入新链表项的结果,但是它并不是插入到了尾部呀。

假设现在正在操作的是编号为2的链表项,所以pxIndex指向它,如果没有新链表项插入的话,这几个链表项的操作顺序是2->3->1

此时要插入新的链表项,但是并不能影响原本的操作顺序,否则就破坏了公平性,所以将新的链表项4插入到pxIndexpxIndex->pxPreviou之间,此时这几个链表项的操作顺序是2->3->1->4,并不会影响原来三个链表项的操作顺序。

  • 尾插时要保证公共性,插入于正在操作链表项pxIndex和它的前一个链表项pxIndex->pxPrevious之间。

上面所演示的是链表中已经存在链表项时尾插的结果,如果是一个空链表呢?

图
如上图所示第一次尾插的结果,在插入之前pxIndexpxIndex->pxPrevious都是指向的链表头中的xListEnd,当新节点插入时,pxIndex->pxPrevious指向新节点,pxIndex->pxNext也指向新节点,新节点的pxIndexpxPrevious都指向链表头中的xListEnd

所以说,尾插函数vListInsertEnd可以实现第一次插入和已经存在多个链表项时再插入新链表项两个场景。

🥞按顺序插入

图

如上图所示按照链表项中的xItemValue值插入函数vListInsert,该函数中主要进行了四步操作:

  1. 获取新插入链表项中的xItemValue值。
  2. 根据xItemValue值在链表中寻找合适位置:
    • xItemValue = portMAX_DELAY说明新的链表项应该插入到链表的最后面,直接让插入位置pxIterator等于链表的最后一项。
    • xItemValue不是最大,则迭代寻找合适位置,pxIterator从链表头开始,直到pxIterator->pxNext指向的链表项中的xItemValue大于新链表项的xItemValue,此时得到插入位置就是pxIterator后面。
  3. 插入新链表项,改变指向关系。
  4. 修改新链表项中的pxContainer,让其属于当前链表头,并且修改链表头中的链表项个数。

tu
如上图所示是插入后的结果,原本链表中的xItemValue从左到右依次是1->3->4,将xItemValue为2的新链表项插入以后,xItemValue成了1->2->3->4

🥞删除

tu
如上图所示删除指定链表项的函数uxListRemove,该函数中进行的主要操作也是四步:

  1. 从要删除的链表项中得到它所属的链表pxList
  2. 改变链表中的指向关系,让被删除链表项的前一个直接和被删除链表项的后一个建立互相的指向关系。
  3. 进行判断,如果删除的是正在操作的链表项,那么需要将pxIndex指向下一个链表项,更换正在操作的链表项。
  4. 让被删除的链表项失忆,也就是将其pxContainer = NULL,让它找不到原本所属的链表,再将链表头中链表项的个数减一,最后返回链表项的个数。

tu
如上图所示是删除后的示意图,所谓删除就是改变被删除链表项的前一个和后一个链表项的指向关系,让其从链表中脱离出来。

🥩堆的管理

很多人把"堆栈"相提并论,其实"堆"、"栈"是完全没有联系。"栈"的作用我们前面已经讲了很多,"堆"是什么?

  • 在汇编代码里指定一个AREA:在汇编代码里,使用SPACE命令可以分配一段空间,这段空间就是堆:
    图
  • 在C代码里,定义一个全局数组:该数组的大小是17KB,这段空间就是堆:

图

"堆"就是一块或者多块内存,我们可以从中申请一小块内存来使用,使用完毕后可以释放这一小块内存。

简单地说,一开始,"堆"是一些空闲内存,我们可以:

  • 使用malloc函数从中申请、获得一小块内存
  • 使用free函数释放这一小块内存
  • 这些malloc、free函数就是用来管理这些内存的
  • malloc、free函数可以有其他名称,比如FreeRTOS里是pvPortMalloc、vPortFree。

在FreeRTOS的源码中,默认提供了5个文件,对应内存管理的5种方法:

文件优点缺点
heap_1.c分配简单,时间确定只分配、不回收
heap_2.c动态分配、最佳匹配碎片、时间不定
heap_3.c调用标准库函数速度慢、时间不定
heap_4.c相邻空闲内存可合并可解决碎片问题、时间不定
heap_5.c在heap_4基础上支持分隔的内存块可解决碎片问题、时间不定

只能使用上诉五种方式的中的一种来管理堆,其中heap_3.c由于使用的是标准库函数中的mallocfree,速度比较慢,所以本喵这里也不做介绍,只介绍其他四种。

🥞heap_1.c

图
如上图所示在堆区上申请空间的函数pvPortMalloc,其中xWantedSize是要申请的空间大小,申请过程主要分为四步:

  1. 创建返回地址pvReturn和对齐地址pucAlignedHeap,让这两个指针都为0。
  2. 对申请的空间大小进行对齐。

tu
如上图所示宏定义#define portBYTE_ALIGNMENT 8规定了8字节对齐,什么是字节对齐呢?

tu
如上图所示,假设现在有9个字节的空间在内存中连续排列,而我们的CPU是32位的,也就是说一次读取或者写入数据必须操作的是4字节大小的空间。

现在一共9字节,操作两次以后还剩下一个字节,再操作时仍然是4字节,所以要想操作第9个字节,就会额外多操作三个字节,此时就会导致效率低下。

  • 甚至有的CPU并不支持这种不对齐访问。

由于是8字节对齐,所以申请的空间大小必须是8的整数倍,如果不符合8字节对齐,就需要向上对齐,如申请100个字节,实际上申请的是对齐后的104字节。

  1. 找到堆区的对齐地址pucAlignedHeap

图
如上图所示,假设整个堆区ucHeap在内存上的起始地址是0x20000001,按照CPU每次操作4字节的规律,该地址无论如何也无法作为单次CPU的起始地址,所以操作起来不方便。

按照代码中所写,所以对齐地址求出来就是0x20000008,该地址一定是CPU操作时4字节中的起始地址。

此时对齐地址pucAlignedHeap就是指向这里,而且让xNextFreeByte + = xWantedSize,得到此次申请空间大小。

  1. 获取申请空间

tu
如上图所示,如果是第一次申请空间,那么最后返回的就是对齐地址pucAlignedHeap所指向的地址,如果不是第一次申请,那么在pucAlignedHeap基础上增加上次空间大小的偏移量pxNextFreeByte得到的就是返回地址。

简单来说,heap_1管理堆的方式就是,通过pucAlignedHeappxNextFreeByte两个全局变量,每申请一次,就返回上一次申请后剩余空间的起始位置。


tu
如上图释放动态空间函数vPortFree,并不支持释放空间。

  • heap_1.c里,只能用pvPortMalloc函数来申请空间,无法使用vPortFree函数来释放空间。

🥞heap_2.c

heap_2.c里,使用链表来管理内存。链表结构体为:

tu
这个结构体用来表示空闲块:

  • pxNextFreeBlock:指向下一个空闲块
  • xBlockSize:当前空闲块的内存大小

图
如上图所示,在这个结构体后面,紧跟着空闲内存。

  • 在堆空间static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]上,空闲空间往前偏移8个字节就得到BlockLink_t结构体的起始地址。

初始化:

tu

如上图所示堆的初始化函数prvHeapInit,在该文件中定义了两个BlockLink_t类型的全局变量,一个是链表头xStart,另一个是链表尾xEnd,在该函数中主要进行四步操作:

  1. 从整个堆空间上计算对齐地址pucAlignedHeap
  2. 让链表头xStart中的pxNextFreeBlock指向对齐地址,并且将链表头中的xBlockSize = 0,因为链表头并不管理空闲内存。
  3. 让链表尾xEnd中的pxNextFreeBlock指向空,xBlockSize = configADJUSTED_HEAP_SIZE,这是堆的最大值,为了排序时方便才这样设计的。
  4. pxFirstFreeBlock指向对齐地址,并且构造BlockLink_t中的成员,让xBlockSize = configADJUSTED_HEAP_SIZEpxNextFreeBlock指向链表尾。

图
如上图所示初始化完成的结果,此时在整块堆空间的中,最前面的8字节就是BlockLink_t结构体,包含该空间的大小,并链入了管理空闲堆空间的链表中。

分配内存:

图

如上图所示分配空间函数pvPortMalloc的部分代码,主要进行了四步操作:

  1. 如果是第一次调用pvPortMalloc,则先对整个堆区进行初始化,让整个堆区作为空闲空间链入到空闲空间管理链表xStartxEnd之间。
  2. 对申请的空间大小进行对齐计算。
  3. 从空闲空间管理链表中寻找合适的空间块。
    • 假设此时链表中存在多个含有BlockLink_t的空闲块,所以根据要申请空间的大小迭代找到比申请空间大的空闲块。
  4. 通过链表找到的是空闲块的起始地址,包含BlockLink_t,所以向后偏移8个字节,得到该空闲块的对齐地址作为返回值供申请者使用。

图
如上图,返回的是该空闲块中红色箭头指向的地址。

图
如上图所示的紧接着前面pvPortMalloc函数剩下的代码,主要有四步操作,本喵这里接着前面从标号5开始讲解:

  1. 将寻找到的空闲块从空闲链表中移除,也就是修改该块前后的指向关系。
  2. 如果找到的这块空间大于所申请的字节数,将用不了的部分赋值给pxNewBlockLink
  3. 构造用不了的那部分块中的BlockLink_t结构体。
  4. 调用prvInsertBlockIntoFreeList将这个剩余的块作为空闲块插入到管理链表中。

图

如上图所示prvInsertBlockIntoFreeList宏函数,用来插入空闲内存块到管理链表中,主要操作分为三步:

  1. 获取插入内存块的大小xBlockSize
  2. 根据大小xBlcokSize迭代按照升序找到合适的插入位置pxIterator
  3. 将内存块pxBlockToInsert插入到pxIterator之后。

tu
如上图所示是最终分配效果,Block1从管理链表中移除,供申请者使用,Blcok2是用不了剩下的部分,重新链入到管理链表中。

释放内存:

tu
如上图所示释放内存函数vPortFree,主要操作有两步:

  1. 由于传入的pv是申请者使用的地址,所以向前偏移8个字节得到该内存块的BlcokLink_t地址。
  2. 调用pxBlockToInsert将该内存块链入到管理链表中。

tu
如上图所示,假设释放的是Block1内存块,此时该内存块和原本的Block2一起在管理链表中。

  • heap_2管理的链表,支持内存的申请和释放。
  • 由于释放的内存块直接链入到管理链表,所以如果再次申请的内存比上次释放的大,那么释放的这块就无法再次利用,就会存在内存碎片。
  • 所以heap_2适合用在频繁申请和释放固定大小内存的场景。

🥞heap_4.c

初始化:
图
如上图所示是使用prvHeapInit初始化完的示意图,heap_4整体上和heap_2的管理方式一样,也是通过链表和BlockLink_t来管理空闲内存块,但是不一样的是:

  • 链表尾xEnd位于整个堆空间的末尾,它并不是独立的一个变量,而是依附在空闲空间上。
  • 链表尾xEnd中的xBlockSize = 0,和heap_2中的configADJUSTED_HEAP_SIZE不一样。

分配内存:

tu
如上图所示,分配内存时和heap_2一样,将Block1中管理链表中移除,将用不完的Block2部分再次链入到链表中,返回值也是Block1的对齐地址。

在将用不了的内存块重新链入到管理链表中时,也会调用prvInsertBlockIntoFreeList函数,heap_4heap_2的区别就在这个函数中。

释放内存:

释放内存时,和heap_2一样,也会调用到prvInsertBlockIntoFreeList函数来将释放的内存块链入到管理链表,来分析一下这个函数:

tu
如上图所示prvInsertBlockIntoFreeList的部分代码,这里进行的操作就是寻找合适的插入位置。

  • 没有获取插入内存块的大小。
  • 迭代时按照内存块的地址寻找,当插入内存块pxBlockToInsert地址小于等于pxIterator->pxNextFreeBlock时,将内存块插入到pxIterator后面。

tu
如上图所示prvInsertBlockIntoFreeList中紧接前面部分的后续代码,进行的操作主要分为3步:

  1. 在寻找到插入位置pxIterator以后,先判断插入内存块pxBlockToInsert和其前面的pxIterator是否紧挨着(pxIterator的地址 + 偏移量 == pxBlockToInsert的地址)。

    • 如果相等,说明pxBlockToInsertpxIterator紧挨着,可以进行合并,此时改变pxBlockToInsert的大小xBlockSizepxNextFreeBlock覆盖pxIterator
  2. 再判断判断插入内存块pxBlockToInsert和其后面的pxIterator->pxNextFreeBlock是否紧挨着(pxBlockToInsert的地址 + 偏移量 == pxIterator->pxNextFreeBlock的地址)。

    • 如果相等,说明 pxBlockToInsertpxIterator->pxNextFreeBlock是紧挨着,可以进行合并,同样仅修改pxBlockToInsertBlockLink_t覆盖pxIterator->pxNextFreeBlock即可。
  • 这里的偏移量就是块的大小pxBlockToInsert->xBlockSize
  1. 如果找到插入位置pxIterator后,发现pxBlockToInsert和它前面以及后面的内存块都不挨着,就无法实现合并,只能和heap_2一样将pxBlockToInsert插入到管理链表中。

tu
如上图所示是在分配内存后将用不了的内存块插入管理链表和释放内存时将释放的内存块插入管理链表时,没有进行合并的结果示意图,此时和heap_2没有区别。

tu

如上图所示是将插入的内存块与管理链表中的其他内存块合并后的结果示意图。

  • 管理链表中的内存块合并以后变大了,再次申请比上一次释放掉的更大内存块时,合并后的内存可以继续被利用。
  • 克服了heap_2再次申请更大空间时会有内存碎片的问题。

🥞heap_5.c

heap_5heap_4机会一样,只是heap_5支持多个不连续区域作为堆。

初始化:

tu
如上图所示结构体HeapRegion_t是用来描述用作堆的区域的,pucStartAddress表示该区域的起始地址,xSizeInbytes表示该区域的大小。

tu
如上图所示,在使用heap_5之前,需要先定义一个全局的HeapRegion_t类型的数组array,该数组中存放的就是多个连续的堆区,然后调用vPortDefineHeapRegions来初始化所有的堆。

  • 数组中的最后一项是{NULL, 0},当发现某一个堆区的地址是NULL的时候,说明就遍历到了数组中的最后一项。

图
如上图所示vPortDefineHeapRegions函数,该函数内部主要进行了五步操作:

  1. 创建while循环来遍历所有不连续的堆区域。
  2. 求每个堆区域的对齐地址和对齐大小。
  3. 处理第一个堆区时,将其链入到xStart链表头。
  4. 在每个堆区域末尾构造BlockLink_t
  5. 将所有堆区域首尾相连在一起。

tu
如上图所示是将两个不连续堆区初始化后首尾相连在一起的结果示意图。

其他操作:

在初始化以后,就可以将这些不连续的堆看成是一个堆,其他操作完全按照heap_4来就可以。

🥩总结

对于FreeRTOS的链表操作,一定要理解透彻,因为后面的任务TCB等结构体完全依赖于链表操作,尤其注意尾部插入时不能破坏原本链表的公平性。

对于多种堆的管理方式,要知道它们适用的场合和大致的原理,根据适用场景适用合适的管理方式。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JS对象笔记

对象声明 对象也只是一种数据类型/字面值。写对象这个字面值有两种写法,一种是普通的对象,这种对象用new 构造函数(),另一种是JS内特有的json对象。这个对象是直接{}就代表对象。且也是在堆内。 对象的构成 无论是上…

Ransac 算法的探索和应用

Ransac 算法python 应用和实现 Ransac 算法是一种常用的图像匹配算法,在参数估计领域也经常被使用到。针对估计各种曲线的鲁棒模型参数,效果显著。这里对ransac算法进行某些探索。 python program: import numpy as np import matplotlib.pyplot as p…

Profibus、Profinet、Ethernet有什么区别?

PROFINET 是一种新的以太网通讯系统,是由西门子公司和 Profibus 用户协会开发。 PROFINET 具有多制造商产品之间的通讯能力,自动化和工程模式,并针对分布式智能自动化系统进行了优化。其应用结果能够大大节省配置和调试费用。 PROFINET 系统集…

_pickle.PicklingError: Can‘t pickle : import of module failed

有问题 没问题的 python - pickle cant import a module that exists? - Stack Overflow

华媒舍:怎样利用旅游业发展媒体套餐宣传推广分析7个经典案例

1.分析经典案例在旅游业发展中,依靠媒体套餐开展宣传推广成为了一种常见的方法。下面将详细介绍7个经典案例,从这当中我们可以得出一些宝贵经验。 案例一:目标市场定位成功宣传推广一定要明确市场定位,针对不同的受众群体制定合理…

【FPGA/verilog -入门学习12】Verilog可配置的PWM设计,参数传递的3种方式

需求: 基于任务(task)的PWM设计仿真验证 需求分析: 1,需求实现可配置PWM输出(频率,占空比) 2,输入,输出端口 input i_clk, //clk 50Mhz input i_rst_n, input i_en, output…

设计模式(三)-结构型模式(1)-适配器模式

一、为何需要适配器模式(Adapter)? 在软件设计中,某个模块里有很多公用的功能接口,其中有些公用接口需要用到不同的类当中时,会出现接口不兼容的问题。因为这些不同的类对这个相同任务的接口,都有各自代码…

DNSLog漏洞探测(七)之SQL注入漏洞实战

DNSLog漏洞探测(七)之SQL注入漏洞实战 在前面的文章之中,我们已经学习了XSS、RCE、XXE、SSRF漏洞中有关于DNSLog平台的使用。这些漏洞本身在执行时就有解析URL地址,发起网络请求的操作,所以只要我们把DNSLog平台获取的子域名地址发送到存在漏…

优化钢铁加工:RFID技术的革新应用

优化钢铁加工:RFID技术的革新应用 RFID是一种无线通信技术,通过将标签上的电子数据以无线电信号的形式传输,实现对物品的远程识别和跟踪。在钢铁加工领域,RFID技术的应用能够提高生产效率、降低成本并优化物流管理。本文将探讨RF…

DevOps 和人工智能 – 天作之合

如今,人工智能和机器学习无处不在,所以它们开始在 DevOps 领域崭露头角也毫不令人意外。人工智能和机器学习正在通过自动化任务改变 DevOps,并使各企业的软件开发生命周期更高效、更深刻和更安全。我们在 DevOps 趋势中简要讨论过这一问题&am…

【Hive】——DDL(DATABASE)

1 概述 2 创建数据库 create database if not exists test_database comment "this is my first db" with dbproperties (createdByAllen);3 描述数据库信息 describe 可以简写为desc extended 可以展示更多信息 describe database test_database; describe databa…

有意思!40小时工作制来了,996再见

​在中国,加班文化已经深入人心。工资越高加班越多,“996”已成为一些行业标签,月薪30k以上的职场人中超过86%经常加班。所以今天我就来说一下这40小时工作制到底是从何而来,感兴趣的往下看看吧! 40小时工…

【Hive_02】查询语法

1、基础语法2、基本查询(Select…From)2.1 全表和特定列查询2.2 列别名2.3 Limit语句2.4 Where语句2.5 关系运算函数2.6 逻辑运算函数2.7 聚合函数 3、分组3.1 Group By语句3.2 Having语句3.3 Join语句(1)等值与不等值Join&#x…

【CCF BDCI 2023】多模态多方对话场景下的发言人识别 Baseline 0.71 NLP 部分

【CCF BDCI 2023】多模态多方对话场景下的发言人识别 Baseline 0.71 NLP 部分 概述NLP 简介文本处理词嵌入上下文理解 文本数据加载to_device 函数构造数据加载样本数量 len获取样本 getitem 分词构造函数调用函数轮次嵌入 RobertaRoberta 创新点NSP (Next Sentence Prediction…

uniapp原生插件之安卓动态权限申请原生插件

插件介绍 安卓动态权限申请原生插件,集成了常用的权限申请,可以自定义权限申请 该插件为原生开发,如果您想使用UTS版本可以点击这里 插件地址 安卓动态权限申请原生插件,支持常用的权限申请,支持自定义权限申请 - DC…

CleanMyMac X2024中文版好不好用?有哪些特点

CleanMyMac X您的 Mac。极速如新。点按一下,即可优化调整整个 Mac畅享智能扫描 — 这款超级简单的工具用于优化您的 Mac。只需点按一下,即可运行所有任务,让您的 Mac 保持干净、快速并得到最佳防护。CleanMyMac 是一款功能强大的 Mac 清理程序…

【网络安全】Suspicious DNS Query(可疑的DNS查询)

文章目录 名词解释可能原因分析Action sinkhole在防火墙里面查询Suspicious DNS Query预防Suspicious DNS查询带来的风险推荐阅读 名词解释 “Suspicious DNS Query(可疑的DNS查询)”通常指的是在网络中检测到的可能具有风险或异常行为的DNS&#xff08…

【数据结构】栈和队列超详解!(Stack Queue)

文章目录 前言一、栈1、栈的基本概念2、栈的实现(数组实现)3、栈的基本操作3.1 栈的结构设计3.2 栈常见的基本函数接口 4、栈的实现4.1 初始化栈4.2 栈的销毁4.3 入栈4.4 出栈4.5 判空4.6 长度4.7 获取栈顶元素 完整代码Stack.hStack.cTest.c 二、队列1、…

点对点协议PPP

目录 一. PPP协议的特点1.1 PPP 协议应满足的需求1.2 PPP 协议的组成 二. PPP协议的帧格式2.1 PPP协议的透明传输2.1.1 同步传输2.1.2 异步传输 三. PPP协议的工作状态 \quad 一. PPP协议的特点 \quad \quad 用户到ISP的链路使用PPP协议 \quad \quad 1.1 PPP 协议应满足的需求 …

面试题:HashMap 为什么不能一边遍历一遍删除

文章目录 前言foreach 循环?HashMap 遍历集合并对集合元素进行 remove、put、add1、现象为什么会抛出这个异常呢?2、细究底层原理 前言 上面出现这样的原因是在使用 foreach 对 HashMap 进行遍历时,同时进行 put 赋值操作会有问题&#xff0c…