Slub分配器的来龙去脉

一口Linux 2022-07-22 11:40

slab分配器设计的需求

在Linux内核的内存子系统中,伙伴系统无疑处于内存管理的核心地带,但是如果将内存管理从逻辑上分层,它的位置则处于最底层。Buddy是所有物理内存的管家,不论使用何种接口申请内存都要经由伙伴系统进行分配。但是,伙伴系统管理的物理内存是以页为单位,以4K页为例,它也包含了4096个字节。但是无论是内核自己还是用户程序,在日常的使用中都很少会需要使用四千多字节大小的内存。试想如果我们仅需要为10个字符的字符串分配内存,但是伙伴系统却给了我们一页,那这一页剩余没有使用的内存就浪费了,而且这个浪费近乎奢侈。除了浪费的问题, 还有一个更需要关心的问题是,在这样的分配情况下,如果分配非常频繁,系统可能很快就会面临严重的碎片化问题。因为频繁使用的数据结构也会频繁的分配和释放,加速生产内存碎片。另外,直接调用伙伴系统的操作对系统的数据和指令高速缓存也有很大的影响。所以,基于以上的原因,也源于现实需求,内核需要一种轻量的、快速的、灵活的新型内存分配器,最主要的是,它可以提供小块内存的分配。为了实现这样的小内存分配器,Sun公司的J.Bonwick首先在Solaris 2.4中设计并实现了slab分配器,并对其开源。在Linux中也实现了具有相同的基本设计思想的同名分配器slab。

slab、slob和slub关系

slab、slob和slub都是小内存分配器,slab是slob和slub实现的基础,而slob和slub是针对slab在不同场景下的优化版本。在slab引入Linux的很多年内,其都是Linux内核管理对象缓冲区的主流算法。并且由于slab的实现非常复杂,很长一段时间内都少有对它的改动。随着多处理器的发展和NUMA架构的广泛应用,slab的不足也逐渐显现。slab的缓存队列管理复杂,其用于管理的数据结构存储开销大,对NUMA支持复杂,slab着色机制效果不明显。这些不足让slab很难在两种场景下提供最优的性能:小型嵌入式系统和配备有大量物理内存的大规模并行系统。对于小型嵌入式系统来说,slab分配器的代码量和复杂性都太高;对于大规模并行系统,slab用于自身管理的数据结构就需要占用很多G字节内存。针对slab的不足,内核开发人员Christoph Lameter在在内核版本2.6开发期间,引入了新的Slub分配器。Slub简化了slab一些复杂的设计,但保持slab的基本设计思想。同时,一种新的针对小型嵌入式系统的分配器slob也被引入,为了适应嵌入式系统的特点,slob进行了特别的优化,以大幅减少代码量(slob只有大约600行代码)。

slab层在内存管理子系统的层次

slab层可以理解为一个通用层,其包含了slab、slob和slub,至于底层具体使用哪种分配器可以通过配置内核选项进行选择。对于内核的其他模块,则不需要关注底层使用了哪个分配器。因为为了保证内核的其他模块都可以无缝迁移到Slub/slob,所有分配器的接口都是相同的,它们都实现了一组特定的接口用于内存分配。下图为Slab层在内存管理中的层次图:

逻辑上看,slab层位于伙伴系统之上。因为Buddy是最底层的分配器,Slub需要先向Buddy申请内存,而不能越过Buddy获取page。从Buddy申请到内存后,Slub才可以对其进行自己的操作。

slub分配器框架

下图是在读完宋牧春大侠的《图解Slub》后,我也总结了一张Slub分配器框架图,可以大致的看到Slub的框架。Slub的框架如下图(图片很大,可以放大):

这篇文章(原文链接以置文末)中用了一个通俗易懂的例子来介绍Slub的工作原理,我觉的这个例子很恰当,所以这里继续借举一下。

每个数组元素对应一种大小的内存,可以把一个kmem_cache结构体看做是一个特定大小内存的零售商,整个Slub系统中有很多个这样的零售商,每个“零售商”只“零售”特定大小的内存,例如:有的“零售商”只"零售"8Byte大小的内存,有的只”零售“16Byte大小的内存。——引自luken.《linux内核内存管理slub算法(一)原理》

