【嵌入式八股6】内存管理

内存模型:data、bss、heap、stack

在嵌入式系统中,内存的管理和组织对于系统的性能和稳定性至关重要。内存主要分为 Flash 和 RAM 两大部分,它们各自存储着不同类型的数据,并且系统中还存在着代码区、全局区、堆区和栈区这四个重要的内存区域。

1、Flash 和 RAM 的数据存储

  1. Flash:Flash 存储器用于存储程序代码和一些只读数据,其存储内容可以表示为:Flash = Code + RO-data + RW-data。
    • Code:即编译后的机器码,是程序执行的指令集合。
    • RO-data:Read Only data,只读数据,例如常量等。
    • RW-data:Read Write data,可读写数据,这些数据在程序启动时需要从 Flash 搬运到 RAM 中。
  2. RAM:随机存取存储器,用于程序运行时的数据存储,其存储内容为:RAM = RW-data + ZI-data。
    • RW-data:已初始化的可读写数据,如静态变量和全局变量中已初始化的部分。
    • ZI-data:Zero Initialized data,未初始化的全局变量和静态变量,在系统启动时会自动初始化为 0。

2、内存四区

内存被划分为四个主要区域,分别是代码区、全局区、堆区和栈区,以下是各区域的详细介绍:

地址范围 区域名称 区域内容 存放位置 举例说明
低地址
(示例:0x0000 开始)
.text 代码段 编译后的机器码,包含程序的指令和操作逻辑 Flash #define ro_def 0x11111111UL(虽然宏定义在预处理阶段处理,但类似常量相关的逻辑与代码段相关),这里的代码段主要是实际执行的机器指令,宏定义参与指令的生成等
.ROdata 只读数据段 只读常量,如 const 修饰的常量,这些数据在程序运行过程中不会被修改 Flash const uint32_t ro_var = 0x22222222;,该变量的值在程序运行期间保持不变
.RWdata 已初始化数据段 静态变量、全局变量中已初始化的部分,在系统启动时,这些数据会从 Flash 读取并搬运到 RAM 中 RAM int global_var = 123;,这是一个已初始化的全局变量;static int c = 0;,这是一个已初始化的静态变量
.bss 未初始化数据段 全局变量和静态变量中未初始化的部分,在系统启动时,这些变量会被自动初始化为 0 RAM int global_var;,该全局变量未进行初始化,会在启动时被置为 0
.heap 堆区 用于动态内存分配,由程序员手动调用内存分配函数(如 malloc 等)来开辟内存空间,并在使用完毕后手动调用释放函数(如 free 等)来释放内存。堆区的内存增长方向是从低地址向高地址(向下增长) RAM 例如使用 int* ptr = (int*)malloc(sizeof(int)); 动态分配内存,使用完后需 free(ptr); 释放内存
高地址
(示例:0xFFFF 结束)
.stack 栈区 用于存储函数的局部变量、函数参数、返回地址等,由编译器自动进行内存的开辟和释放。栈区的内存增长方向是从高地址向低地址(向上增长) RAM 在函数内部定义的变量,如 void func() { int local_var = 10; }local_var 存储在栈区

3、初始化过程

在嵌入式系统启动时,内存的初始化过程如下:

  1. 数据最初存储在 ROM(通常是 Flash)中,其中包括 RO DATA(只读常量数据)、text(代码段)、RW DATA(已初始化的可读写数据,先存储在 Flash 中)。
  2. 系统上电后,RAM 会加载来自于 ROM 的 RW DATA,将这些已初始化的数据搬运到 RAM 中对应的位置。
  3. 随后,依据启动文件的配置,系统会自动将 ZI DATA(未初始化的全局变量和静态变量)初始化为 0,使得这些变量在程序开始执行前处于确定的初始状态。

以下是为你润色丰富内容、按照逻辑排序和优化排版后的内容,使得各部分之间的逻辑更加清晰,表述更加详细准确:

数组下标越界问题

在 C 语言中,数组下标越界是一个常见且需要注意的问题。以下面的代码为例:

int arr[5];

arr[-1];  // 可能可以正常执行
arr[5];  // 一定报错

