面灵AI→

大疆嵌入式软件一二面:static 与 volatile、缓冲容量与丢数据排查

轮次
一二面
时间
2026-10
来源
牛客网

《面试题目》

一面·项目与基础

  1. 自我介绍,然后讲项目:现在主要做什么,用什么芯片和系统,自己负责哪一部分?
  2. 挑一个 MCU 加 RTOS 的项目,从数据采集、任务处理到通信往外发,完整讲一遍。
  3. static 的作用是什么?修饰局部变量和全局变量分别有什么区别?
  4. volatile 用过没有,项目里什么地方用了?它能不能保证操作的原子性?
  5. 指针和数组有什么区别?数组作为函数参数传进去以后,sizeof 算出来是什么?
  6. 结构体内存对齐怎么算?现场给几个成员算大小,调整顺序后再算一遍。
  7. 堆和栈有什么区别?局部变量的地址能不能返回?
  8. memcpy 和 memmove 有什么区别?内存区域重叠时怎么处理?
  9. 中断里哪些事情不适合做?耗时操作一般怎么交给任务处理?
  10. 中断和任务之间传数据,用过什么方式?
  11. 互斥锁和二值信号量有什么区别?
  12. 优先级反转是什么?优先级继承怎么缓解?
  13. 任务什么时候会发生切换?高优先级任务一直处于就绪状态会怎么样?
  14. 任务栈大小怎么定?有没有看过栈的实际使用情况?
  15. 手撕:链表反转,写完自己检查空链表、只有一个节点这些边界。

二面·数据链路

  1. 把数据从进来到出去的整个过程讲清楚:中间经过哪些中断、哪些任务、哪些缓冲区?
  2. 采样频率是多少,一次产生多少数据?
  3. 接收缓冲区为什么开这么大,有没有算过最坏情况下要存多少?
  4. 如果消费数据的任务被延迟了,缓冲区能撑多久?
  5. 缓冲区满了怎么办,丢新数据还是覆盖旧数据,依据是什么?
  6. 怎么判断系统已经开始丢数据,有没有计数或者其他检测方式?
  7. 项目里几个任务的优先级怎么排,哪个任务必须及时响应?
  8. 从数据采集完成到处理结束,允许的最大延迟是多少?
  9. 怎么测这段延迟?只看平均值够不够?
  10. 两个任务共用一个缓冲区,怎么避免读到写了一半的数据?
  11. 如果 DMA 还在往缓冲区写,任务能不能直接读,怎么划分可读的部分?
  12. 临界区里处理的数据比较多,会有什么影响?

二面·故障排查

  1. 讲一个你最难定位的 bug。
  2. 最开始怎么发现丢数据的,凭什么确定不是发送端少发了?
  3. 排查过程中排除了哪些可能,每一步依据是什么?
  4. 有没有留下出问题时的现场数据?
  5. 加日志以后,问题出现的频率有没有变化?
  6. 最后怎么验证修改有效?如果跑几天没复现,能不能说明解决了?
  7. 设备偶发进 HardFault,你会先保留哪些信息?
  8. 怀疑任务栈溢出,怎么确认?还可能是什么原因?
  9. 开了编译优化以后才出现异常,会从哪些地方查?
  10. 看门狗由谁来喂?如果有个业务任务卡死,其他任务还在正常运行,能不能发现?

二面·手撕

  1. 实现一个环形缓冲区,先按单线程情况写。
  2. 如果一个任务写、另一个任务读,需要额外考虑什么?读写位置怎么设计,判满和判空怎么区分?

《参考解析》

volatile 只保证「每次都去内存读」,不保证原子性。它抑制编译器把变量缓存进寄存器、或删掉看起来没用的读写,适合中断与任务共享的标志、DMA 更新的状态、寄存器映射。但 cnt++ 是读-改-写三步,中断插在中间就会丢更新;要原子得关中断、关调度,或用 LDREX/STREX 与 __atomic 内建。volatile 也不带内存屏障,编译器和 CPU 仍可重排,跨核共享必须自己补 barrier,否则「读到了标志、数据还是旧的」这类问题会在优化等级变化后突然出现。

结构体内存对齐是填空而不是压缩。每个成员的偏移必须是其对齐值的整数倍,结构体总大小按最大成员对齐向上取整。char、int、char 在四字节对齐下是 1+3 填充+4+1+3 填充 = 12,改成 char、char、int 就是 1+1+2+4 = 8。面试官让现场重排,是看你知道填充从哪来、能不能靠调序省内存。嵌入式里还要留意 #pragma pack 的代价:省了空间,却可能换来非对齐访问,在 Cortex-M0 这类核上直接 HardFault。

环形缓冲区的判空判满有两套写法。留一格空位时,head == tail 为空、(head+1) % N == tail 为满,代价是浪费一个槽;用单调递增的读写计数、靠差值判断,则能吃完容量,但要处理计数回绕。单写单读场景不需要锁:写者只动 head、读者只动 tail,各自对另一个位置只读,配 acquire/release 语义即可;多写者才需要临界区或原子 CAS。DMA 写入时还要把「有效长度」放在最后更新,并保证 cache 已清,否则读者可能看到半条数据。

容量要按最坏情况算,不能按「没出过事」算。公式是生产速率乘消费者最长阻塞时间,再加一次突发量:10 kHz 采样、每次 4 字节、消费者可能被压 20 ms,就需要 800 字节,然后留余量。溢出策略必须显式选:丢新保旧适合状态量,覆盖最旧适合实时流;同时加溢出计数与水位告警。没有计数,「从来没丢过」通常只是没测过,而不是真的没丢。

延迟要看分布,验证修复也不能靠「几天没复现」。在采集完成和处理完成各打一个时间戳(DWT 周期计数器或硬件定时器),统计 P99 与最大值,长尾才对应丢数据的时刻,平均值会把问题掩盖掉。验证有效性要么构造能稳定复现的压力场景——拉高采样率、注入高优先级负载、缩小缓冲,要么给出统计口径:跑了多少帧、按当前速率还有多大概率复现。只跑几天属于采样不足,不构成结论。