Slub的工作原理和日常生产生活的产销环节很类似,所以为了清晰直观的看到其工作原理,我把这个过程画了一幅图来表示,如下图:

每个零售商(kmem_cache)有两个“部门”,一个是“仓库”:kmem_cache_node,一个“营业厅”:kmem_cache_cpu。“营业厅”里只保留一个slab,只有在营业厅(kmem_cache_cpu)中没有空闲内存的情况下才会从仓库中换出其他的slab。所谓slab就是零售商(kmem_cache)批发的连续的整页内存,零售商把这些整页的内存分成许多小内存,然后分别“零售”出去,一个slab可能包含多个连续的内存页。slab的大小和零售商有关。——引自luken.《linux内核内存管理slub算法(一)原理》

总的来说,Slub就相当于零售商,它从伙伴系统“批发”内存,然后再零售出去。

slub的重要数据结构

  • kmem_cache
struct kmem_cache {
    struct kmem_cache_cpu __percpu *cpu_slab;
    /* Used for retriving partial slabs etc */
    unsigned long flags;
    unsigned long min_partial;
    /* size = object_size + 对象后面下个空闲对象的指针的size */
    int size;           /* The size of an object including meta data */
    int object_size;    /* The size of an object without meta data */
    /* object首地址 + offset = 下一个空闲对象的指针地址 */
    int offset;         /* Free pointer offset. */
    int cpu_partial;    /* Number of per cpu partial objects to keep around */
    /* 
     * oo表示存放最优slab的order和object的数量
     * 低16位表示对象数,高16位表示slab的order
     */

    struct kmem_cache_order_objects oo;
    /* Allocation and freeing of slabs */
    struct kmem_cache_order_objects max;
    /* 
     * 最小slab只需要足够存放一个对象。当设备长时间运行以后,内存碎片化严重,
     * 分配连续物理页很难成功,如果分配最优slab失败,就分配最小slab。
     */

    struct kmem_cache_order_objects min;
    gfp_t allocflags;   /* gfp flags to use on each alloc */
    int refcount;       /* Refcount for slab cache destroy */
    void (*ctor)(void *);
    int inuse;          /* Offset to metadata */
    int align;          /* Alignment */
    // 当slab长度不是对象长度的整数倍的时候,尾部有剩余部分,保存在reserved中
    int reserved;       /* Reserved bytes at the end of slabs */
    const char *name;   /* Name (only for display!) */
    struct list_head list;  /* List of slab caches */
    int red_left_pad;   /* Left redzone padding size */
#ifdef CONFIG_SYSFS
    struct kobject kobj;    /* For sysfs */
#endif
#ifdef CONFIG_MEMCG
    struct memcg_cache_params memcg_params;
    int max_attr_size; /* for propagation, maximum size of a stored attr */
#ifdef CONFIG_SYSFS
    struct kset *memcg_kset;
#endif
#endif
#ifdef CONFIG_NUMA
    /*
     * Defragmentation by allocating from a remote node.
     */

    int remote_node_defrag_ratio;
#endif
#ifdef CONFIG_SLAB_FREELIST_RANDOM
    unsigned int *random_seq;
#endif
#ifdef CONFIG_KASAN
    struct kasan_cache kasan_info;
#endif
    struct kmem_cache_node *node[MAX_NUMNODES]; /* 每个NUMA节点都有一个kmem_cache_node */
};

根据是否打开Slub Debug,next object指针可以有两种方式放置,如果打开了Slub Debug,则采用指针外置式;反之,采用指针内置式。两种指针放置方式如下图:

  • 指针外置式
  • 指针内置式

指针内置式的方法实际上是复用了object的前8个字节,因为在object被分配出去之前,这一段内存具体存放什么内容并不重要,所以可以利用这一段内存来保存下一个free object的地址。

  • kmem_cache_cpu