对于上述代码,数组 arr 被定义为可以容纳 5 个 int 类型元素的数组,其合法的下标范围是从 04

当访问 arr[-1] 时,之所以说可能可以正常执行,是因为函数栈的增长方向为从高地址到低地址。在栈的内存布局中,高地址处存放着函数的返回信息以及比数组先存入的其他信息,并且数组的存储顺序是下标小的元素在低地址。因此,当往低地址越界(如 arr[-1])时,修改的可能是空的未使用的栈空间,所以在某些情况下可能不会立即引发错误或异常。

而访问 arr[5] 时一定会报错,这是因为 arr[5] 已经超出了数组 arr 所分配的内存空间,它会访问到不属于该数组的内存区域,这很可能会导致程序崩溃或产生不可预期的结果。

为了避免数组下标越界问题,可以利用 assert 断言和迭代器来进行检查和处理。assert 可以在程序运行时检查条件是否满足,如果不满足则会触发程序中断并输出错误信息,帮助开发者快速定位问题;而迭代器则可以在遍历数组时,更安全地控制访问的范围,避免越界情况的发生。

MCU 采用 XIP(eXecute In Place)方式运行程序

MCU(微控制单元)常常采用 XIP(eXecute In Place)的方式在 Flash 中直接运行程序,而不是将程序搬运到 RAM 中。这种方式具有以下显著优势:

  1. 节省内存空间:MCU 通常内存容量有限,尤其是 RAM 的容量相对较小。采用 XIP 方式运行程序,可以避免将程序完整地复制到 RAM 中,从而节省了宝贵的 RAM 空间。这样,就可以将 RAM 资源用于存储那些需要快速存取的数据,比如程序运行过程中的临时变量、缓存数据等,提高了系统的整体性能。
  2. 成本优势:从成本角度来看,RAM 的价格往往比 Flash 更高。在 MCU 系统中,Flash 通常是固化在芯片内部的,而 RAM 则可能需要额外的外部芯片或部件来支持,这不仅增加了系统的成本,还增加了系统的复杂性。因此,将程序直接运行在 Flash 中,可以有效降低系统成本,使产品在市场上更具竞争力。
  3. 提高读取速度:Flash 存储器一般具有较快的访问速度,对于微控制器而言,其访问速度在执行程序时往往已经足够快。在 XIP 模式下,程序无需从 Flash 复制到 RAM,避免了复制过程中所耗费的时间,能够直接在 Flash 中运行,从而加快了程序的启动时间和响应速度,提升了用户体验。
  4. 适用于嵌入式系统:MCU 常常被嵌入在一些资源受限、对功耗要求较低的嵌入式系统中。使用 XIP 方式可以减少对外部 RAM 的需求,降低系统的整体功耗。同时,由于减少了数据搬运等操作,也提高了系统整体的稳定性和可靠性,更适合嵌入式系统的应用场景。

然而,尽管 XIP 具有诸多优势,但它也存在一些限制和需要考虑的因素。例如,Flash 的访问延迟相对较高,在一些对实时性要求极高的场景中可能不太适用;而且 Flash 不适用于频繁写操作的场景,因为 Flash 的擦写次数是有限的,频繁的写操作可能会缩短其使用寿命。因此,在设计 MCU 系统时,需要综合考虑具体的应用场景和需求,选择合适的存储方案。

Linux 栈的大小相关问题

在 Linux 系统中,栈的大小是一个重要的参数,它可以在编译内核时进行配置,并且能够根据系统的实际需求进行灵活调整。栈的大小决定了每个线程所拥有的可用栈空间大小。

在大多数常见的 Linux 系统上,默认的栈大小为 8MB。但需要注意的是,这个默认值并不是固定不变的。用户可以通过修改内核参数或使用特定的命令来改变栈的大小。例如,在某些情况下,如果程序需要更大的栈空间来处理复杂的递归调用或存储大量的局部变量,就可以适当增大栈的大小;反之,如果系统资源有限,也可以减小栈的大小以节省内存。

栈的生长方向问题

