面灵AI→

汇川嵌入式软件开发凉经:八股只是第一刀,追问才会有第二刀

轮次
一面+二面
结果
已挂
时间
2026-10
来源
牛客网

《面试题目》

一面:C 语言、MCU 和项目基础

  1. 从系统上电开始讲简历里最熟悉的一个 MCU 项目:任务怎么启动、数据怎么流、最后怎么控制执行器
  2. static 修饰局部变量、全局变量和函数分别有什么作用?
  3. const 和宏定义有什么区别?
  4. 指针常量和常量指针怎么区分?
  5. 函数指针在嵌入式项目里有哪些实际用途?
  6. 结构体为什么需要内存对齐?
  7. 给一个结构体,计算它在 32 位平台上的大小
  8. 大端和小端分别是什么,怎么用 C 代码判断?
  9. volatile 一般用在什么地方?
  10. 中断和主循环同时访问一个变量,只加 volatile 是否安全?
  11. 栈和堆有什么区别?
  12. MCU 出现栈溢出通常会有什么表现?
  13. 野指针是怎么产生的,怎么排查?
  14. memcpy 遇到内存重叠会怎样,应该换成什么?
  15. MCU 从上电到进入 main 函数之前做了什么?
  16. 中断响应的完整过程?
  17. 中断里为什么不建议做耗时操作?
  18. 中断优先级是怎么配置的?
  19. DMA 和中断接收串口数据分别适合什么场景?
  20. 串口数据长度不固定,怎么判断一帧接收完成?
  21. I²C 为什么需要上拉电阻?
  22. SPI 四种模式是怎么区分的?
  23. 看门狗怎么使用,直接在定时器中喂狗有什么问题?

一面手撕

  1. 不用库函数实现字符串转整数,需要考虑正负号、非法字符和溢出
  2. 实现一个固定长度的环形缓冲区,写出初始化、写入和读取;追问:如果中断负责写、任务负责读,会不会有并发问题?

二面:RTOS、CAN 和故障定位

  1. 任务调度的基本原理是什么?
  2. 抢占式调度和时间片调度有什么区别?
  3. 同优先级任务一定会轮流运行吗?
  4. 任务有哪些状态,状态之间怎么切换?
  5. 为什么不能在中断里调用普通版本的队列发送函数?
  6. FromISR 版本和普通版本有什么区别?
  7. 二值信号量、计数信号量和互斥锁分别适合什么场景?
  8. 互斥锁为什么需要优先级继承?什么是优先级反转?
  9. 高优先级任务长期得不到运行,可能是什么原因?
  10. 软件定时器的回调函数在哪个任务里执行?回调阻塞太久会造成什么影响?
  11. 怎么估算每个任务需要分配多少栈?
  12. 系统偶发进入 HardFault,怎么判断是不是任务栈溢出?
  13. 内存碎片是怎么产生的?为什么有些嵌入式项目不允许动态申请内存?
  14. CAN 总线为什么能够解决多个节点同时发送的问题?
  15. 显性位和隐性位分别是什么?CAN 仲裁失败算不算发送错误?
  16. 标准帧和扩展帧有什么区别?CAN ID 越小为什么优先级越高?
  17. 位填充的作用是什么?ACK 槽由谁发送?
  18. TEC 和 REC 分别表示什么?Error Active、Error Passive 和 Bus-Off 怎么切换?
  19. 节点进入 Bus-Off 后应该怎么恢复?
  20. 总线上偶发大量错误帧,先从软件还是硬件排查?
  21. 终端电阻为什么一般是 120Ω?两个终端电阻并联后测量为什么接近 60Ω?
  22. 波特率、采样点和线缆长度之间有什么关系?
  23. 接收消息很多时,怎么避免某类低优先级报文饿死?
  24. 多个相同设备使用同一套程序,节点地址怎么分配?

《参考解析》

这轮面试的结构值得单独说。 作者自己的复盘抓到要点:题目之间是连着的。你说项目用了 FreeRTOS,面试官就接着问任务优先级、任务间通信和栈空间;你说通过 CAN 控制设备,他就问错误处理和 Bus-Off;你说做了看门狗,他就问怎么判断究竟是哪一个任务卡死。所以「只能说出用了什么」和「能说清为什么这样设计、异常发生后怎么恢复」在这类面试里是两条完全不同的分数线。准备方法可以固定成每个技术点往下追三层:为什么用它 → 它带来什么代价 → 出故障时现象是什么、怎么定位。