struct kmem_cache_cpu {
    /* 指向下一个空闲的object,用于快速找到可用对象 */
    void **freelist;    /* Pointer to next available object */
    /*
     * 要保证tid和kmem_cache是由同一个CPU访问。
     * 开启了内核抢占后,访问tid和kmem_cache的CPU可能不是同一个CPU,
     * 所以要检查是否匹配,直到它们是由同一个CPU进行访问 
     */

    unsigned long tid;  /* Globally unique transaction id */
    /* 指向当前使用的slab */
    struct page *page;  /* The slab from which we are allocating */
    /* 指向当前cpu上缓存的部分空闲slab链表 */
    struct page *partial;   /* Partially allocated frozen slabs */
#ifdef CONFIG_SLUB_STATS
    /* 
     * 记录对slab操作的状态变化,这个stat非常重要,
     * 通过这个stat就大概了解object从申请到释放经过了哪些步骤 
     */

    unsigned stat[NR_SLUB_STAT_ITEMS]; 
#endif
};
  • kmem_cache_node
struct kmem_cache_node {
        spinlock_t list_lock;
        /* 此处省略掉SLAB的配置 */  
#ifdef CONFIG_SLUB
        /* 挂入kmem_cache_node中的slab数量 */
        unsigned long nr_partial;
        /* 指向当前内存节点上的部分空闲slab链表 */
        struct list_head partial;
#ifdef CONFIG_SLUB_DEBUG
        atomic_long_t nr_slabs;
        atomic_long_t total_objects;
        struct list_head full;
#endif
#endif
};

page中描述Slub信息的字段:

struct page {
     /* 如果flag设置成PG_slab,表示页属于slub分配器 */
      unsigned long flags;
      union {
        struct address_space *mapping;  
        /* 指向当前slab中第一个object */
        void *s_mem;      /* slab first object */
        atomic_t compound_mapcount;  /* first tail page */
      };
      union {
        pgoff_t index;    /* Our offset within mapping. */
        /* 指向当前slab中第一个空闲的object */
        void *freelist;    /* sl[aou]b first free object */
      };
      union {
        unsigned counters;
        struct {
          union {
            atomic_t _mapcount;
            unsigned int active;    /* SLAB */
            struct {      /* SLUB */
              /* 该slab中已经分配使用的object数量 */
              unsigned inuse:16;
              /* 该slab中的所有object数量 */
              unsigned objects:15;
              /* 
               * 如果slab在kmem_cache_cpu中,表示处于冻结状态;
               * 如果slab在kmem_cache_node的部分空闲slab链表中,表示处于解冻状态
        */

              unsigned frozen:1;
            };
            int units;      /* SLOB */
          };
          atomic_t _refcount;
        };
      };
      union {
        /* 作为链表节点加入到kmem_cache_node的部分空闲slab链表中
        struct list_head lru;  /* Pageout list   */

        struct dev_pagemap *pgmap; 
        struct {    /* slub per cpu partial pages */
          struct page *next;  /* Next partial slab */
          int pages;  /* Nr of partial slabs left */
          int pobjects;  /* Approximate # of objects */
        };
        struct rcu_head rcu_head;
        struct {
          unsigned long compound_head; /* If bit zero is set */
          unsigned int compound_dtor;
          unsigned int compound_order;
        };
      };
      union {
        unsigned long private;
        struct kmem_cache *slab_cache;  /* SL[AU]B: Pointer to slab */
      };
    ......
    }

Slub的分配过程

Slub的分配流程大致如下:首先从kmem_cache_cpu中分配,如果没有则从kmem_cache_cpu的partial链表分配,如果还没有则从kmem_cache_node中分配,如果kmem_cache_node中也没有,则需要向伙伴系统申请内存。

Slub的分配接口是kmem_cache_malloc()。其分配object的流程大概如下:首先在kmem_cache_cpu所使用的slab中查找free object,如果当前slab中有free object,则返回这个object。如果当前slab没有free object,就要看Slub是否开启了kmem_cache_cpu的Partial队列,如果开启了partial队列,就在Partial队列中查看有没有free object的slab,如果有的话就选定这个slab,并返回其free object。如果kmem_cache_cpu的partial链表中也没有拥有free object的slab,则在kmem_cache_node中查找。如果kmem_cache_node中的slab有free object,则选定这个slab并返回free object。如果kmem_cache_node中也没有free object,则需要向伙伴系统申请内存,制作新的slab。

