超级精简系列之:高效且运行时间固定的内存池实现

原创 嵌入式Lee 2024-06-20 08:31

一. 前言

这里简单提一下在RTOS环境开发中,比较容易出现的一个BUG为例,引出我们为什么要设计我们这个内存池的轮子。以FreeRTOS为例,堆管理mallocfree的实现是基于链表进行的,链表是一个全局的资源,且操作不是一个原子操作,涉及很多步骤,所以多处调用时就必须进行临界段保护,否则操作到一半被打断进行另外的操作就会导致资源被破坏。FreeRTOS的处理方式是关调度的方式来实现临界段保护,关调度只能保证任务不会抢占,无法保证中断抢占,所以中断中不能调用FreeRTOSmallocfree接口。而有时驱动设计又必须使用动态内存管理,驱动很多都是中断驱动的有时必须在中断中调用堆管理接口,此时直接调用FreeRTOS的堆管理接口就会导致问题,上述问题很可能难以发现,并且不一定能测试出来,可能在某种凑巧时机,刚好中断中打断任务中的堆接口执行,并调用堆管理接口此时就会出现问题。这种问题很可能就会出现很大的随机性导致了的调试的困难。知道了这个原因就可以避免类似的错误了,但是回到问题来,我们中断中还是有动态内存的使用需求那怎么办呢,最简单的方式就是还是使用FreeRTOS提供的堆接口,把关调度改为关中断,这样就可以保证临界段不被抢占,就不会有问题,但是这里会有一些副作用就是关中断时间的增加,并且这个时间还是不确定的,我们下一节就进行了实测。当然堆管理还有碎片化等问题,所以不适合在驱动层和中断时调用。所以我们还有一种方式就是使用内存池,这是很多中间件,驱动比较常见的方式,我们这一篇就是要来实现一个简单高效的内存池,但是我们不满足于此,我们还有更高的要求,毕竟是在驱动层甚至中断中使用,我们希望它执行非常快,并且执行时间基本无波动,因为这两个指标都是很重要的,很多系统甚至是必须要满足的。

二. 堆管理接口执行时间测试

执行时间测试有很多种方式,一般使用硬件定时器。我们这里使用另一种实践中也常用的方式,使用IO翻转,示波器查看波形的方式测试,进入接口执行时翻转IO,退出接口执行,再次翻转IO,IO波形得脉宽即执行时间,这样可以可视化观察,疏密变化可以看出执行时间的抖动,脉宽可以看出执行时间,malocfree使用不同IO对比,还可以看出申请和释放的时间分布等等,所以这是一种非常好的方式。

我这里实测某个平台,freertos得malloc和free执行时间如下(注意执行时间硬件平台不一样也不一样),可以看到时间抖动较大,时间也不短,4.6uS已经算很长了,比如中断中做时序解析这个时间很可能就难以满足需求。

后面我们对比修改为我们高效的内存池实现的对比测试,可以看到基本无抖动,且执行时间非常短(这里还未去掉翻转IO函数本身的执行和IO响应时间)。从汇编代码其实也可以看出指令很少了。

三. 内存池实现

常规设计

将一块空间划分为n个等大小的块,前面用一个字段标记是否使用的标记,剩余部分作为有效存储空间。初始化全部是空闲,标记都是0,需要申请时从头开始搜索,搜索到标记为0的块则可申请将标记改为1,释放则直接匹配地址将对应的标记设置为0.

上述标记至少需要占用一个字节,并且插入到有效块前会影响有效块的对齐属性,所以还可以优化,标记信息单独使用bitmap记录,如下所示,

有效块可以按照需要的原始数据结构连续对齐,bitmap一个bit对应一个块,为1表示该块已分配为0表示空闲,bitmap也减少了空间占用。申请释放算法和原来类似,申请只是变为了搜索bitmap的最低字节最低位开始的0的位置将其设置为1,获取对应的块地址,释放则是根据地址计算bitmapbit索引将对应的bit设置为0.

高效无时间抖动设计

