【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等结构体完全依赖于链表操作,尤其注意尾部插入时不能破坏原本链表的公平性。

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

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

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

相关文章

OpenWRT搭建本地web站点并结合内网穿透实现公网远程访问

文章目录 前言1. 检查uhttpd安装2. 部署web站点3. 安装cpolar内网穿透4. 配置远程访问地址5. 配置固定远程地址 前言 uhttpd 是 OpenWrt/LuCI 开发者从零开始编写的 Web 服务器,目的是成为优秀稳定的、适合嵌入式设备的轻量级任务的 HTTP 服务器,并且和…

【Windows】MCSM面板搭建Mycraft服务器,实现公网远程联机

文章目录 前言1.Mcsmanager安装2.创建Minecraft服务器3.本地测试联机4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射内网端口 5.远程联机测试6. 配置固定远程联机端口地址6.1 保留一个固定TCP地址6.2 配置固定TCP地址 7. 使用固定公网地址远程联机 前言 MCSManager是一个…

[香橙派]Orange pi zero 3命令行配网方法——建立ssh连接——Ubuntu配置WIFI自动连接

一、前言 前面我们给Orange Pi安装了Ubuntu系统,并通过MobaXterm进行了串口连接,但其实并不方便,在日常开发中,我们希望能够使用更方便的ssh连接来进行操作,因此配置网络是必要的。 本章介绍的方法无需网线、HDMI线等&…

upload-labs

01 随便上传个文件 发现对于上传类型有限制 查看页面代码发现是js的过滤直接关闭js 上传成功 右键图片在新建标签页打开文件 这里直接抓包改名字也行 02 抓包修改后缀名 03 发现后端做了检测抓包修改失败 大小写绕过失败,php特性php1等会被当成php执行 这里图片的…

MS1242,替代ADS1242,24bit 高精度、低功耗模数转换器

产品简述 MS1242/MS1243 是一款高精度、宽动态范围、 ∆-Σ 模数转 换芯片,其工作电压为 2.7V 至 5.25V ,可以达到 24bit 无失码转 换,有效精度可达 21bit 。 MS1242/MS1243 可以广泛使用在工 业控制、称重、液体 / 气体化学分析、血液分…

手眼标定 - 最终精度和误差优化心得

手眼标定 - 标定误差优化项 一、TCP标定误差优化1、注意标定针摆放范围2、TCP标定时的点次态与工作姿态尽可能保持相近 二、深度相机对齐矩阵误差1、手动计算对齐矩阵 三、手眼标定拍照姿态1、TCP标定姿态优先2、水平放置棋盘格优先 为减少最终手眼标定的误差,可做或…

Get职场新知识:做分析,用大数据分析工具

为什么企业每天累积那么多的数据,也做数据分析,但最后决策还是靠经验?很大程度上是因为这些数据都被以不同的指标和存储方式放在各自的系统中,这就导致了数据的分析口径和标准不一致,无法在同一个分析软件上做综合分析…

Rsync+Sersync

服务器相关参数 源服务器 192.168.17.101 目标服务器(同步到的服务器) 192.168.17.103 ##目标服务器配置 ###1、配置rsync服务 1、安装rsync yum -y install rsync 2、配置rsync vim /etc/rsyncd.conf 配置文件内容 uid root gid root use c…

Module build failed : Error : Vue packages version mismatch:

Vue packages version mismatch: - vue2.7.15 (E:\Workspace_ce\erp\erp-web\node_modules\vue\dist\vue.runtime.common.js) - vue-template-compiler2.6.11 (E:\Workspace_ce\erp\erp-web\node_modules\vue-template-compiler\package.json) 【问题解决了,我很不…

bootstrap:选项卡功能DEMO

<!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>选项卡</title> <link rel"stylesheet" type"text/css" href"/cdn.bootcss.com/bootstrap/3.3.2/css/bootstrap.min.css" />…

记一次测试环境git翻车经历

本来想拉一个功能分支进行新的功能开发&#xff0c;合并代码发现没有冲突居然有文件被修改了&#xff0c;贸然选择最近的一次回滚提交&#xff0c;没想到不假思索的push -f 导致一部分dev主干的代码不见了。 事故记录 开发分支origin/dev&#xff0c;功能分支file 合并之后发…

Java 实现 文档 添加 水印 工具类

一、pom 文件引用 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>…

MeterSphere实战(一)

MeterSphere是一位朋友讲到的测试平台&#xff0c;说这东西是开源的&#xff0c;因为我是做测试的&#xff0c;很乐意了解一些新鲜事物。在我看来&#xff0c;测试就是要专注一些领域&#xff0c;然后要啥都会一点点&#xff0c;接着融会贯通起来&#xff0c;这样就可以万变不离…

C语言--不使用库函数,把一个数字转为字符串【详细解释】

一.题目描述 输入一个数字&#xff0c;把他转为字符串 比如&#xff1a;输入数字&#xff1a;12345 输出&#xff1a;12345&#xff08;这里的12345是字符串12345&#xff09; 二.思路分析 比如给定一个数字12345&#xff0c;先把它转为字符54321&#xff08;“54321”&#…

线程互斥与同步

用户级线程 内核的LWP Linux线程 OS概念中经常说的 用户级线程 和 内核级线程 也就是线程实现真的是在OS内部实现&#xff0c;还是应用层或用户层实现 很明显Linux是属于用户级线程 用户级执行流&#xff08;用户级线程&#xff09; &#xff1a;内核lwp 1 : 1 也有1&…

骁龙8 Gen 3 vs A17 Pro

骁龙8 Gen 3 vs A17 Pro——谁会更胜一筹&#xff1f; Geekbench、AnTuTu 和 3DMark 等基准测试在智能手机领域发挥着至关重要的作用。它们为制造商和手机爱好者提供了设备性能的客观衡量标准。这些测试有助于评估难以测量的无形方面。然而&#xff0c;值得注意的是&#xff0c…

骚操作:NanoDrop测蛋白浓度

​大家好&#xff0c;最近实验室的BCA仪器坏了&#xff0c;偶然发现nanodrop也可以测蛋白浓度&#xff0c;省不少时间&#xff01;本方法原理是&#xff1a;紫外吸收 友情提示&#xff1a;由于表格的存在&#xff0c;用电脑看本推文&#xff0c;效果更好 紫外吸收法 较为灵…

31条PCB设计布线技巧:

大家在做PCB设计时&#xff0c;都会发现布线这个环节必不可少&#xff0c;而且布线的合理性&#xff0c;也决定了PCB的美观度和其生产成本的高低&#xff0c;同时还能体现出电路性能和散热性能的好坏&#xff0c;以及是否可以让器件的性能达到最优等。 本篇内容&#xff0c;将…

分布式锁实现方案 - Lock4j 使用

一、Lock4j 分布式锁工具 你是不是在使用分布式锁的时候&#xff0c;还在自己用 AOP 封装框架&#xff1f;那么 Lock4j 你可以考虑一下。 Lock4j 是一个分布式锁组件&#xff0c;其提供了多种不同的支持以满足不同性能和环境的需求。 立志打造一个简单但富有内涵的分布式锁组…

Redis分布式缓存超详细总结!

文章目录 前言一、Redis持久化解决数据丢失问题1.RDB&#xff08;Redis Database Backup file&#xff09;持久化&#xff08;1&#xff09;执行RDB&#xff08;2&#xff09;RDB方式bgsave的基本流程&#xff08;3&#xff09;RDB会在什么时候执行&#xff1f;save 60 1000代表…