volatile 与并发那问是经典陷阱。 「中断和主循环同时访问一个变量,只加 volatile 是否安全」的答案是不安全:volatile 只保证每次访问真的去内存读写、不被编译器缓存到寄存器或优化掉,它不保证原子性、不提供内存屏障。对单字节标志位,volatile 通常够用;但对 uint32_t 计数器或结构体这类多字节共享数据,读改写仍然可能被打断(比如主循环正在写高 16 位、中断进来读走了半新半旧的值),必须关中断或用原子操作保护。回答时把「volatile 解决什么、不解决什么」两句话分清楚,比堆术语有用。

互斥锁为什么需要优先级继承。 场景是优先级反转:低优先级任务 L 持有互斥锁,高优先级任务 H 等这把锁而被阻塞,此时中优先级任务 M 就绪——M 抢占了 L,L 迟迟释放不了锁,结果 H 被 M 无限期拖延。优先级继承的做法是:当 H 阻塞在 L 持有的锁上时,临时把 L 的优先级提升到 H 的水平,让 L 尽快跑完临界区释放锁,H 才能继续。注意它只是缓解而不是消除,且只对互斥锁有效——二值信号量没有所有者概念,做不了继承,所以保护共享资源要用互斥量而不是信号量。更彻底的办法是把临界区设计得足够短,或用无锁队列传递所有权。

环形缓冲区手撕的追加追问。 写完初始化/写/读之后被问「中断写、任务读会不会有并发问题」,这是嵌入式里最常见的生产者消费者。答案取决于实现细节:单生产者单消费者(只有中断写、只有任务读)下,只要读写指针各自只由一个上下文写、并且用 volatile 或内存屏障保证可见性,就可以做成无锁;麻烦的是判断空和满——习惯上留一个空槽(head == tail 为空、head+1 == tail 为满)或额外维护一个计数。一旦写指针不止一个写者(比如两个中断都可能写),就必须关中断或用临界区保护。答题时把「谁写谁读、各几个上下文、临界区多长」这三点先问清楚,再给方案,比直接写代码得分高。

CAN 的几个必答点。 仲裁能解决多节点同时发送,靠的是显性位覆盖隐性位:节点边发边听,谁发出隐性却读到显性就立刻退出,ID 越小(前导显性位越多)优先级越高,且仲裁失败不算发送错误、可自动重发。位填充是为了让接收方能从跳变沿恢复时钟,连续 5 个相同电平后强制插入一个反相位。ACK 槽由所有正确接收到的其他节点共同拉低,发送方据此确认至少有一个节点收到。错误状态机由 TEC/REC 两个计数器驱动:错误计数超过阈值从 Error Active 降到 Error Passive,再恶化就进入 Bus-Off(节点脱离总线),恢复方式是等待一段静默期后重新初始化并重入总线(单片机通常要求软件显式处理,别指望自动恢复)。终端电阻 120Ω 是匹配双绞线特性阻抗以吸收反射,两个终端并联后测量自然接近 60Ω。总线上偶发大量错误帧时,先怀疑硬件——线缆、终端电阻、共地、屏蔽、分支长度,这些远比软件更常见。

「偶发 HardFault 怎么判断是栈溢出」的思路。 可以分三层取证:① 静态上计算最深调用链加中断嵌套的栈需求,和分配量对比;② 运行时给每个任务的栈填魔术字(如 0xA5),跑一段时间后统计未被打扰的长度得到高水位,这是最直接的证据;③ 崩溃现场取证——从栈帧取出出错时的 PC/LR/PSR,对照 map 和反汇编定位到函数,同时看 SCB 里的异常状态寄存器判断是哪个异常;FreeRTOS 下还可以开 configCHECK_FOR_STACK_OVERFLOW 钩子或栈底 canary。注意区分「任务栈溢出」和「主栈(MSP)溢出」:中断和启动阶段用的是主栈,任务用的是 PSP,两者独立。