栈的生长方向,具体指的是入栈操作时栈空间的扩展方向。当栈从高地址向低地址生长时,我们称之为向下生长,也叫逆向生长。以 STM32 为例,其栈就是向下生长的。

当程序执行过程中需要分配新的栈帧时,栈指针(通常是 SP 寄存器)会向较低的内存地址方向移动,从而为新的栈帧分配足够的空间来存储局部变量、函数参数等信息。而当某个栈帧不再被需要时,比如函数执行完毕,栈指针会向较高的内存地址方向移动,释放该栈帧所占用的内存空间,以便后续的栈操作可以使用这些空间。这种从高地址向低地址生长的方式,是由处理器的架构和操作系统的设计共同决定的,并且在大多数常见的计算机系统中被广泛采用。

操作系统对内存管理的作用

  • 内存分配与回收
  • 采用虚拟内存进行扩容
  • 负责逻辑地址到物理地址的转换
  • 实现内存保护与隔离(应用间、内核隔离)

分页管理

定义:将内存分为大小相等的页框、进程也分为页框,OS将进程的页框一一对应放入内存

在进程控制块PCB中存放页表,记录了进程页号和内存块号之间的对应关系 alt

在进程控制块PCB中存放页表,记录了进程页号和内存块号之间的对应关系

alt

逻辑地址到物理地址的转换

  • 依据逻辑地址,整除页面大小得到页号,余数为页内偏移量
  • 判断越界
  • 通过PCB中保存的页表查询该页存放在哪一块内存(逻辑内存地址)
  • 通过逻辑内存地址计算实际物理内存地址

alt

缺页中断

为了使得页表不用常驻内存,将页表分为2级管理,1级页表存储页表索引,2级页表存储内存逻辑地址

当某些页面不在内存中但被访问到时发生缺页中断

alt

虚拟内存

虚拟内存是现代操作系统中一项重要的内存管理技术,它允许进程使用比实际物理内存更大的内存空间。其核心原理是将即将使用的数据装入内存,当内存空间已满时,将暂时不用的数据换出到磁盘存储(如硬盘上的 swap 区域)。

  1. 突破物理内存限制:虚拟内存使得进程能够运行内存需求超过物理内存大小的程序。这是因为程序运行具有局部性原理,即 CPU 访问内存时存在明显的重复访问倾向。对于那些不常被使用的内存区域,操作系统可以将其换出到物理内存之外,如硬盘上的 swap 区域。当后续需要使用这些数据时,再将其从磁盘换回内存,从而在逻辑上实现了比物理内存更大的内存空间。
  2. 进程内存空间隔离:每个进程都拥有自己独立的页表,页表用于实现虚拟地址到物理地址的映射。由于页表的私有性,进程无法访问其他进程的页表,这就有效地解决了多进程之间的地址冲突问题。每个进程都感觉自己拥有独立的、连续的内存空间,增强了系统的稳定性和安全性。
  3. 内存访问控制与安全:页表中的页表项除了包含物理地址外,还包含一些标记属性的比特位。例如,这些比特位可以用于控制一个页的读写权限,标记该页是否存在于内存中等等。通过这些控制位,操作系统能够对内存访问进行更精细的管理,提供了更好的安全性,防止非法的内存访问操作。

Nor Flash 和 Nand Flash

Nor Flash 和 Nand Flash 是两种常见的闪存存储器,它们在功能和应用上存在一些差异:

  1. Nor Flash:Nor Flash 不仅可以存储数据,还支持取指运行(XIR)功能。也就是说,当 MCU(微控制单元)给出地址时,Nor Flash 可以直接返回指令交给 MCU 去执行,无需将指令先拷贝到 RAM(随机存取存储器)中再执行。这种特性使得 Nor Flash 适合用于存储程序代码,并且能够直接在其中运行简单的程序,减少了数据搬运的开销。
  2. Nand Flash:Nand Flash 主要用于数据存储。在取值时,需要先将数据从 Nand Flash 搬运到 RAM 中,然后才能被 CPU 访问和处理。Nand Flash 具有较高的存储密度和较低的成本,因此常用于大容量的数据存储,如固态硬盘(SSD)等设备中。

