《Linux内核深度解析》选载之引导内存分配器

原创 Linux阅码场 2022-07-22 11:00

Ftrace训练营火热报名中:Ftrace训练营:站在设计者的角度来理解ftrace(限50人)。训练营第一期报名已圆满成功,好评如潮。第二期课程正在进行中,第三期报名正在火爆进行中(咨询小月微信:linuxer2016)。


ARM安全架构训练营2期火热报名中:阅码场训练营:ARM安全架构之Trustzone/TEE实战--【介绍视频】。报名咨询客服(小月微信:linuxer2016)。


ARM架构与调优调试训练营火热报名中:阅码场训练营:ARM架构与调试调优。报名咨询客服(小月微信:linuxer2016)。



阅码场用户程磊对《Linux内核深度解析》推荐如下:

1.语言浅显易懂,内容深入浅出。

2.逻辑清晰,条理分明,逐步深入,层层递进。

3.基于较新的4.12内核版本,很多经典内核书籍虽然写的都非常好,但是都是基于2.6内核,很多在2.6之后引入的新技术并没有讲到,而本书对这些新技术都有非常详细的讲解。




作者简介:

余华兵,2005年毕业于华中科技大学计算机学院,取得硕士学位。毕业后的十余年一直在网络通信行业从事软件设计和开发工作,研究方向包括IPv4协议栈、IPv6协议栈和Linux内核。


目录

3.6 引导内存分配器 

3.6.1 bootmem分配器 
3.6.2 memblock分配器 
3.6.3 物理内存信息 


3.6 引导内存分配器


在内核初始化的过程中需要分配内存,内核提供了临时的引导内存分配器,在页分配器和块分配器初始化完毕后,把空闲的物理页交给页分配器管理,丢弃引导内存分配器。


早期使用的引导内存分配器是 bootmem,目前正在使用 memblock 取代 bootmem。如果开启配置宏 CONFIG_NO_BOOTMEMmemblock 就会取代 bootmem。为了保证兼容性,bootmem memblock 提供了相同的接口。

3.6.1 bootmem 分配器


bootmem 分配器使用的数据结构如下:


include/linux/bootmem.h typedef struct bootmem_data {  unsigned long node_min_pfn;  unsigned long node_low_pfn;  void *node_bootmem_map;  unsigned long last_end_off;  unsigned long hint_idx;  struct list_head list; } bootmem_data_t;

下面解释结构体 bootmem_data 的成员。


1node_min_pfn 是起始物理页号。


2node_low_pfn 是结束物理页号。


3node_bootmem_map 指向一个位图,每个物理页对应一位,如果物理页被分配,把对应的位设置为 1


4last_end_off 是上次分配的内存块的结束位置后面一个字节的偏移。


5hint_idx 的字面意思是“暗示的索引”,是上次分配的内存块的结束位置后面的物理页在位图中的索引,下次优先考虑从这个物理页开始分配。


每个内存节点有一个 bootmem_data 实例:

include/linux/mmzone.h typedef struct pglist_data { #ifndef CONFIG_NO_BOOTMEM  struct bootmem_data *bdata; #endif } pg_data_t;


bootmem 分配器的算法如下。


1)只把低端内存添加到 bootmem 分配器,低端内存是可以直接映射到内核虚拟地址空间的物理内存。


2)使用一个位图记录哪些物理页被分配,如果物理页被分配,把这个物理页对应的位设置成 1


3)采用最先适配算法,扫描位图,找到第一个足够大的空闲内存块。


4)为了支持分配小于一页的内存块,记录上次分配的内存块的结束位置后面一个字节的偏移和后面一页的索引,下次分配时,从上次分配的位置后面开始尝试。如果上次分配的最后一个物理页的剩余空间足够,可以直接在这个物理页上分配内存。


bootmem 分配器对外提供的分配内存的函数是 alloc_bootmem 及其变体,释放内存的函数是 free_bootmem。分配内存的核心函数是源文件“mm/bootmem.c”中的函数 alloc_bootmem_bdata