创建slab缓存(kmem_cache)的函数分析

斗胆分析一下slab缓存的创建过程,新手小白分析内核代码,分析的可能不够深度和完整,如有不对还请各路高手指教,提前谢过。

函数调用流程:

kmem_cache_create()
    ——> kmem_cache_create_usercopy()
        ——> create_cache()
            ——> __kmem_cache_create()
                ——> kmem_cache_open()

下面是每个函数的主干分析,代码有精简。

kmem_cache_create():

kmem_cache_create()里继续调用了kmem_cache_create_usercopy()。

kmem_cache_create() {
 return kmem_cache_create_usercopy(name, size, align, flags, 00, ctor);
}

kmem_cache_create_usercopy():

kmem_cache_create_usercopy() {
 struct kmem_cache *s = NULL;
 const char *cache_name;
 
 /*
  * Some allocators will constraint the set of valid flags to a subset
  * of all flags. We expect them to define CACHE_CREATE_MASK in this
  * case, and we'll just provide them with a sanitized version of the
  * passed flags.
  */

 flags &= CACHE_CREATE_MASK;
 
 /* 定义这个缓存的名字,用于在/proc/slabinfo中显示 */
 cache_name = kstrdup_const(name, GFP_KERNEL);
 
 /* kmem_cache结构,并返回其地址 */
 s = create_cache(cache_name, size,
    calculate_alignment(flags, align, size),
    flags, useroffset, usersize, ctor, NULLNULL);
 
 return s;
}

create_cache():

create_cache() {
 struct kmem_cache *s;
 int err;

 /* 为kmem_cache结构申请一段内存并清零 */
 s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);

 /* 初始化kmem_cache结构的部分成员 */
 s->name = name;
 s->size = s->object_size = object_size;
 s->align = align;
 s->ctor = ctor;
 s->useroffset = useroffset;
 s->usersize = usersize;

 /* 核心函数,slub/slab/slob都实现了这个函数 */
 err = __kmem_cache_create(s, flags);
 
 /* 将新创建的kmem_cache加入slab caches链表 */
 list_add(&s->list, &slab_caches);
 
 return s;
    }

__kmem_cache_create():

__kmem_cache_create() {
  int err;

  /* 在kmem_cache_open中处理剩余的结构成员,如min_partial、cpu_partial等 */
  err = kmem_cache_open(s, flags);
 }

kmem_cache_open():

kmem_cache_open() {
 /* 设置kmem_cache中的min_partial,它表示kmem_cache_node中partial链表可挂入的slab数量 */
 set_min_partial(s, ilog2(s->size) / 2);
 
 /* 设置kmem_cache中的cpu_partial,它表示per cpu partial上所有slab中free object总数 */
 set_cpu_partial(s);
 
 /* 为每个节点分配kmem_cache_node */
 if (!init_kmem_cache_nodes(s))
  goto error;

 /* 为kmem_cache_cpu变量创建每CPU副本 */
 if (alloc_kmem_cache_cpus(s))
  return 0;
}

分配对象(object)的函数分析

函数调用流程:

kmem_cache_alloc()
    ——> slab_alloc()
        ——> slab_alloc_node()
           ——> __slab_alloc()
              ——> ___slab_alloc()

kmem_cache_alloc():

kmem_cache_alloc() {
 /* 直接调用slab_alloc */
 void *ret = slab_alloc(s, gfpflags, _RET_IP_);

 return ret;
}

slab_alloc():

slab_alloc() {
 return slab_alloc_node(s, gfpflags, NUMA_NO_NODE, addr);
}

slab_alloc_node():