堆和栈的区别

堆和栈是程序运行时内存中的两个重要区域,它们在多个方面存在明显的区别:

  1. 申请方式
    • 栈(stack):栈内存的分配与回收由系统自动完成。栈内存的分配运算内置于处理器的指令集中,当函数调用时,系统会自动为函数的局部变量、参数等分配栈空间;函数返回时,系统会自动释放这些栈空间。
    • 堆(heap):堆内存由程序员手动申请和释放。程序员需要使用特定的内存分配函数(如 C 语言中的 malloc 函数,C++ 中的 new 操作符)来申请堆内存,并在使用完毕后使用相应的释放函数(如 free 函数、delete 操作符)来释放内存,以避免内存泄漏。
  2. 存储位置与方向
    • :栈的内存地址从高地址向低地址方向生长。当有新的函数调用或局部变量需要存储时,栈指针(SP)会向低地址方向移动,为新的栈帧分配空间。
    • :堆的内存地址从低地址向高地址方向生长。当程序员申请堆内存时,堆管理器会从低地址的空闲内存区域中分配一块合适大小的内存空间,并返回该空间的起始地址。
  3. 碎片问题
    • :栈采用先进先出(FIFO)的方式进行内存管理,不存在内存碎片问题。因为栈空间的分配和释放是按照函数调用的顺序依次进行的,不会出现内存空间被分割成不连续小块的情况。
    • :堆内存管理可能会产生内存碎片,包括内部碎片和外部碎片。内部碎片是指已经分配给进程但由于分配策略等原因无法被充分利用的内存空间;外部碎片是指系统中存在的未被分配的空闲内存区域,但由于这些区域太小,无法满足新的内存分配请求。
  4. 存放内容
    • :栈主要存放函数的返回地址、局部变量的值、函数参数等。在函数调用过程中,这些信息会按照一定的顺序被压入栈中,函数返回时再按照相反的顺序被弹出。
    • :堆主要用于存放用户定义的数据,如动态分配的数组、对象等。程序员可以根据实际需求在堆中分配任意大小的内存空间,并在其中存储和操作数据。

需要注意的是,栈的内存分配和释放由系统自动完成,而不是通过 malloc 函数实现(malloc 用于堆内存分配);堆内存的动态分配在 C 语言中常用 malloc 函数,在 C++ 中常用 new 操作符,并且都需要程序员手动释放(freedelete)。

内存碎片

内存碎片是指在内存管理过程中,由于内存分配和释放的操作,导致内存空间出现不连续或无法被有效利用的情况。内存碎片主要分为内碎片和外碎片两种类型:

  1. 外碎片:外碎片是指那些还没有被分配出去(不属于任何进程)的内存空闲区域,但由于这些空闲区域的大小太小,无法满足新的内存分配请求。例如,系统中存在多个小的空闲内存块,但这些块的大小都不足以分配给一个新的进程,从而导致这些空闲内存无法被有效利用。
  2. 内碎片:内碎片是指已经被分配出去(能够明确指出属于哪个进程)的内存空间,但由于分配策略的原因,这些内存空间不能被进程充分利用。例如,当系统按照固定大小的块来分配内存时,如果进程申请的内存大小小于块的大小,那么剩余的部分就成为了内碎片。

内存碎片产生的主要原因是在进行多次内存分配和释放操作后,剩余的可用内存空间被分割成了许多不连续的小块,这些小块之间相互孤立,无法合并成较大的连续内存块,从而导致内存空间的利用率降低。

内存对齐

内存对齐是指在内存中对数据进行存储时,按照一定的规则将数据存储在特定的地址上,以满足硬件平台和提高访问性能的要求。

  1. 平台原因(移植):不同的硬件平台对内存访问的要求不同,不是所有的硬件平台都能访问任意地址上的任意数据。某些硬件平台只能在特定的地址处读取特定类型的数据,否则会抛出硬件异常。例如,一些处理器可能要求 32 位的数据必须存储在 4 的倍数的地址上,以保证数据的正确读取和写入。
  2. 性能原因:为了访问未对齐的内存,处理器通常需要进行两次或多次内存访问,而对齐的内存访问只需要一次访问。这是因为未对齐的数据可能跨越了多个内存单元,处理器需要分别读取这些单元并进行组合才能获取完整的数据。而对齐的数据可以一次性被读取,从而提高了内存访问的效率。

