面灵AI→

超聚变 嵌入式软件一面:任务划分、ISR 通信与结构体内存对齐

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

《面试题目》

一面(约 40 分钟)

  1. 自我介绍
  2. 项目
  3. 项目中的最困难的地方?如何解决?
  4. 如何划分任务的?怎么避免资源冲突?
  5. ISR 和任务之间怎么通信?
  6. volatile 有什么作用?中断变量为什么经常要加 volatile?
  7. 结构体和联合体有什么区别?
  8. 结构体为什么要内存对齐和填充?怎么计算一个结构体的 sizeof?

《参考解析》

  1. RTOS 任务划分与资源冲突:划分的依据是「职责单一 + 实时性等级」。典型切法是采集任务、数据处理任务、通信任务、控制/业务任务、监控任务各自独立;优先级按实时性定——硬实时的高,周期性采集按采样周期定,日志与上报这类非实时任务给低优先级。任务之间用 RTOS 机制通信(Queue 传数据、Semaphore 同步事件、Mutex 保护共享资源、Event Group 表达多事件状态、Task Notification 做轻量通知)。资源冲突的防护要分层说:SPI/I2C/UART 这类共享外设用 Mutex 包住一次完整事务;共享 Buffer 用 Mutex、临界区或无锁设计(生产者-消费者队列优先,能不用锁就不用);ISR 与任务之间的共享数据走中断通知 + 任务侧处理,尽量不在 ISR 里直接改复杂结构。设计原则一句话收尾:职责单一、资源访问集中、临界区尽可能短,从而避免死锁和优先级反转——补一句「优先级反转用优先级继承或直接让高优先级任务不等待低优先级任务的锁」,是这题的高分点。

  2. ISR 与任务的通信:分工是 ISR 快速响应、任务做复杂处理,流程是「硬件事件 → ISR → 读取/保存必要数据 → 通知任务 → 任务处理」。可用手段有 Queue(ISR 传数据)、Semaphore 或 Task Notification(ISR 通知事件,Task Notification 更轻量、少一次上下文开销)、Event Group(多个事件状态)、Ring Buffer(连续数据流,串口/DMA 场景)。ISR 侧的纪律必须讲清楚:尽量短、不做复杂计算、不调用可能阻塞的 API、只能用带 FromISR 后缀的 FreeRTOS 接口,退出时按需触发一次上下文切换(portYIELD_FROM_ISR)。常见的踩坑是「在 ISR 里用了非 FromISR 版本的 API」或「在 ISR 里做浮点运算与内存分配」,主动说出来会显得真写过。

  3. volatile 的作用与边界:volatile 告诉编译器这个变量可能在当前执行流之外被改变,因此每次访问都要真实读写内存,不能把它缓存到寄存器或优化掉、也不能重排它与其它 volatile 访问的相对顺序。典型场景是中断里修改的标志位、硬件寄存器、DMA 状态、内存映射 IO。要强调它不等于线程安全:它不提供原子性、不解决多任务竞争、不提供互斥与内存屏障语义。正确表述是——volatile 解决的是编译器优化导致的可见性问题,Mutex、Semaphore、原子操作解决的是并发同步问题;多核场景下还需要内存屏障或原子类型才能保证顺序与可见性。

  4. struct 与 union 的区别:本质在内存是否共享。struct 的每个成员有独立存储空间,可以同时保存有效值,sizeof 通常大于等于成员大小之和(受对齐影响);union 的所有成员共享同一块内存,同一时刻通常只用一种成员,sizeof 由最大的成员及对齐要求决定。用途上 struct 用来组织数据结构,union 用来节省空间或对同一块内存做不同类型解释(协议解析、寄存器位域、类型双关)。可以补两个实战点:union 里读一个没写过的成员,得到的是那块内存按该类型的重新解释,属于实现相关行为,别用来做类型转换依赖;判断大小端也可以用 union(写 1 看首字节),但更推荐直接用移位与字节序检测。

  5. 内存对齐与 sizeof 计算:对齐的动因是处理器对访问地址有对齐要求(不对齐可能触发异常或需要多次访存),顺带提升缓存与访存效率,所以编译器会在成员之间插入 padding。计算一个结构体的 sizeof 按固定步骤走:按声明顺序逐个看成员大小与对齐要求(通常是自身大小,指针按机器字长)→ 累加当前偏移,若不满足该成员的对齐要求就补 padding 到对齐边界 → 处理下一个成员 → 全部处理完后,把总大小补齐到最大成员对齐值的整数倍。举例:struct { char a; int b; char c; } 是 1 + 3(padding) + 4 + 1 + 3(padding) = 12,而不是 6。追问常有两个方向:怎么省空间——把成员按大小从大到小排列能显著减少 padding;怎么改布局——用 #pragma pack 或 __attribute__((packed)) 取消对齐,但要说明代价是访存变慢、在部分架构上访问未对齐地址会出错,只在协议报文、Flash 存储这类「布局必须严格一致」的场合使用。