slab_alloc_node() {
 void *object;  
 struct kmem_cache_cpu *c;
 struct page *page;

redo:
 /*
  * 要保证tid和kmem_cache是由同一个CPU访问。但是如果配置了CONFIG_PREEMPT = y,
  * 即开启了内核抢占后,访问tid和kmem_cache的CPU可能不是同一个CPU,所以要检查
  * 是否匹配,直到它们是由同一个CPU进行访问。
  * 
  * 内核态抢占的时机是:
  * 1.中断处理函数返回内核空间之前会检查请求重新调度的标志(TIF_NEED_RESCHED),
  * 如果置位则调用preempt_schedule_irq()执行抢占。
  * 2. 当内核从non-preemptible(禁止抢占)状态变成preemptible(允许抢占)的时候。
  */

 do {
  tid = this_cpu_read(s->cpu_slab->tid); /* 访问当前CPU的per CPU变量的副本的tid */
  c = raw_cpu_ptr(s->cpu_slab);
 } while (IS_ENABLED(CONFIG_PREEMPT) &&  /* 检查是否开启了内核抢占 */
   unlikely(tid != READ_ONCE(c->tid)));

 barrier(); /* 内存屏障,消除指令乱序执行的影响 */

 object = c->freelist;  /* 下一个free object的地址 */
 page = c->page;   /* 当前使用的slab */
 if (unlikely(!object || !node_match(page, node))) {
  /* 调用核心函数__slab_alloc() */
  object = __slab_alloc(s, gfpflags, node, addr, c);
  stat(s, ALLOC_SLOWPATH);
 } else {
  void *next_object = get_freepointer_safe(s, object);

  if (unlikely(!this_cpu_cmpxchg_double(
    s->cpu_slab->freelist, s->cpu_slab->tid,
    object, tid,
    next_object, next_tid(tid)))) {

   note_cmpxchg_failure("slab_alloc", s, tid);
   goto redo;
  }
  prefetch_freepointer(s, next_object);
  stat(s, ALLOC_FASTPATH);
 }

 maybe_wipe_obj_freeptr(s, object);

 /* 如果gfpflags标志需要对object对象的内存清零 */
 if (unlikely(slab_want_init_on_alloc(gfpflags, s)) && object)
  memset(object, 0, s->object_size);

 slab_post_alloc_hook(s, gfpflags, 1, &object);

 return object;
    }

__slab_alloc():

__slab_alloc() {
 void *p;
 unsigned long flags;
 
 /* 
  * 关中断。关闭当前处理器上的所有中断处理
  *
  * local_irq_save()将当前的中断状态(开或关)
  * 保存在flags中然后再禁用处理器上的中断。
  * 
  * 与local_irq_save不同,local_irq_disable()
  * 不保存状态而关闭本地处理器的中断服务。 
  */

 local_irq_save(flags);  
#ifdef CONFIG_PREEMPT
 /*
  * 在关中断之前,可能已经被抢占并被调度在不同的CPU上,
  * 所以需要重新加载CPU区域的指针。
  */

 c = this_cpu_ptr(s->cpu_slab);
#endif
 /* 调用核心函数___slab_alloc() */
 p = ___slab_alloc(s, gfpflags, node, addr, c);
 
 /*
  * 恢复本地处理器的中断。
  *
  * local_irq_restore()将local_irq_save()保存的状态值(flags)恢复,
  * 注意是恢复之前的中断状态,不一定会开启中断。如果之前的状态是
  * 开中断,就打开中断;如果之前的状态是关中断,就关闭中断。
  * 而local_irq_enable()会无条件开启中断,所以可能会破坏之前的中
  * 断环境。所以local_irq_restore()比local_irq_enable()更安全。
  */

 local_irq_restore(flags); 
 
        return p;
}

slub的frozen(冻结)和unfrozen(解冻)

如果cpu1的kcmem_cache_cpu的slab是frozen, 那么cpu1可以从该slab中取出或放回obj,但是cpu2不能从该slab中取obj, 只能把obj还给该slab。另外,cpu partial上的slab都是frozen状态。node partial上的slab都是unfrozen。耗尽kmem_cache_cpu的slab的obj后解冻slab。