在 32 位的机器下,常见数据类型的对齐值如下:char 对齐值为 1,short 为 2,intfloat 为 4,double 为 8。也就是说,如果一个变量的内存地址正好是其长度的整数倍,那么它就被称为自然对齐。

以下是一些结构体内存对齐的示例:

struct asd1{
    char a;
    char b;
    short c;
    int d;
};//8 字节
// 解释:char a 和 char b 共占 2 字节,由于 short c 对齐值为 2,所以 c 从第 2 字节开始存储,占用 2 字节,此时共 4 字节。
// int d 对齐值为 4,所以从第 4 字节开始存储,占用 4 字节,结构体总大小为 8 字节。

struct asd2{
    char a;
    short b;
    char c;
    int d;
};//12 字节
// 解释:char a 占 1 字节,short b 对齐值为 2,所以 b 从第 2 字节开始存储,占用 2 字节,此时共 3 字节。
// char c 占 1 字节,此时共 4 字节。int d 对齐值为 4,从第 4 字节开始存储,占用 4 字节,但为了满足对齐要求,结构体总大小需为 4 的倍数,所以总大小为 12 字节。

内存对齐的规则是:按照 #pragma pack 指定的数值和数据成员自身长度中较小的那个进行对齐。例如:

#pragma pack(4)
struct asd3{
    char a;
    int b;
    short c;
    float d;
    char e;
};//20 字节
// 解释:char a 占 1 字节,int b 对齐值为 4,所以 b 从第 4 字节开始存储,占用 4 字节,此时共 8 字节。
// short c 对齐值为 2,由于指定 pack 为 4,取较小值 2,所以 c 从第 8 字节开始存储,占用 2 字节,此时共 10 字节。
// float d 对齐值为 4,从第 12 字节开始存储,占用 4 字节,此时共 16 字节。
// char e 占 1 字节,为了满足对齐要求(总大小为 4 的倍数),结构体总大小为 20 字节。
#pragma pack()

#pragma pack(1)
struct asd4{
    char a;
    int b;
    short c;
    float d;
    char e;
};//12 字节
// 解释:由于指定 pack 为 1,所有数据成员都按 1 字节对齐,依次存储,总大小为 1 + 4 + 2 + 4 + 1 = 12 字节。
#pragma pack()

程序的装入、静态链接与动态链接

在程序从源代码到可执行文件的过程中,程序的装入和链接是重要的环节,其中程序装入方式有绝对装入、静态重定位和动态重定位,链接方式则有静态链接、装入时动态链接和运行时动态链接。

(一)程序装入方式

  1. 绝对装入:在这种装入方式中,程序在编译时就确定了绝对地址。也就是说,程序中的所有地址都是基于特定的内存布局确定的。然而,这种方式存在一个明显的缺点,即如果将该程序放到另一台内存布局不同的电脑上,由于地址的不匹配,程序很可能无法运行。因为不同电脑的内存地址空间分配可能存在差异,原程序中固定的绝对地址可能无法正确访问对应的内存区域。
  2. 静态重定位:静态重定位方式下,程序在编译、链接后存放的是逻辑地址,这些逻辑地址都是相对于 0 地址的相对值。在程序读入内存时,需要对所有的逻辑地址进行运算,将其转换为物理地址。这种方式要求程序的地址空间必须是连续的,因为在转换过程中是基于连续的逻辑地址空间进行计算的。例如,程序中的变量 A 的逻辑地址为 100,在转换为物理地址时,会根据内存的起始地址等信息进行相应的计算。
  3. 动态重定位:与静态重定位不同,动态重定位在程序读入内存后,并不立即计算物理地址。而是在实际执行程序时,当需要访问某个地址时,才将逻辑地址转换为物理地址。这种方式的优点在于程序在装入内存时不需要进行复杂的地址转换,提高了装入的效率,并且在一定程度上增加了程序的灵活性,因为地址转换是在运行时根据实际情况进行的。

