面灵AI→

嵌入式精选 20 道必备八股文:C 语言、中断、RTOS、总线与驱动

时间
2026-09
来源
牛客网

《面试题目》

一、C 语言与内存

  1. static、const、volatile 分别解决什么问题?嵌入式里最容易用错的是哪一个?
  2. 指针和数组有什么本质区别?什么时候「数组名」不能当成指针随便用?
  3. 结构体内存对齐规则是什么?怎么算 sizeof?#pragma pack 会带来什么代价?
  4. 栈、堆、全局/静态区、BSS、代码段分别放什么?栈溢出常见现象有哪些?
  5. 大小端怎么判断?网络字节序和本地字节序转换时,嵌入式项目里容易踩什么坑?
  6. 宏和内联函数有什么区别?带副作用的宏参数为什么危险?

二、中断、RTOS 与并发

  1. 中断响应流程是什么?抢占优先级和子优先级分别干什么?
  2. 中断上下文和任务上下文有何区别?ISR 里为什么不能做耗时操作、不能随便调阻塞 API?
  3. 什么是临界区?关中断、关调度、拿锁,分别适合什么场景?
  4. FreeRTOS 任务状态有哪些?调度何时发生?时间片和抢占怎么配合理解?
  5. 二值信号量、计数信号量、互斥锁、消息队列分别解决什么问题?怎么选型?
  6. 什么是优先级反转?为什么互斥锁常配合优先级继承,而普通信号量通常不行?

三、MCU、外设与通信

  1. MCU 从上电到跑到 main,大致经过哪些阶段?启动文件和链接脚本各自干什么?
  2. DMA 解决什么问题?和 Cache 一起用时,为什么数据可能「看起来没更新」?
  3. UART 丢包、SPI 时序不对、I2C 卡死,分别优先查什么?超时和重试怎么设计才不算瞎重试?
  4. 轮询、中断、DMA 三种采集方式怎么选?实时性和 CPU 占用怎么权衡?

四、Linux、驱动与工程闭环

  1. 用户态和内核态有什么区别?一次系统调用大致怎么进内核?
  2. 字符设备驱动的基本框架是什么?file_operations 里常见接口各自负责什么?
  3. 设备树解决了什么问题?一个外设从 DTS 到 probe 成功,路径怎么走?
  4. HardFault、死机、看门狗复位,你的标准排查路径是什么?现场该保留哪些信息?

面试官在后面接着追的方向

  • 说 volatile:能替代内存屏障吗?和原子操作是什么关系?
  • 说互斥锁:能在中断里拿吗?和二值信号量差在哪?
  • 说 DMA:缓存一致性怎么保证?半传输中断干什么用?
  • 说设备树:匹配失败怎么查?compatible 对不上是什么现象?
  • 说 HardFault:怎么区分栈溢出、空指针、非法指令?