ARM64 架构的内核已经不使用 bootmem 分配器,但是其他处理器架构还在使用 bootmem分配器。


3.6.2 memblock 分配器


1.数据结构


memblock 分配器使用的数据结构如下:


include/linux/memblock.h struct memblock {  bool bottom_up; /* 是从下向上的方向?*/  phys_addr_t current_limit;  struct memblock_type memory;  struct memblock_type reserved; #ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP  struct memblock_type physmem; #endif };

成员 bottom_up 表示分配内存的方式,值为真表示从低地址向上分配,值为假表示从高地址向下分配。


成员 current_limit 是可分配内存的最大物理地址。


接下来是 3 种内存块:memory 是内存类型(包括已分配的内存和未分配的内存),reserved 是预留类型(已分配的内存),physmem 是物理内存类型。物理内存类型和内存类型的区别是:内存类型是物理内存类型的子集,在引导内核时可以使用内核参数“mem=nn[KMG]指定可用内存的大小,导致内核不能看见所有内存;物理内存类型总是包含所有内存范围,内存类型只包含内核参数“mem=”指定的可用内存范围。

内存块类型的数据结构如下:


include/linux/memblock.h struct memblock_type {  unsigned long cnt; /* 区域数量 */  unsigned long max; /* 已分配数组的大小 */  phys_addr_t total_size; /* 所有区域的长度 */  struct memblock_region *regions;  char *name; };

内存块类型使用数组存放内存块区域,成员 regions 指向内存块区域数组,cnt 是内存块区域的数量,max 是数组的元素个数,total_size 是所有内存块区域的总长度,name 是内存块类型的名称。


内存块区域的数据结构如下:


include/linux/memblock.h struct memblock_region {  phys_addr_t base;  phys_addr_t size;  unsigned long flags; #ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP  int nid; #endif }; /* memblock标志位的定义. */ enum {  MEMBLOCK_NONE = 0x0, /* 无特殊要求 */  MEMBLOCK_HOTPLUG = 0x1, /* 可热插拔区域 */  MEMBLOCK_MIRROR = 0x2, /* 镜像区域 */  MEMBLOCK_NOMAP = 0x4, /* 不添加到内核直接映射 */ };

成员 base 是起始物理地址,size 是长度,nid 是节点编号。成员 flags 是标志,可以是MEMBLOCK_NONE 或其他标志的组合。

1MEMBLOCK_NONE 表示没有特殊要求的区域。


2MEMBLOCK_HOTPLUG 表示可以热插拔的区域,即在系统运行过程中可以拔出或插入物理内存。


3MEMBLOCK_MIRROR 表示镜像的区域。内存镜像是内存冗余技术的一种,工作原理与硬盘的热备份类似,将内存数据做两个复制,分别放在主内存和镜像内存中。


4MEMBLOCK_NOMAP 表示不添加到内核直接映射区域(即线性映射区域)。


2.初始化


源文件“mm/memblock.c”定义了全局变量 memblock,把成员 bottom_up 初始化为假,表示从高地址向下分配。


ARM64 内核初始化 memblock 分配器的过程是:


1)解析设备树二进制文件中的节点“/memory”,把所有物理内存范围添加到 memblock.memory,具体过程参考 3.6.3 节。

2)在函数 arm64_memblock_init 中初始化 memblock


函数 arm64_memblock_init 的主要代码如下:


start_kernel() ->setup_arch() -> arm64_memblock_init() arch/arm64/mm/init.c1 void __init arm64_memblock_init(void) 2 { 3 const s64 linear_region_size = -(s64)PAGE_OFFSET; 4 5 fdt_enforce_memory_region(); 6 7 memstart_addr = round_down(memblock_start_of_DRAM(), 8 ARM64_MEMSTART_ALIGN); 146 3.6 引导内存分配器9 10 memblock_remove(max_t(u64, memstart_addr + linear_region_size, 11 __pa_symbol(_end)), ULLONG_MAX); 12 if (memstart_addr + linear_region_size < memblock_end_of_DRAM()) { 13 /* 确保memstart_addr严格对齐 */ 14 memstart_addr = round_up(memblock_end_of_DRAM() - linear_region_size, 15 ARM64_MEMSTART_ALIGN); 16 memblock_remove(0, memstart_addr); 17 } 18 19 if (memory_limit != (phys_addr_t)ULLONG_MAX) { 20 memblock_mem_limit_remove_map(memory_limit); 21 memblock_add(__pa_symbol(_text), (u64)(_end - _text)); 22 } 23 24 25 memblock_reserve(__pa_symbol(_text), _end - _text); 26 27 28 early_init_fdt_scan_reserved_mem(); 29 30 }