一口Linux 写点代码,写点人生!
评论
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-10 16:13 91浏览
  • 开发板在默认情况下,OpenHarmony系统开机后 30 秒会自动息屏,自动息屏会让不少用户感到麻烦,触觉智能教大家两招轻松取消自动息屏。使用触觉智能Purple Pi OH鸿蒙开发板演示,搭载了瑞芯微RK3566四核处理器,Laval鸿蒙社区推荐开发板,已适配全新OpenHarmony5.0 Release系统,SDK源码全开放!SDK源码中修改修改以下文件参数:base/powermgr/power_manager/services/native/profile/power_mode_co
    Industio_触觉智能 2024-12-09 11:39 112浏览
  • 2024年12月09日 环洋市场咨询机构出版了一份详细的、综合性的调研分析报告【全球电机控制系统芯片 (SoC)行业总体规模、主要厂商及IPO上市调研报告,2024-2030】。本报告研究全球电机控制系统芯片 (SoC)总体规模,包括产量、产值、消费量、主要生产地区、主要生产商及市场份额,同时分析电机控制系统芯片 (SoC)市场主要驱动因素、阻碍因素、市场机遇、挑战、新产品发布等。报告从电机控制系统芯片 (SoC)产品类型细分、应用细分、企业、地区等角度,进行定量和定性分析,包括产量、产值、均价
    GIRtina 2024-12-09 11:32 181浏览
  • 全球知名半导体制造商ROHM Co., Ltd.(以下简称“罗姆”)宣布与Taiwan Semiconductor Manufacturing Company Limited(以下简称“台积公司”)就车载氮化镓功率器件的开发和量产事宜建立战略合作伙伴关系。通过该合作关系,双方将致力于将罗姆的氮化镓器件开发技术与台积公司业界先进的GaN-on-Silicon工艺技术优势结合起来,满足市场对高耐压和高频特性优异的功率元器件日益增长的需求。氮化镓功率器件目前主要被用于AC适配器和服务器电源等消费电子和
    电子资讯报 2024-12-10 17:09 78浏览
  • 我的一台很多年前人家不要了的九十年代SONY台式组合音响,接手时只有CD功能不行了,因为不需要,也就没修,只使用收音机、磁带机和外接信号功能就够了。最近五年在外地,就断电闲置,没使用了。今年9月回到家里,就一个劲儿地忙着收拾家当,忙了一个多月,太多事啦!修了电气,清理了闲置不用了的电器和电子,就是一个劲儿地扔扔扔!几十年的“工匠式”收留收藏,只能断舍离,拆解不过来的了。一天,忽然感觉室内有股臭味,用鼻子的嗅觉功能朝着臭味重的方向寻找,觉得应该就是这台组合音响?怎么会呢?这无机物的东西不会腐臭吧?
    自做自受 2024-12-10 16:34 129浏览
  •         霍尔传感器是根据霍尔效应制作的一种磁场传感器。霍尔效应是磁电效应的一种,这一现象是霍尔(A.H.Hall,1855—1938)于1879年在研究金属的导电机构时发现的。后来发现半导体、导电流体等也有这种效应,而半导体的霍尔效应比金属强得多,利用这现象制成的各种霍尔元件,广泛地应用于工业自动化技术、检测技术及信息处理等方面。霍尔效应是研究半导体材料性能的基本方法。通过霍尔效应实验测定的霍尔系数,能够判断半导体材料的导电类型、载流子浓度及载流子
    锦正茂科技 2024-12-10 11:07 62浏览
  • 肖特基具有很多的应用场景, 可以做同步整流,防止电流倒灌和电源反接等,但是随着电源电流的增大,肖特基导通正向压降0.3~0.7v的劣势也越发明显,产生了很多的热,对于工程师的散热设计是个考验,增加了工程师的设计难度和产品成本,目前一种新的理想二极管及其控制器,目前正在得到越来越广泛的应用- BMS,无人机,PLC,安防,家电,电动工具,汽车等都在快速普及理想二极管有三种架构,内置电荷泵的类似无锡明芯微MX5050T这种,驱动能力会弱点,静态功耗200uA,外置电荷泵MX74700T的这种驱动能力
    王萌 2024-12-10 08:51 85浏览
  • 本文介绍Linux系统(Ubuntu/Debian通用)挂载exfat格式U盘的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。修改对应的内核配置文件# 进入sdk目录cdrk3562_linux# 编辑内核配置文件vi./kernel-5.10/arch/arm64/configs/rockchip_linux_defconfig注:不清楚内核使用哪个defc
    Industio_触觉智能 2024-12-10 09:44 89浏览
  • 概述 通过前面的研究学习,已经可以在CycloneVGX器件中成功实现完整的TDC(或者说完整的TDL,即延时线),测试结果也比较满足,解决了超大BIN尺寸以及大量0尺寸BIN的问题,但是还是存在一些之前系列器件还未遇到的问题,这些问题将在本文中进行详细描述介绍。 在五代Cyclone器件内部系统时钟受限的情况下,意味着大量逻辑资源将被浪费在于实现较大长度的TDL上面。是否可以找到方法可以对此前TDL的长度进行优化呢?本文还将探讨这个问题。TDC前段BIN颗粒堵塞问题分析 将延时链在逻辑中实现后
    coyoo 2024-12-10 13:28 89浏览
  • 在驾驶培训与考试的严谨流程中,EST580驾培驾考系统扮演着至关重要的数据角色。它不仅集成了转速监控、车速管理、转向角度测量、转向灯光控制以及手刹与安全带状态检测等多项功能,还通过高精度的OBD数据采集器实时捕捉车辆运行状态,确保学员在模拟及实际驾驶中的每一步操作都精准无误。EST580驾培驾考转速车速转向角转向灯光手刹安全带OBD数据采集器系统的重要性及其功能:1、提高评判效率:通过原车CAN协议兼容,不同车型通过刷写固件覆盖,不仅提高了考试的数字化、自动化程度,还减少了人为干预的安装需要,从
    lauguo2013 2024-12-09 16:51 101浏览
  • 【萤火工场CEM5826-M11测评】OLED显示雷达数据本文结合之前关于串口打印雷达监测数据的研究,进一步扩展至 OLED 屏幕显示。该项目整体分为两部分: 一、框架显示; 二、数据采集与填充显示。为了减小 MCU 负担,采用 局部刷新 的方案。1. 显示框架所需库函数 Wire.h 、Adafruit_GFX.h 、Adafruit_SSD1306.h . 代码#include #include #include #include "logo_128x64.h"#include "logo_
    无垠的广袤 2024-12-10 14:03 68浏览
  • 智能汽车可替换LED前照灯控制运行的原理涉及多个方面,包括自适应前照灯系统(AFS)的工作原理、传感器的应用、步进电机的控制以及模糊控制策略等。当下时代的智能汽车灯光控制系统通过车载网关控制单元集中控制,表现特殊点的有特斯拉,仅通过前车身控制器,整个系统就包括了灯光旋转开关、车灯变光开关、左LED前照灯总成、右LED前照灯总成、转向柱电子控制单元、CAN数据总线接口、组合仪表控制单元、车载网关控制单元等器件。变光开关、转向开关和辅助操作系统一般连为一体,开关之间通过内部线束和转向柱装置连接为多,
    lauguo2013 2024-12-10 15:53 65浏览
  •         在有电流流过的导线周围会感生出磁场,再用霍尔器件检测由电流感生的磁场,即可测出产生这个磁场的电流的量值。由此就可以构成霍尔电流、电压传感器。因为霍尔器件的输出电压与加在它上面的磁感应强度以及流过其中的工作电流的乘积成比例,是一个具有乘法器功能的器件,并且可与各种逻辑电路直接接口,还可以直接驱动各种性质的负载。因为霍尔器件的应用原理简单,信号处理方便,器件本身又具有一系列的du特优点,所以在变频器中也发挥了非常重要的作用。  &nb
    锦正茂科技 2024-12-10 12:57 69浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