大疆嵌入式软件一二面:static 与 volatile、缓冲容量与丢数据排查
- 轮次
- 一二面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
一面·项目与基础
- 自我介绍,然后讲项目:现在主要做什么,用什么芯片和系统,自己负责哪一部分?
- 挑一个 MCU 加 RTOS 的项目,从数据采集、任务处理到通信往外发,完整讲一遍。
- static 的作用是什么?修饰局部变量和全局变量分别有什么区别?
- volatile 用过没有,项目里什么地方用了?它能不能保证操作的原子性?
- 指针和数组有什么区别?数组作为函数参数传进去以后,sizeof 算出来是什么?
- 结构体内存对齐怎么算?现场给几个成员算大小,调整顺序后再算一遍。
- 堆和栈有什么区别?局部变量的地址能不能返回?
- memcpy 和 memmove 有什么区别?内存区域重叠时怎么处理?
- 中断里哪些事情不适合做?耗时操作一般怎么交给任务处理?
- 中断和任务之间传数据,用过什么方式?
- 互斥锁和二值信号量有什么区别?
- 优先级反转是什么?优先级继承怎么缓解?
- 任务什么时候会发生切换?高优先级任务一直处于就绪状态会怎么样?
- 任务栈大小怎么定?有没有看过栈的实际使用情况?
- 手撕:链表反转,写完自己检查空链表、只有一个节点这些边界。
二面·数据链路
- 把数据从进来到出去的整个过程讲清楚:中间经过哪些中断、哪些任务、哪些缓冲区?
- 采样频率是多少,一次产生多少数据?
- 接收缓冲区为什么开这么大,有没有算过最坏情况下要存多少?
- 如果消费数据的任务被延迟了,缓冲区能撑多久?
- 缓冲区满了怎么办,丢新数据还是覆盖旧数据,依据是什么?
- 怎么判断系统已经开始丢数据,有没有计数或者其他检测方式?
- 项目里几个任务的优先级怎么排,哪个任务必须及时响应?
- 从数据采集完成到处理结束,允许的最大延迟是多少?
- 怎么测这段延迟?只看平均值够不够?
- 两个任务共用一个缓冲区,怎么避免读到写了一半的数据?
- 如果 DMA 还在往缓冲区写,任务能不能直接读,怎么划分可读的部分?
- 临界区里处理的数据比较多,会有什么影响?
二面·故障排查
- 讲一个你最难定位的 bug。
- 最开始怎么发现丢数据的,凭什么确定不是发送端少发了?
- 排查过程中排除了哪些可能,每一步依据是什么?
- 有没有留下出问题时的现场数据?
- 加日志以后,问题出现的频率有没有变化?
- 最后怎么验证修改有效?如果跑几天没复现,能不能说明解决了?
- 设备偶发进 HardFault,你会先保留哪些信息?
- 怀疑任务栈溢出,怎么确认?还可能是什么原因?
- 开了编译优化以后才出现异常,会从哪些地方查?
- 看门狗由谁来喂?如果有个业务任务卡死,其他任务还在正常运行,能不能发现?
二面·手撕
- 实现一个环形缓冲区,先按单线程情况写。
- 如果一个任务写、另一个任务读,需要额外考虑什么?读写位置怎么设计,判满和判空怎么区分?
《参考解析》
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 与最大值,长尾才对应丢数据的时刻,平均值会把问题掩盖掉。验证有效性要么构造能稳定复现的压力场景——拉高采样率、注入高优先级负载、缩小缓冲,要么给出统计口径:跑了多少帧、按当前速率还有多大概率复现。只跑几天属于采样不足,不构成结论。