嵌入式精选 20 道必备八股文:C 语言、中断、RTOS、总线与驱动
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
一、C 语言与内存
- static、const、volatile 分别解决什么问题?嵌入式里最容易用错的是哪一个?
- 指针和数组有什么本质区别?什么时候「数组名」不能当成指针随便用?
- 结构体内存对齐规则是什么?怎么算 sizeof?#pragma pack 会带来什么代价?
- 栈、堆、全局/静态区、BSS、代码段分别放什么?栈溢出常见现象有哪些?
- 大小端怎么判断?网络字节序和本地字节序转换时,嵌入式项目里容易踩什么坑?
- 宏和内联函数有什么区别?带副作用的宏参数为什么危险?
二、中断、RTOS 与并发
- 中断响应流程是什么?抢占优先级和子优先级分别干什么?
- 中断上下文和任务上下文有何区别?ISR 里为什么不能做耗时操作、不能随便调阻塞 API?
- 什么是临界区?关中断、关调度、拿锁,分别适合什么场景?
- FreeRTOS 任务状态有哪些?调度何时发生?时间片和抢占怎么配合理解?
- 二值信号量、计数信号量、互斥锁、消息队列分别解决什么问题?怎么选型?
- 什么是优先级反转?为什么互斥锁常配合优先级继承,而普通信号量通常不行?
三、MCU、外设与通信
- MCU 从上电到跑到 main,大致经过哪些阶段?启动文件和链接脚本各自干什么?
- DMA 解决什么问题?和 Cache 一起用时,为什么数据可能「看起来没更新」?
- UART 丢包、SPI 时序不对、I2C 卡死,分别优先查什么?超时和重试怎么设计才不算瞎重试?
- 轮询、中断、DMA 三种采集方式怎么选?实时性和 CPU 占用怎么权衡?
四、Linux、驱动与工程闭环
- 用户态和内核态有什么区别?一次系统调用大致怎么进内核?
- 字符设备驱动的基本框架是什么?file_operations 里常见接口各自负责什么?
- 设备树解决了什么问题?一个外设从 DTS 到 probe 成功,路径怎么走?
- HardFault、死机、看门狗复位,你的标准排查路径是什么?现场该保留哪些信息?
面试官在后面接着追的方向
- 说 volatile:能替代内存屏障吗?和原子操作是什么关系?
- 说互斥锁:能在中断里拿吗?和二值信号量差在哪?
- 说 DMA:缓存一致性怎么保证?半传输中断干什么用?
- 说设备树:匹配失败怎么查?compatible 对不上是什么现象?
- 说 HardFault:怎么区分栈溢出、空指针、非法指令?
《参考解析》
-
volatile 的三类场景讲全,才算真懂:它约束的是编译器优化,不提供原子性、不提供内存屏障。三类典型场景是硬件寄存器(值可能被外设改写,编译器不能缓存进寄存器)、中断与任务共享的变量(ISR 里改、主循环里读,不加可能读到寄存器里的旧值)、以及无锁共享的标志位。高频追问「加了 volatile 是不是就线程安全了」——不是,读改写仍可能交错,计数类共享变量要靠临界区或原子操作;「能替代内存屏障吗」——也不能,屏障约束的是指令重排与可见性顺序。
-
内存布局与对齐要能手算:五个区的分工要说得清——代码段放指令与常量,已初始化的全局/静态变量在 data,未初始化或初值为零的在 BSS(不占镜像体积),栈用于函数调用与局部变量,堆由 malloc 管理。对齐规则是每个成员的偏移量必须是自身对齐数的整数倍、结构体总大小是最大对齐数的整数倍,对齐数取成员大小与编译器对齐参数中较小者;
#pragma pack能压小体积,代价是 CPU 可能要把一次访问拆成多次总线操作,某些架构上直接异常。栈溢出的典型现象是 HardFault、踩坏相邻变量、返回值莫名改变,定位手段是查 MSP/PSP 指针、填充魔数(如 0xDEADBEEF)看水位、把栈放到带 MPU 保护的独立区。 -
中断上下文的边界,是嵌入式最容易被追问塌的地方:ISR 抢占的是当前任务,运行在特权上下文里,所以不能阻塞、不能睡眠、不能等锁、不能跑长循环,
printf这类会走缓冲和信号量的调用尤其危险。ISR 该做的事只有两件:把数据或事件收下(读寄存器、拷进缓冲区)、把事件投递出去(给队列、放信号量、置标志),重活留给任务。临界区三种手段的取舍也要说清:关中断最硬但影响实时性(时间要尽可能短),关调度只挡任务间竞争、挡不住 ISR,拿锁最灵活但要盯持锁时间与死锁风险;只加 volatile 挡不住并发写。 -
RTOS 要讲成设计选择,不是教材目录:任务状态(就绪、运行、阻塞、挂起)与调度时机(SysTick、任务让出、事件唤醒、中断退出)背下来只算入门,面试官想听「你项目里为什么这么拆」。同步原语按解决什么问题来分:二值信号量做事件通知,计数信号量管资源数量,互斥锁带所有权、能配合优先级继承保护临界区,消息队列传数据并解耦。优先级反转是低优先级任务持锁、被中优先级任务抢占、把高优先级任务堵在锁上的现象;互斥锁能配优先级继承(因为锁有明确所有者),普通信号量没有所有者语义,所以通常继承不了。根本解法还是缩短临界区、避免多锁嵌套。
-
DMA 与 Cache 的一致性是必考追问:DMA 的价值是让外设与内存之间搬数据不占 CPU,换取吞吐与实时性。和 Cache 一起用时会出现「看起来没更新」,原因是 CPU 读的是 Cache、DMA 写的是内存,两边不同步。处理办法是按方向做 clean/invalidate(发送前把数据从 Cache 刷到内存,接收后把内存里的新数据作废掉对应 Cache 行),或者把 DMA 缓冲区放到 MPU 配置为不可 Cache 的区、或按 Cache 行对齐并回避伪共享。半传输中断的用途是双缓冲与流水处理:前半区满了就先处理前半,后半继续收,避免只在整块收完才动。轮询、中断、DMA 的选型按数据率、实时性要求和 CPU 余量来定,能给出具体场景的取舍就有说服力。
-
总线与驱动题按「时序 → 机制 → 异常 → 选型」答:UART 丢包先查波特率与时钟误差、收发中断是否被长临界区挡住、缓冲区大小与溢出标志;SPI 时序不对先查 CPOL/CPHA 四种模式、片选管理和时钟频率上限;I2C 卡死先看从机是否把 SDA 拉低不放(可用补时钟顶出)、上拉电阻与总线电容、地址是否冲突。重试不能瞎重试,要带超时、有限次数、退避与幂等。驱动侧要能讲清用户态与内核态的边界与一次系统调用的进入路径、字符设备框架(设备号、cdev、file_operations 里的 open/read/write/ioctl/release 各管什么),以及设备树解决的「板级信息与驱动代码分离」问题——从 DTS 节点到匹配 compatible、probe 拿到资源、注册设备这条链路要能顺着说下来。HardFault 的排查是固定五步:现场留信息(LR/PC/xPSR、栈回溯、备份寄存器写故障码)→ 判断类型(栈溢出、空指针、非对齐访问、非法指令)→ 复现 → 定位改动 → 补异常路径与日志。
-
准备标准与复习顺序:三层准备法:每道题按「定义一句说清 → 适用场景与相近概念对比 → 项目里怎么用/怎么炸」三层准备,只背第一层,追问基本扛不住。判断自己的位置也很直接:只会背定义算不会;能说清适用场景与区别算会用;能顺着讲到失败现象、排查路径和系统设计取舍才算熟悉。复习不要按「今天背 C、明天背 RTOS」割裂刷,按项目串题更有效——项目用了 I2C,就把时序、上拉、超时、中断与 DMA 选型一起过;每题录音 90 秒,讲不清的地方就是面试会卡的地方;手写两个最小题(结构体对齐计算、环形缓冲区)很多岗位会直接让你写。分岗加减法:MCU/RTOS 岗把 C 语言到总线这 16 题加第 20 题打穿,Linux 岗加重用户态内核态、字符设备与设备树三题,另补 platform 驱动与中断下半部。