5 行代码,调用函数 fdt_enforce_memory_region 解析设备树二进制文件中节点“/chosen的属性“linux,usable-memory-range”,得到可用内存的范围,把超出这个范围的物理内存范围从 memblock.memory 中删除。

7 行和第 8 行代码,全局变量 memstart_addr 记录内存的起始物理地址。


1017 行代码,把线性映射区域不能覆盖的物理内存范围从 memblock.memory 中删除。


1922 行代码,设备树二进制文件中节点“/chosen”的属性“bootargs”指定的命令行中,可以使用参数“mem”指定可用内存的大小。如果指定了内存的大小,那么把超过可用长度的物理内存范围从 memblock.memory 中删除。因为内核镜像可以被加载到内存的高地址部分,并且内核镜像必须是可以通过线性映射区域访问的,所以需要把内核镜像占用的物理内存范围重新添加到 memblock.memory 中。

25 行代码,把内核镜像占用的物理内存范围添加到 memblock.reserved 中。


28 行代码,从设备树二进制文件中的内存保留区域(memory reserve map,对应设备树源文件的字段“/memreserve/”)和节点“/reserved-memory”读取保留的物理内存范围,添加到 memblock.reserved 中。

3.编程接口


memblock 分配器对外提供的接口如下。


1memblock_add:添加新的内存块区域到 memblock.memory 中。

2memblock_remove:删除内存块区域。

3memblock_alloc:分配内存。

4memblock_free:释放内存。


为了兼容 bootmem 分配器,memblock 分配器也实现了 bootmem 分配器提供的接口。如果开启配置宏 CONFIG_NO_BOOTMEMmemblock 分配器就完全替代了 bootmem 分配器。

4.算法


memblock 分配器把所有内存添加到 memblock.memory 中,把分配出去的内存块添加memblock.reserved 中。内存块类型中的内存块区域数组按起始物理地址从小到大排序。

函数 memblock_alloc 负责分配内存,把主要工作委托给函数 memblock_alloc_range_nid算法如下。


1)调用函数 memblock_find_in_range_node 以找到没有分配的内存块区域,默认从高地址向下分配。


函数 memblock_find_in_range_node 有两层循环,外层循环从高到低遍历 memblock.memory的内存块区域数组;针对每个内存块区域 M1,执行内层循环,从高到低遍历 memblock.reserved的内存块区域数组。针对每个内存块区域 M2,目标区域是内存块区域 M2 和前一个内存块区域之间的区域,如果目标区域属于内存块区域 M1,并且长度大于或等于请求分配的长度,那么可以从目标区域分配内存。


2)调用函数 memblock_reserve,把分配出去的内存块区域添加到 memblock.reserved 中。


函数 memblock_free 负责释放内存,只需要把内存块区域从 memblock.reserved 中删除。


3.6.3 物理内存信息


在内核初始化的过程中,引导内存分配器负责分配内存,问题是:引导内存分配器怎么知道内存的大小和物理地址范围?


ARM64 架构使用扁平设备树(Flattened Device TreeFDT)描述板卡的硬件信息,好处是可以把板卡特定的代码从内核中删除,编译生成通用的板卡无关的内核。驱动开发者编写设备树源文件(Device Tree SourceDTS),存放在目录“arch/arm64/boot/dts”下,然后使用设备树编译器(Device Tree CompilerDTC)把设备树源文件转换成设备树二进制文件(Device Tree BlobDTB),接着把设备树二进制文件写到存储设备上。设备启动时,引导程序把设备树二进制文件从存储设备读到内存中,引导内核的时候把设备树二进制文件的起始地址传给内核,内核解析设备树二进制文件后得到硬件信息。