(二)链接方式

  1. 静态链接:在程序运行之前,静态链接会先将各个目标模块以及它们所需的库函数连接成一个完整的可执行文件(装入模块)。一旦链接完成,这个可执行文件在后续的运行过程中就不再拆开。这种方式的好处是程序运行时不需要再进行额外的链接操作,执行速度相对较快。但缺点是如果多个程序都使用相同的库函数,会导致库函数的代码在多个可执行文件中重复存在,占用较多的磁盘空间和内存空间。
  2. 装入时动态链接:这种链接方式是在将各目标模块装入内存的过程中,边装入边进行链接。也就是说,在程序被加载到内存时,系统会根据需要将相关的目标模块和库函数进行链接,使得程序能够正确运行。与静态链接相比,装入时动态链接可以减少可执行文件的大小,因为相同的库函数代码不需要在每个可执行文件中重复存储,而是在装入时动态链接到相应的位置。
  3. 运行时动态链接:运行时动态链接是在程序执行过程中,当需要某个目标模块时,才对它进行链接。这种方式具有便于修改和更新的优点,因为如果某个目标模块有更新,只需要替换对应的模块文件,而不需要重新链接整个程序。同时,它也便于实现对目标模块的共享,多个程序可以在运行时共享同一个目标模块的代码,提高了代码的复用性和系统的资源利用率。

页表

页表是一种带有权限属性的重要数据结构,它存放在物理内存中,主要用于记录虚拟内存页与物理页之间的映射关系。通过页表,操作系统能够实现虚拟地址到物理地址的转换,同时也在进程管理和内存保护等方面发挥着重要作用。

(一)页表的功能

  1. 虚拟地址与物理地址转换:这是页表最基本的功能,通过查找页表,操作系统可以将程序中使用的虚拟地址转换为实际的物理地址,使得程序能够正确访问内存中的数据。
  2. 隔离各进程:每个进程都有自己独立的页表,这使得不同进程的虚拟地址空间相互隔离,一个进程无法访问其他进程的内存空间,从而保证了系统的稳定性和安全性。
  3. 各进程分配连续空间:虽然物理内存可能是不连续的,但通过页表的映射,操作系统可以为每个进程提供一个连续的虚拟地址空间,方便程序的编写和运行。
  4. 权限管理(RW):页表中的权限属性位可以用来控制对内存页的读写权限,例如可以设置某些页为只读,防止程序意外修改重要的数据,增强了内存访问的安全性。

(二)不同类型页表的比较

类型 一级页表 多级页表 快表
内存访问速度 每次访问数据需要进行 2 次内存访问,第一次是访问页表获取物理地址,第二次是访问数据所在的物理内存位置 由于存在多级页表,访问数据时需要依次访问一级页表、二级页表等,直到获取到物理地址,所以内存访问次数较多 快表是用高速缓存存放常用的页表项,当需要访问内存时,首先在快表中查找,如果找到对应的页表项,就可以直接获取物理地址,大大提高了内存访问速度
空间利用率 随着虚拟内存的增大,页表的大小也会相应增大,并且由于页表数量的限制,可能会导致内存碎片化严重,空间利用率较低 多级页表采用按需分配各级页表的方式,只有在需要时才创建下一级页表,因此可以更有效地利用内存空间,空间利用率较高 /

brk()与mmap()

在标准 C 库中,提供了 malloc/free 函数用于分配和释放内存,而这两个函数的底层实现是由 brk(C++)、sbrk(C)、mmapmunmap 等系统调用完成的。在进程分配内存时,主要有 brkmmap 这两种系统调用方式。

  1. brkbrk 系统调用是将数据段(.data)的最高地址指针 _edata 往高地址方向推进来分配内存。这种方式的特点是高地址的内存释放后,低地址的内存才能释放,因此只适用于小内存的分配。而且由于内存分配和释放的方式相对固定,容易产生较多的内存碎片,影响内存的使用效率。
  2. mmapmmap 系统调用是在进程的虚拟地址空间中(位于堆和栈中间,被称为文件映射区域的地方)寻找一块空闲的虚拟内存进行分配。与 brk 不同,mmap 分配的内存可以单独释放,不容易产生大量的内存碎片,在内存管理方面更加灵活。