《参考解析》

  1. volatile 的三类场景讲全,才算真懂:它约束的是编译器优化,不提供原子性、不提供内存屏障。三类典型场景是硬件寄存器(值可能被外设改写,编译器不能缓存进寄存器)、中断与任务共享的变量(ISR 里改、主循环里读,不加可能读到寄存器里的旧值)、以及无锁共享的标志位。高频追问「加了 volatile 是不是就线程安全了」——不是,读改写仍可能交错,计数类共享变量要靠临界区或原子操作;「能替代内存屏障吗」——也不能,屏障约束的是指令重排与可见性顺序。

  2. 内存布局与对齐要能手算:五个区的分工要说得清——代码段放指令与常量,已初始化的全局/静态变量在 data,未初始化或初值为零的在 BSS(不占镜像体积),栈用于函数调用与局部变量,堆由 malloc 管理。对齐规则是每个成员的偏移量必须是自身对齐数的整数倍、结构体总大小是最大对齐数的整数倍,对齐数取成员大小与编译器对齐参数中较小者;#pragma pack 能压小体积,代价是 CPU 可能要把一次访问拆成多次总线操作,某些架构上直接异常。栈溢出的典型现象是 HardFault、踩坏相邻变量、返回值莫名改变,定位手段是查 MSP/PSP 指针、填充魔数(如 0xDEADBEEF)看水位、把栈放到带 MPU 保护的独立区。

  3. 中断上下文的边界,是嵌入式最容易被追问塌的地方:ISR 抢占的是当前任务,运行在特权上下文里,所以不能阻塞、不能睡眠、不能等锁、不能跑长循环,printf 这类会走缓冲和信号量的调用尤其危险。ISR 该做的事只有两件:把数据或事件收下(读寄存器、拷进缓冲区)、把事件投递出去(给队列、放信号量、置标志),重活留给任务。临界区三种手段的取舍也要说清:关中断最硬但影响实时性(时间要尽可能短),关调度只挡任务间竞争、挡不住 ISR,拿锁最灵活但要盯持锁时间与死锁风险;只加 volatile 挡不住并发写。

  4. RTOS 要讲成设计选择,不是教材目录:任务状态(就绪、运行、阻塞、挂起)与调度时机(SysTick、任务让出、事件唤醒、中断退出)背下来只算入门,面试官想听「你项目里为什么这么拆」。同步原语按解决什么问题来分:二值信号量做事件通知,计数信号量管资源数量,互斥锁带所有权、能配合优先级继承保护临界区,消息队列传数据并解耦。优先级反转是低优先级任务持锁、被中优先级任务抢占、把高优先级任务堵在锁上的现象;互斥锁能配优先级继承(因为锁有明确所有者),普通信号量没有所有者语义,所以通常继承不了。根本解法还是缩短临界区、避免多锁嵌套。

  5. DMA 与 Cache 的一致性是必考追问:DMA 的价值是让外设与内存之间搬数据不占 CPU,换取吞吐与实时性。和 Cache 一起用时会出现「看起来没更新」,原因是 CPU 读的是 Cache、DMA 写的是内存,两边不同步。处理办法是按方向做 clean/invalidate(发送前把数据从 Cache 刷到内存,接收后把内存里的新数据作废掉对应 Cache 行),或者把 DMA 缓冲区放到 MPU 配置为不可 Cache 的区、或按 Cache 行对齐并回避伪共享。半传输中断的用途是双缓冲与流水处理:前半区满了就先处理前半,后半继续收,避免只在整块收完才动。轮询、中断、DMA 的选型按数据率、实时性要求和 CPU 余量来定,能给出具体场景的取舍就有说服力。

  6. 总线与驱动题按「时序 → 机制 → 异常 → 选型」答:UART 丢包先查波特率与时钟误差、收发中断是否被长临界区挡住、缓冲区大小与溢出标志;SPI 时序不对先查 CPOL/CPHA 四种模式、片选管理和时钟频率上限;I2C 卡死先看从机是否把 SDA 拉低不放(可用补时钟顶出)、上拉电阻与总线电容、地址是否冲突。重试不能瞎重试,要带超时、有限次数、退避与幂等。驱动侧要能讲清用户态与内核态的边界与一次系统调用的进入路径、字符设备框架(设备号、cdev、file_operations 里的 open/read/write/ioctl/release 各管什么),以及设备树解决的「板级信息与驱动代码分离」问题——从 DTS 节点到匹配 compatible、probe 拿到资源、注册设备这条链路要能顺着说下来。HardFault 的排查是固定五步:现场留信息(LR/PC/xPSR、栈回溯、备份寄存器写故障码)→ 判断类型(栈溢出、空指针、非对齐访问、非法指令)→ 复现 → 定位改动 → 补异常路径与日志。

  7. 准备标准与复习顺序:三层准备法:每道题按「定义一句说清 → 适用场景与相近概念对比 → 项目里怎么用/怎么炸」三层准备,只背第一层,追问基本扛不住。判断自己的位置也很直接:只会背定义算不会;能说清适用场景与区别算会用;能顺着讲到失败现象、排查路径和系统设计取舍才算熟悉。复习不要按「今天背 C、明天背 RTOS」割裂刷,按项目串题更有效——项目用了 I2C,就把时序、上拉、超时、中断与 DMA 选型一起过;每题录音 90 秒,讲不清的地方就是面试会卡的地方;手写两个最小题(结构体对齐计算、环形缓冲区)很多岗位会直接让你写。分岗加减法:MCU/RTOS 岗把 C 语言到总线这 16 题加第 20 题打穿,Linux 岗加重用户态内核态、字符设备与设备树三题,另补 platform 驱动与中断下半部。