上述bitmap实现在存储上已经优化了,但是在执行时间上因为要遍历搜寻所以是有抖动的,那么要满足我们的无时间抖动设计,目标就是要实现查找最低字节最低为开始的0的位置的算法时间固定。我们前面很多文章其实已经提到了矛盾的思想即,从矛盾的角度看问题,要解决一个问题可以看其对立面,因为往往矛盾是此消彼长的,算法的时间和空间就是矛盾体,要求时间那么我们就去看看是否能从空间的角度去考虑,一种常见的空间换时间的方法就是查表法,查表一方面时间固定一方面也高效。实际在uSOSII的实现中,查找最高就绪优先级就使用了该算法,我们直接拿过来使用并简单介绍下其原理。

首先我们将bitmap按照矩阵排列,自然而然地想到以字节为列,bit0~7对应0~7列,多个字节对应多行。每个bit对应一个块,bit1表示空闲,为0表示占用。

那么最低字节的最低位位1则是我们要找的空闲块。那么如何快速找到这个bit呢,常规思想是从字节0开始找,如果全为0则继续找字节1,找到不为0的字节,然后从bit0bit7找最开始出现的1的位置,那么这个bit在整个bit的索引就是(字节序号*8+位序号).

所以时间上可以分解为两步,第一步是找字节位置,第二步找bit位置。

bit位置实际我们可以很快想到通过查表法实现。

8为数据有256种可能,每一种可能我们直接将其最低出现1的位置写出来就行,实际就是构造一个256字节的数组表,其指定索引的值,就是这个索引对应的数字,其最先出现1bit位置。

比如

00000000 没有1则最低最先出现1的位置是0,所以数组索引1的位置值为0

00000001 最先出现1的位置是bit0,所以数组索引1的位置值为0

00000010 最先出现1的位置是bit1,所以数组索引2的位置值为1

00000011 最先出现1的位置是bit0,所以数组索引3的位置值为0

00000100 最先出现1的位置是bit2,所以数组索引4的位置值为2

所以最终构造出如下表格


static uint8_t  const  s_unmaptbl[256] = {    0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x00 to 0x0F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x10 to 0x1F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x20 to 0x2F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x30 to 0x3F                             */    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x40 to 0x4F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x50 to 0x5F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x60 to 0x6F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x70 to 0x7F                             */    7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x80 to 0x8F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x90 to 0x9F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xA0 to 0xAF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xB0 to 0xBF                             */    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xC0 to 0xCF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xD0 to 0xDF                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xE0 to 0xEF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0        /* 0xF0 to 0xFF                             */};

现在查找字节最开始出现1的位置查表一步就得到了时间是固定的,那么查找字节按照前面介绍还是的从字节0开始查询最开始不是0的字节。我们仔细思考一下,前面一个字节8bit一行的角度去查找最开始的1是对应的列,现在查找字节不就是对应的行吗。

那么我们把每一行是否是0先记录下来,再用一个bitmap来记录这一行是否为0不就行了吗。如下所示我们原来的bitmap8个字节,8行,那么我们再使用一个字节8bit就可以记录这8个字节是否为0了,这个新增的bitmap我们叫做grp

如果原来bitmap的字节10grpbit00,否则为1表示这个字节中有1即有空闲块。那么问题简单了,查找这8个字节,最低字节不为0的字节,即查找这个grp的最低位最先出现的1,这样问题归一了,就是我们前面的查表。这样要找最低字节的最低先出现的1就是两步查表,先根据grp查找到字节位置,然后根据这个字节的值查找bit位置。最终的索引就是字节位置*8+bit位置

最终算法实现如下


y = s_unmaptbl[dev->grp];    x = s_unmaptbl[dev->tbl[y]];    index = (uint8_t)((y<<3) + x);

这里的dev->grp即我们上面的grp字节,

Dev->tblbitmap字节数组。

y即字节位置,xbit位置,indexbitmap的索引位置。