相同点:无论是 brk 还是 mmap,它们分配的都是虚拟内存。在首次访问这些分配的虚拟内存时,会发生缺页中断,此时操作系统会负责分配相应的物理内存,并建立虚拟内存与物理内存之间的映射关系,使得程序能够正确访问内存中的数据。

FLEX RAM

FLEX RAM 包含了 TCM(Tightly-Coupled Memory,紧密耦合内存)以及 OCRAM(片上随机访问存储器)等,其中 TCM 又分为 ITCM(指令紧耦合存储器)和 DTCM(数据紧耦合存储器),它们在功能和性能上各有特点。

  1. ITCM(指令紧耦合存储器)
    • 功能:ITCM 主要用于存储指令(程序代码),它具有较低的访问延迟和较高的带宽,能够为处理器提供快速且可预测的指令访问。这意味着处理器可以更快地获取指令并执行,从而提高程序的运行速度。
    • 连接方式:ITCM 通常与处理器核心直接相连,这种紧密的连接方式使得指令可以直接从 ITCM 快速加载到处理器中,减少了指令传输的时间开销。
    • 容量特点:ITCM 的容量相对较小,一般只能存储少量的指令代码。这是因为其设计目的是为了提供快速的指令访问,而不是存储大量的代码。
  2. DTCM(数据紧耦合存储器)
    • 功能:DTCM 主要用于存储数据,包括变量、栈、堆等。它同样具有较低的读写访问延迟和高带宽,能够快速地加载和存储数据,提高数据操作的效率。
    • 连接方式:与 ITCM 类似,DTCM 也与处理器核心直接相连,以确保数据能够快速地被处理器访问。
    • 容量特点:DTCM 的容量通常也相对较小,只能存储有限量的数据。这是为了在保证数据访问速度的同时,控制成本和芯片面积。
  3. OCRAM(片上随机访问存储器)
    • 功能:OCRAM 是一种通用的片上随机访问存储器,既可以用于存储数据,也可以用于存储指令。它在系统中起到了一定的缓冲和存储作用。
    • 容量特点:OCRAM 的容量通常比 ITCM 和 DTCM 更大,可以存储更多的数据和代码。这使得它能够满足一些对存储容量要求较高的应用场景。
    • 速度特点:OCRAM 的访问速度和带宽一般较低,虽然相对外部存储器来说它的访问速度还是较快的,但与 ITCM 和 DTCM 相比,其数据访问的速度和效率可能会稍逊一筹。

三者之间的主要区别在于其设计目标和功能。ITCM 侧重于提供快速的指令访问,以加快程序的执行速度;DTCM 则专注于快速的数据访问,提高数据操作的效率;而 OCRAM 作为通用存储器,虽然速度和带宽相对较低,但具有较大的存储容量,适用于存储一些对访问速度要求不是特别高的大量数据和代码。

#牛客激励计划#
嵌入式八股/模拟面试拷打 文章被收录于专栏

一些八股模拟拷打Point,万一有点用呢

全部评论
mark一下内存模型
点赞 回复 分享
发布于 03-03 11:24 陕西
mark一下内存模型
点赞 回复 分享
发布于 昨天 20:01 山东
mark一下内存模型
点赞 回复 分享
发布于 昨天 21:43 山东

相关推荐