设备树源文件是文本文件,扩展名是“.dts”,描述物理内存布局的方法如下:


/ {  #address-cells = <2>;  #size-cells = <2>;  memory@80000000 {  device_type = "memory";  reg = <0x00000000 0x80000000 0 0x80000000>,  <0x00000008 0x80000000 0 0x80000000>;  }; };


/”是根节点。


属性“#address-cells”定义一个地址的单元数量,属性“#size-cells”定义一个长度的单元数量。单元(cell)是一个 32 位数值,属性“#address-cells = <2>”表示一个地址由两个单元组成,即地址是一个 64 位数值;属性“#size-cells = <2>”表示一个长度由两个单元组成,即长度是一个 64 位数值。

memory”节点描述物理内存布局,“@”后面的设备地址用来区分名字相同的节点,如果节点有属性“reg”,那么设备地址必须是属性“reg”的第一个地址。如果有多块内存,可以使用多个“memory”节点来描述,也可以使用一个“memory”节点的属性“reg”的地址/长度列表来描述。

属性“device_type”定义设备类型,“memory”节点的属性“device_type”的值必须是memory”。

属性“reg”定义物理内存范围,值是一个地址/长度列表,每个地址包含的单元数量是由根节点的属性“#address-cells”定义的,每个长度包含的单元数量是由根节点的属性#size-cells”定义的。在上面的例子中,第一个物理内存范围的起始地址是“0x00000000 0x80000000”,长度是“0 0x80000000”,即起始地址是 2GB,长度是 2GB;第二个物理内存范围的起始地址是“0x00000008 0x80000000”,长度是“0 0x80000000”,即起始地址是34GB,长度是 2GB

内核在初始化的时候调用函数 early_init_dt_scan_nodes 以解析设备树二进制文件,从而得到物理内存信息。


start_kernel() ->setup_arch() ->setup_machine_fdt() ->early_init_dt_scan() ->early_init_dt_scan_nodes() drivers/of/fdt.c1 void __init early_init_dt_scan_nodes(void) 2 { 3 4 /* 初始化size-cells和address-cells信息 */ 5 of_scan_flat_dt(early_init_dt_scan_root, NULL); 6 7 /* 调用函数early_init_dt_add_memory_arch设置内存 */ 8 of_scan_flat_dt(early_init_dt_scan_memory, NULL); 9 }

5 行代码,调用函数 early_init_dt_scan_root,解析根节点的属性“#address-cells”得到地址的单元数量,保存在全局变量 dt_root_addr_cells 中;解析根节点的属性“#size-cells得到长度的单元数量,保存在全局变量 dt_root_size_cells 中。

8 行代码,调用函数 early_init_dt_scan_memory,解析“memory”节点得到物理内存布局。


函数 early_init_dt_scan_memory 负责解析“memory”节点,其主要代码如下:


drivers/of/fdt.c1 int __init early_init_dt_scan_memory(unsigned long node, const char *uname, 2 int depth, void *data) 3 { 4 const char *type = of_get_flat_dt_prop(node, "device_type", NULL); 5 const __be32 *reg, *endp; 6 int l; 7 8 9 /* 只扫描 "memory" 节点 */ 10 if (type == NULL) { 11 /* 如果没有属性“device_type”,判断节点名称是不是“memory@0”*/ 12 if (!IS_ENABLED(CONFIG_PPC32) || depth != 1 || strcmp(uname, "memory@0") != 0) 13 return 0; 14 } else if (strcmp(type, "memory") != 0) 15 return 0; 1617 reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l); 18 if (reg == NULL) 19 reg = of_get_flat_dt_prop(node, "reg", &l); 20 if (reg == NULL) 21 return 0; 22 23 endp = reg + (l / sizeof(__be32)); 24 25 26 while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) { 27 u64 base, size; 28 29 base = dt_mem_next_cell(dt_root_addr_cells, ®); 30 size = dt_mem_next_cell(dt_root_size_cells, ®); 31 32 if (size == 0) 33 continue; 34 35 early_init_dt_add_memory_arch(base, size); 36 37 } 38 39 return 0; 40 }

