(嵌入式面经)第11章 20+公司面经杂谈(二):影石insta360、汇川、星纵科技、金风科技
本篇涉及的所有问题概要:大家可以试试在看参考答案前,提前尝试解答,以便明确自身知识点的不足部分!
1. 在多线程中,其中一个线程调用在其他地方的函数时,函数的参数会存在哪里?
2. 写时复制技术有了解过吗,说下实际应用的案例?
3. 有没有遇到过栈爆炸的情况?
4.了解systick定时器吗,说说有什么作用?
5.有没有遇到SPI时钟不匹配或者乱序的问题,如何解决的?
6.CPU寄存器中哪几个比较关键?
7.FREERTOS线程切换中大概做了哪些工作?
8.看你有涉及内存管理,那你知道动态内存管理的方法,有哪些吗?
9. 在我们平常内存管理中,除了内存碎片,还会碰到什么难题吗?
10. 专栏订阅奖励(支持模仿)——个人创新点问答:你所设计的FreeRTOS PLUS中操作系统中是怎么去做/检测有没有内存泄漏的?
---------------------------------------------------------------------------------------------------
1. 在多线程中,其中一个线程调用在其他地方的函数时,函数的参数会存在哪里?
在多线程编程中,每个线程有自己独立的栈空间来存储函数的参数和局部变量。
因此,函数参数存储在调用该函数的线程的栈中,不会影响其他线程。
1. 线程栈的作用
在 多线程编程 中,每个线程都有自己的 独立栈,它的主要作用包括: ✅ 存储函数调用的局部变量(包括函数参数、局部变量、返回地址)。
✅ 维护函数调用状态(记录返回地址、执行上下文)。
✅ 支持递归调用(每次递归调用都会在栈上创建一个新的栈帧)。
2. 函数参数的存储
当线程调用一个函数时,函数的参数会被压入该线程的栈。
- 函数调用时,参数被存入栈,函数执行期间这些参数一直存在。
- 函数返回后,栈上的参数会被销毁(栈帧弹出),释放给其他函数调用使用。
✅ 示例:线程调用函数,参数存储在栈上
✅ value
的地址传递给 thread_func()
,但 local_var
作为局部变量存储在线程的栈上。
3. 线程与栈的关系
(1)每个线程有独立栈
(2)线程栈的大小
- 默认栈大小(Linux
pthread
):通常为 8 MB。 - 可调整栈大小:
4. 栈帧和函数调用
函数调用时栈的变化
每次函数调用都会创建一个新的栈帧,包含:
✅ 示例:函数调用栈帧
✅ 执行 funcB()
时,栈帧变化如下:
| funcB() 栈帧 | // 进入 funcB() | funcA() 栈帧 | // 调用 funcA(),创建新栈帧,存储参数 5 | 局部变量 x |
当 funcA()
执行完毕,其栈帧会被销毁,返回 funcB()
。
5. 线程栈的线程安全性
(1)为什么线程的栈是线程安全的?
✅ 每个线程的栈是独立的,不会影响其他线程。
✅ 函数参数和局部变量在各自线程的栈上,互不干扰。
✅ 除非主动共享数据(如全局变量),否则栈上的变量是线程安全的。
(2)非线程安全的情况
⚠ 使用全局变量可能导致线程安全问题:
✅ 解决方案:使用互斥锁(Mutex)
6. 结论
✅ 函数的参数存储在调用线程的栈中,函数返回后该栈帧会被销毁。
✅ 每个线程拥有独立栈空间,确保线程调用相同函数时互不干扰。
✅ 局部变量和参数是线程安全的,但共享数据需要额外同步机制。
2. 写时复制技术有了解过吗,说下实际应用的案例?
1. 什么是写时复制?
写时复制(Copy-On-Write, COW) 是一种 内存优化技术,其核心思想是: ✅ 延迟数据复制,只有当数据被修改时才进行真正的拷贝。
✅ 多个进程/线程可以共享相同的内存区域,避免不必要的复制,节省资源。
🚀 典型应用场景:
- 进程创建(Linux
fork()
) - 字符串、数据结构(C++
std::string
、智能指针) - 虚拟内存管理(内存页共享)
2. 为什么需要写时复制?
在 传统拷贝 方式中,当一个进程 调用 fork()
创建子进程 时,整个进程的内存都会被复制一遍:
这会 浪费大量内存,尤其是当子进程 只是读取数据,而不修改时,这部分拷贝完全没必要。
写时复制的解决方案:
- 初始时,父子进程共享相同的内存页(只读)。
- 只有当子进程或父进程尝试修改数据时,才触发真正的内存拷贝(COW 机制)。
- 这样只有被修改的页面才会被复制,避免不必要的拷贝,提高性能。
✅ 示例(写时复制)
✅ 如果 B 修改 [ 共享页 2 ]
🚀 只复制修改的页,而不是整个进程的内存,节省内存资源!
3. Linux fork() 机制与 COW
fork() 的默认行为
- 在 Linux 中,
fork()
不会立即复制整个父进程的内存页,而是通过写时复制(COW)技术,父子进程共享相同的内存页。 - 只有在 父进程或子进程尝试写入数据 时,才会触发内存页的复制。
✅ 示例(C 语言 fork()
)
var
在 fork()
时 不会立即复制,父子进程 共享相同的内存页。var = 20
,触发 COW,复制 var
变量的内存页。var = 10
,因为它没有修改该变量。4. 写时复制在 C++ STL 容器中的应用
在 C++ 中,某些数据结构(如 std::string
、std::vector
)也使用 写时复制(COW) 来优化性能。
✅ C++ std::string
的 COW
str2 = str1
不会立即复制内存,而是共享 str1 的数据。str2[0] = 'X'
触发 COW,str2
获得独立副本,避免修改str1
。
⚠ C++11 之后,标准库已移除 std::string
的 COW,改用 std::move()
进行高效拷贝。
5. 写时复制的优缺点
✅ 优点
fork()
性能)。❌ 缺点
- 增加了额外的逻辑开销(需要标记哪些数据是共享的)。
- 多线程环境可能需要额外的锁机制(避免多个线程同时修改)。
6. 写时复制 vs 深拷贝 vs 浅拷贝
✅ COW 适用于只读多、写入少 的场景,避免不必要的拷贝,提高效率。
结论
✅ 写时复制(Copy-On-Write, COW)是一种优化内存使用的技术,主要用于进程 fork()
、C++ STL、数据库等场景。
✅ 通过 COW,多个对象可以共享相同数据,只有在修改数据时才真正分配内存,避免不必要的复制。
✅ COW 适用于"读多写少" 的场景,如 fork()
进程创建、共享 std::string
数据、数据库事务。
3. 有没有遇到过栈爆炸的情况?
1. 什么是栈爆炸?
栈爆炸(Stack Overflow) 发生在 程序调用栈空间被耗尽 时,通常导致 程序崩溃。
🚀 常见原因:
2. 栈爆炸的典型场景
(1)递归没有终止条件
如果递归函数没有合适的终止条件,它会一直递归调用自己,直到栈空间耗尽。
✅ 示例
recursive_function()
调用自身,都会在 栈上分配新的栈帧。✅ 修正方法
(2)递归层次过深
即使递归有终止条件,但如果 递归深度过大,依然会导致栈空间不足。
✅ 示例
✅ 修正方法
- 使用循环代替递归(尾递归优化)
- 增大栈大小(Linux
ulimit -s
命令)
(3)局部变量过大
局部变量 存储在栈上,如果分配的变量过大,会导致栈空间耗尽。
✅ 示例
malloc()
申请堆内存。✅ 修正方法
(4)多线程栈空间不足
在多线程程序中,每个线程都有自己的 独立栈(通常比主线程小,如 1MB)。
✅ 示例
✅ 修正方法
3. 如何防止栈爆炸?
✅ (1)优化递归
- 确保递归有终止条件。
- 使用循环代替深度递归(尾递归优化)。
- 使用动态编程减少递归深度。
✅ (2)避免大局部变量
- 使用
malloc()
分配大数组,而不是栈上分配。 - 释放不再需要的堆内存,避免泄漏。
✅ (3)增加栈大小
- Linux 通过
ulimit -s unlimited
取消限制。 pthread
线程手动设置更大栈空间。
✅ (4)检测栈使用
- 在嵌入式系统中,使用
栈监视工具
检查栈溢出。 - 使用
GDB
断点跟踪递归调用栈。
结论
✅ 栈爆炸(Stack Overflow)通常由递归过深、局部变量过大、多线程栈不足导致。
✅ 避免无限递归,使用尾递归优化、动态编程或循环替代递归。
✅ 合理管理内存,避免大数组分配在栈上,必要时增加栈空间。
4.了解systick定时器吗,说说有什么作用?
1. 什么是 SysTick?
SysTick(System Timer)是 Cortex-M4 内核 内置的 24 位递减定时器,嵌入在 NVIC(Nested Vectored Interrupt Controller) 中。
它的主要特点包括:✅ 24-bit 递减计数器(最大计数 2^24 - 1 = 16,777,215
)。
✅ 计数完毕触发中断,可用于 周期性任务(如 OS 时基)。
✅ 固定时钟源(通常是 SYSCLK 或内部时钟)。
2. SysTick 的主要作用
(1)普通定时器
- SysTick 可以用作普通定时器,例如 精确延时、定时触发任务。
(2)OS 时基
- FreeRTOS、RT-Thread 等实时操作系统(RTOS) 需要一个 固定的时钟节拍(Tick),SysTick 常用于 提供 OS 心跳,驱动 任务调度。
✅ 示例
SysTick 周期性触发中断 --> 增加系统 Tick 计数器 --> 触发任务调度
3. SysTick 计时原理
SysTick 计数方式为 向下递减:
- 当 SysTick 计数器(CURRENT)从
Reload
递减到 0 时:触发 SysTick 中断。计数器 自动重新加载Reload 值,继续递减。 - SysTick 计数频率 = 系统时钟(SYSCLK) / 预分频系数。
4. SysTick 寄存器
SysTick 主要有以下 三个寄存器:
✅ 配置寄存器
5. SysTick 配置示例
✅ 示例:使用 SysTick 实现 1ms 定时
Get_SysTick()
获取当前时间。6. SysTick 在 RTOS(FreeRTOS)中的作用
(1)为什么 RTOS 需要 SysTick?
- RTOS 任务调度 依赖于 时基(Tick Timer),SysTick 作为 时钟源 为 RTOS 提供固定节拍。
- 任务切换发生在 SysTick 中断 里,每次触发时会判断是否需要 切换任务。
(2)SysTick 在 FreeRTOS 中的配置
✅ FreeRTOS 需要重定义 SysTick_Handler
✅ 配置 FreeRTOS 的时钟
7. SysTick 的优缺点
✅ 优点
- 内核自带,无需额外硬件,占用资源少。
- 适用于定时任务、OS 时基、。
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
作者简介:仅用几个月时间0基础天坑急转嵌入式开发,逆袭成功拿下华为、vivo、小米等15个offer,面试经验100+,收藏20+面经,分享求职历程与学习心得。 专栏内容:这是一份覆盖嵌入式求职过程中99%问题指南,详细讲解了嵌入式开发的学习路径、项目经验分享、简历优化技巧、面试心得及实习经验,从技术面,HR面,AI面,主管面,谈薪一站式服务,助你突破技术瓶颈、打破信息差,争取更多大厂offer。