02-24 14:38
一、进阶阶段(6 - 12 个月)深入学习知识体系操作系统:深入理解嵌入式操作系统的原理和机制,如实时操作系统(RTOS)。学习任务调度、中断处理、内存管理等核心概念。推荐研究 FreeRTOS 等开源 RTOS,并阅读相关的技术文档和书籍。通信协议:掌握常见的嵌入式通信协议,如 UART、SPI、I2C 等。了解这些协议的工作原理和应用场景,通过实际项目进行协议的编程实现。推荐阅读《嵌入式系统通信协议实战》。数据结构与算法:学习基本的数据结构(链表、栈、队列、树等)和算法(排序、搜索等),提高程序的效率和性能。可以参考《数据结构与算法分析(C 语言描述)》,并在在线编程平台上进行算法练习。开发工具进阶集成开发环境(IDE):熟练使用专业的嵌入式开发 IDE,如 Keil、IAR 等。掌握项目创建、代码编辑、编译、调试等全流程操作。版本控制工具:学会使用版本控制工具,如 Git,进行代码管理和团队协作。了解分支管理、合并冲突解决等基本操作。硬件平台拓展尝试使用更复杂的嵌入式开发板或模块,如 STM32 系列微控制器。深入了解其硬件架构、外设功能和编程方法。通过实际项目,如电机控制、传感器数据采集等,提升硬件开发能力。嵌入式C++面试冲刺可以看大佬面经  链接在下边https://www.nowcoder.com/creation/manager/columnDetail/MJNwoM
点赞 评论 收藏
分享
秋招进入尾声了,还没拿到offer,大家是不是还在暗自哭泣,今年秋招难!!!但是!不要气馁,咱们还有补录机会!!!那要怎样才能抓住机会,成功上车呢?相信大家在前期的面试中应该积累了一定的面试经验了,对于面试官常问的问题心中应该也有数了吧,但是面试前的准备工作是很多同学容易忽视的,而这个准备工作有时候也能直接决定面试的成功率。所以今天小编就来给大家盘一盘面试前要做哪些准备工作,供大家查漏补缺!!!首先,要保证自己对过往的经历非常熟悉。ps:这里也提示我们在工作中,不能一味执行。不去思考背后的动机,会让自己的工作变得毫无意义不说,还会让我们的成长非常缓慢。其次,将自己的能力与岗位做好一一匹配。以上内容可以拿张纸列出来,然后和自己的情况做一一对应,并保证自己和岗位匹配之处,也能找到对应的事例证明,这样在面试中基本不会出现被问倒的情况。最后,想清楚自己的求职动机。大家都知道,为什么来应聘这个岗位之类的问题,几乎是面试的必问题,面试官要清楚候选人的动机,才能判断候选人的稳定性以及入职后在这个岗位上的主观能动性。围绕面试岗位,我们需要充分了解该岗位服务的对象是什么?是对外还是对内?对外的话是to b还是to c?产品特性是什么?行业内有什么经典案例?企业在行业的地位?该岗位一天下来的工作状态\体验如何?了解以上内容,一方面能保证我们能更好的回答面试官针对业务方向的问题,另一方面也能让面试官看到我们明确且坚定的求职动机。同时,自己也可以想清楚该岗位的工作内容是否和自己的职业规划一致。了解渠道:可以通过企业的招聘简章、企业官网、企业官方公众号、行业网站、行业公众号去了解,也可以上网看看同类型的工作状态是什么样的。同类型的部门,一般在企业里面会对接哪些部门?该部门在公司处于什么样的角色/定位?发展如何?尤其是大厂,同一个岗位,在不同的业务线上,工作体验可能是完全不一样的,有的是神仙工作,有的可能是深坑。提前了解,可以帮助我们进一步判断这个工作的内容、晋升和自己的职业规划是否一致,也可以帮我们避坑。了解渠道:可以通过网络平台,也可以通过询问该方向工作的师兄师姐或者老师。有的面试者会在面试中去问企业薪资,有的面试者会在面试官问到期望薪资时回答不上来,这两种情况显然在面试中都会减分。前者会让面试官觉得候选人眼高手低、好高骛远,尤其是应届生在初面中问薪酬,是非常减分的。后者会让面试官觉得候选人对自己不够自信,对岗位缺乏了解。而这两种情况其实是完全可以避免的,我们在面试前去了解目标岗位在同类型企业的薪酬范围,并不是一件难事。嵌入式面经可以看这个大佬总结的就很详细https://www.nowcoder.com/creation/manager/columnDetail/MJNwoM
点赞 评论 收藏
分享
评论
4
9
分享

创作者周榜

更多
牛客网
牛客企业服务