4 行代码,解析节点的属性“device_type”。


14 行代码,如果属性“device_type”的值是“memory”,说明这个节点描述物理内存信息。


1719 行代码,解析属性“linux,usable-memory”,如果不存在,那么解析属性“reg”。这两个属性都用来定义物理内存范围。


2637 行代码,解析出每块内存的起始地址和大小后,调用函数 early_init_dt_add_memory_arch

函数 early_init_dt_add_memory_arch 的主要代码如下:

drivers/of/fdt.cvoid __init __weak early_init_dt_add_memory_arch(u64 base, u64 size) {  const u64 phys_offset = MIN_MEMBLOCK_ADDR;  if (!PAGE_ALIGNED(base)) {  if (size < PAGE_SIZE - (base & ~PAGE_MASK)) {  pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",  base, base + size);  return;  }  size -= PAGE_SIZE - (base & ~PAGE_MASK);  base = PAGE_ALIGN(base);  }  size &= PAGE_MASK;  if (base > MAX_MEMBLOCK_ADDR) {  pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",  base, base + size);  return;  }  if (base + size - 1 > MAX_MEMBLOCK_ADDR) {  pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",  ((u64)MAX_MEMBLOCK_ADDR) + 1base + size);  size = MAX_MEMBLOCK_ADDR - base + 1;  }  if (base + size < phys_offset) {  pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",  base, base + size);  return;  }  if (base < phys_offset) {  pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",  base, phys_offset);  size -= phys_offset - base;  base = phys_offset;  } memblock_add(base, size); }

函数 early_init_dt_add_memory_arch 对起始地址和长度做了检查以后,调用函数memblock_add 把物理内存范围添加到 memblock.memory 中。


感兴趣想进群者可以添加小月微信(linuxer2016)邀请进群一起交流学习




连载已发布文章:

《Linux内核深度解析》选载之物理内存组织

《Linux内核深度解析》选载之内存映射

《Linux内核深度解析》选载之内存地址空间


精华文章:【精华】Linux阅码场原创精华文章汇总



阅码场付费会员专业交流群

会员招募:各专业群会员费为88元/季度,权益包含群内提问,线下活动8折,全年不定期群技术分享(普通用户直播免费,分享后每次点播价为19元/次),有意加入请私信客服小月(小月微信号:linuxer2016)


专业群介绍:

彭伟林-阅码场内核性能与稳定性
本群定位内核性能与稳定性技术交流,覆盖云/网/车/机/芯领域资深内核专家,由阅码场资深讲师彭伟林主持。


甄建勇-性能优化与体系结构

本群定位Perf、cache和CPU架构技术交流,覆盖云/网/车/机/芯领域资深用户,由阅码场资深讲师甄建勇主持。


李春良-Xenomai与实时优化

本群定位Xenomai与实时优化技术交流,覆盖云/网/车/机/芯领域资深用户,由阅码场资深讲师李春良和彭伟林共同主持。


周贺贺-Tee和ARM架构

本群定位Tee和ARM架构技术交流,覆盖云/网/车/机/芯领域资深用户,由阅码场资深讲师周贺贺主持。


谢欢-Linux tracers

本群定位Linux tracers技术交流,覆盖云/网/车/机/芯领域资深用户,由阅码场资深讲师谢欢主持。







Linux阅码场 专业的Linux技术社区和Linux操作系统学习平台,内容涉及Linux内核,Linux内存管理,Linux进程管理,Linux文件系统和IO,Linux性能调优,Linux设备驱动以及Linux虚拟化和云计算等各方各面.
评论
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 53浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 91浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 93浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 116浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 118浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 86浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 66浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 59浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 107浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 75浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 97浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 88浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