而找bitmap索引将对应的bit改为0即申请,操作如下,index>>3得到字节索引,~(1u<<(index & 0x07)是清除对应的bit。如果这个字节变为了0,则相应的grp字节的对应bit也要清零


      if(((dev->tbl[index>>3] &= (~(1u<<(index & 0x07)))) == 0))        {            dev->grp &= ~(1u<<(index>>3));        }

释放则是将bitmap对应索引位置的bit置为1

    dev->grp |= 1u<<(index>>3);    dev->tbl[index>>3] |= 1u<<(index & 0x07);

举例如下

Tbl2~7字节都非0所以,grpbit2~bit7都为1

tbl中最低字节最低位出现的1是字节2bit3.

计算过程如下,先grp=0xFC=252查表得到,索引252处的值为2,所以定位到字节2

Tbl[2]的值为0x08,查表得到该索引对应的值是3

所以最开始出现1的位置是字节2bit3,即2*8+3=19.

源码如下

Mem_pool.c

#include "mem_pool.h"
static uint8_t  const  s_unmaptbl[256] = {    0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x00 to 0x0F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x10 to 0x1F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x20 to 0x2F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x30 to 0x3F                             */    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x40 to 0x4F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x50 to 0x5F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x60 to 0x6F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x70 to 0x7F                             */    7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x80 to 0x8F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x90 to 0x9F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xA0 to 0xAF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xB0 to 0xBF                             */    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xC0 to 0xCF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xD0 to 0xDF                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xE0 to 0xEF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0        /* 0xF0 to 0xFF                             */};
int mem_pool_init(mem_pool_st* dev, void* buffer, void* tbl, uint32_t itemsize, uint32_t itemnum){    #if MEM_POOL_PARAM_CHECK        if(dev == (mem_pool_st*)0)        {            return -1;        }        if((buffer == (void*)0) || (tbl == (void*)0))        {            return -1;        }    #endif    dev->buffer = buffer;    dev->itemnum = itemnum;    dev->itemsize = itemsize;    dev->grp = (uint8_t)0xFF;    dev->tbl = tbl;    for(uint32_t i=0; i< (dev->itemnum+7)/8; i++)    {        dev->tbl[i] = (uint8_t)0xFF;    }    return 0;}
uint8_t* mem_pool_malloc(mem_pool_st* dev){    /* 查找最靠前的,空闲的块 */#if MEM_POOL_PARAM_CHECK    if(dev == (mem_pool_st*)0)    {        return (void*)0;    }    if((dev->buffer == (void*)0) || (dev->tbl == (uint8_t*)0))    {        return (void*)0;    }#endif
    uint8_t y;    uint8_t x;    uint8_t index;    y = s_unmaptbl[dev->grp];    x = s_unmaptbl[dev->tbl[y]];    index = (uint8_t)((y<<3) + x);    if((index == 0) && (dev->tbl[0]==0))    {        /* 找到的索引为0,且对应的tbl为0, 说明bitmap所有位都为0,即无空闲 */        return (void*)0;    }    else    {        /* 对应bit置0,表示已经占用 */        if(((dev->tbl[index>>3] &= (~(1u<<(index & 0x07)))) == 0))        {            dev->grp &= ~(1u<<(index>>3));        }        /* 返回对应的地址 */        return dev->buffer + dev->itemsize*index;    }}
int mem_pool_free(mem_pool_st* dev, uint8_t* buffer){#if MEM_POOL_PARAM_CHECK    if(dev == (mem_pool_st*)0)    {        return -1;    }    if((dev->buffer == (void*)0) || (dev->tbl == (uint8_t*)0))    {        return -1;    }#endif
#if MEM_POOL_ERR_CHECK    if(buffer == (void*)0)    {        return -2;    }#endif    uint8_t index;    index = (buffer - dev->buffer)/dev->itemsize;#if MEM_POOL_ERR_CHECK    if(((dev->grp & (1u<<(index>>3))) != 0) && ((dev->tbl[index>>3] & (1u<<(index & 0x07))) != 0))    {        /* 释放空闲的块 */        return -3;    }#endif    /* 设置对应bit位置为1表示空闲 */    dev->grp |= 1u<<(index>>3);    dev->tbl[index>>3] |= 1u<<(index & 0x07);    return 0;}

Mem_pool.h

#ifndef MEM_POOL_H#define MEM_POOL_H
#ifdef __cplusplusextern "C" {#endif  #include
#define MEM_POOL_PARAM_CHECK 1#define MEM_POOL_ERR_CHECK 1
typedef struct{    uint32_t   grp;    uint32_t   itemsize; /**< 区块大小                               */    uint32_t   itemnum;  /**< 总区块数,8的倍数,最多256                */    uint8_t*   buffer;   /**< 用户提供的缓存区,大小为itemsize*itemnum  */    uint8_t*   tbl;} mem_pool_st;
int mem_pool_init(mem_pool_st* dev, void* buffer, void* tbl, uint32_t itemsize, uint32_t itemnum);uint8_t* mem_pool_malloc(mem_pool_st* dev);int mem_pool_free(mem_pool_st* dev, uint8_t* buffer);
#ifdef __cplusplus}#endif
#endif

四. 实测

我们将其替换到我们某个驱动的实现中的mallocfree调用,测试功能OK,并按照二测试其时间抖动和执行时间,确认执行时间非常短,且基本无抖动。

初始化代码如下

  #define TRANS_POOL_NUM 64    #define GROUP_POOL_NUM 64    static dma_trans_t s_trans_buffer[TRANS_POOL_NUM];    static uint8_t s_trans_bitmap_buffer[(TRANS_POOL_NUM+7)/8];    static mem_group_info_t s_group_buffer[GROUP_POOL_NUM];    static uint8_t s_group_bitmap_buffer[(GROUP_POOL_NUM+7)/8];
    mem_pool_st s_trans_mem_pool_dev;    mem_pool_st s_group_mem_pool_dev;    mem_pool_init(&s_trans_mem_pool_dev, s_trans_buffer, s_trans_bitmap_buffer, sizeof(dma_trans_t), TRANS_POOL_NUM);    mem_pool_init(&s_group_mem_pool_dev, s_group_buffer, s_group_bitmap_buffer, sizeof(mem_group_info_t), GROUP_POOL_NUM);

申请

trans = (dma_trans_t *)mem_pool_malloc(&s_trans_mem_pool_dev);

释放

mem_pool_free(&s_trans_mem_pool_dev, trans);

五. 总结

以上我们借鉴uCOSII中的算法应用到我们自己设计的内存池轮子上,实现了高效且无时间抖动的内存池实现,是一个非常有用的轮子。这也提醒我们平常多注意借鉴参考别人的思想实现,并为己所用,不断积累。

以上实际使用的是bitmap查表实现固定时间,使用二维bitmap来扩充容量,上述例子是支持最大块数64个即8x8。如果要扩充更大的块数怎么办呢,自然想到得是增加二维的X/Y即可,即可将tblgrp8位改为16位,但是这存在一个问题,改为16位查表的表有2^16种情况,需要64k这在嵌入式中很可能是不可接受的。我们再来思考前面tbl扩展到grp,即X扩展到Y的过程,即一维扩展到二维的过程,我们是不是突然眼前一亮,拍案而起,甚至有点激动了,是的很自然的我们可以再增加一维变为三维,即对于体中点得位置,可以先找面,再找行,再找行中的bit点,就是这么简单自然,所以只需要将grp也改为数组即可,再增加一个vol字节对grp数组进行记录,vol一个bit 记录grp一个字节是否为0。所以我们要从本质去理解一项技术才可能是正真得理解,比好比这个查表从维度扩充角度去思考这才是本质,懂了这个本质自然会扩充,并为己所用应用到其他场景,而不是纠结于查表等技术细节,纠结于这些实际并没有理解本质,也仅仅会这个案例而已很难举一反三,这也是我们为什么思考问题一定要思考到本质,为什么我一直强调思考技术问题一定要思考到背后的本质甚至哲学意义,背后的原理甚至哲学意义才是普适得本质原理。

我们通过增加维度实现扩充,这就是降维打击,三阶搜寻变为了三次查表,有点类似对数的本质是把乘除法降维成加减法,也许四维度空间生物看我们三维空间的生物是多么的藐视了。

不由的要感叹世界的奇妙,也许只有造物主能做到,能够降维实际也是一种普遍的现象,可能也是造物主虽然没有让我们活在高维世界但是给我们开了个后门,让我们拥有了对高维进行降维分析的能力,对数降维,投影降维等都类似,可以让我们从侧面推测全貌,不知道能否从数学抽象角度来建立体系,证明具备何种属性的空间具备可降维性,那么这就建立了一套分析高维的数理体系了。


评论
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 209浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 137浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 115浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 160浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 108浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 195浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 51浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 117浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 101浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 84浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 47浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 152浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 44浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