影石 嵌入式软件开发实习一面凉经:45 分钟八股连问
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
一、C 语言基础
- const、static、volatile 关键字作用
- 编译的四个过程,分别做什么
- 内存对齐是什么,有什么方法可以达成
二、通信协议
- I2C 通信原理
- I2C 可以挂多少个从机外设?
- I2C 总线速度
- I2C 通信过程中出现 NACK 现象,怎么排查?
- I2C tools 相关工具有哪些?有什么用?
- I2C 为什么默认电平为高?
- I2C 总线拉死怎么办?
- 什么场景会出现 I2C 总线拉死?
- I2C 读取从机的某个寄存器数据的流程
- SPI 通信原理
- 介绍 SPI 的 4 种模式
- SPI 出现数据传输错误,可能的原因有哪些?
三、Linux 底层
- 平台设备总线原理
- 字符设备驱动原理,有写过吗?框架是什么样的?
- cdev 介绍
- 设备树的结构
- dts、dtsi 分别是什么文件?
- 设备树节点的覆盖原则
- 底层驱动的中断机制是什么?
- 中断下半部能不能睡眠?
- 有做过中断下半部相关的函数实现吗?怎么做?
- 自旋锁、互斥锁是什么?
- uboot 启动流程
- Linux 内核启动流程
《参考解析》
-
C 语言基础题要按「作用域、存储期、优化约束」三条线答:
const是只读约束(编译期检查,不是真正常量;修饰指针时要分清const char *与char * const的区别),static管的是链接性与存储期(文件作用域内私有、函数内变量只初始化一次),volatile抑制编译器优化、保证每次都去内存读。嵌入式语境下 volatile 的三类典型场景要能说全:硬件寄存器、中断与任务共享的变量、无锁场景下的共享标志;紧接着必然被追问「加了 volatile 就线程安全吗」——不是,它不提供原子性,计数器这类读改写操作还要靠临界区或原子操作。 -
编译四过程是送分题,但追问会打到细节:预处理负责宏展开、头文件包含和条件编译(对应
-E),编译把预处理后的源文件做词法语法分析、优化并生成汇编(-S),汇编把汇编代码转成目标文件、生成符号表和重定位信息(-c),链接做符号解析与地址重定位、合并段、解析静态库动态库依赖,最终产出可执行文件或镜像。能顺口说出「未定义符号报错发生在链接期而不是编译期」「内联函数没被调用时不生成符号」,就说明真用过工具链,而不是背流程名。 -
内存对齐要「能算 + 能解释为什么 + 知道怎么控制」:规则是成员的偏移量必须是自身对齐数的整数倍,结构体总大小要补齐到最大对齐数的整数倍。控制手段有:按对齐数从大到小排列成员以省空间、必要时显式加 padding 字段、用
#pragma pack或__attribute__((packed))压缩(代价是访问可能变成多次总线读写、在部分架构上触发对齐异常,还会破坏 DMA 与 cache line 对齐)。反过来,做 DMA 缓冲区时往往要按 cache line 对齐,这是同一件事的另一面。 -
I2C 会被串起来问一长串,答案要成体系:物理层是 SDA、SCL 两根开漏线加上拉电阻,所以「默认电平为高」——器件只能把线拉低,靠上拉回到高,多个器件同时拉低就是线与;协议层是起始条件、7 位地址加读写位、每 8 位数据后第 9 个时钟的 ACK / NACK、停止条件,以及从机拉低 SCL 实现的时钟拉伸。可挂从机数量受两部分限制:7 位地址空间里去掉保留地址大约 112 个可用地址,工程上还要看总线电容(通常 400pF 上限)和地址冲突,所以实际常见是几个到十几个。速度分标准 100kHz、快速 400kHz、快速加 1MHz、高速 3.4MHz 几档。读寄存器流程是:起始 → 发从机地址(写)→ 发寄存器地址 → 重复起始 → 发从机地址(读)→ 连续读 → 非应答加停止。
-
NACK 和总线拉死是 I2C 最实际的两问:NACK 分几种情形——地址阶段没人应答(从机没上电、地址配错、器件损坏)、数据阶段从机没接住(从机忙、写进了只读寄存器、时序太快)、读操作最后一个字节主机主动给 NACK 表示结束。排查顺序是先确认地址与上电,再查上拉电阻和电平,再上逻辑分析仪看时序和有没有时钟拉伸,最后换一个从机排除器件问题。总线拉死的典型场景是从机复位时序错乱或供电掉电后把 SDA 拖在低位不放,恢复手段依次是从机复位脚复位、主机补最多 9 个时钟把它顶出去、重新初始化总线驱动,实在不行就整板断电。工具层面要能说出 i2cdetect(扫地址)、i2cget / i2cset(读写寄存器)、i2cdump(整片 dump)、i2ctransfer(组合报文)各自干什么。
-
SPI 四种模式是按 CPOL 和 CPHA 两两组合来的:CPOL 决定空闲时钟是高还是低,CPHA 决定在第一个还是第二个边沿采样,四组配置双方必须一致,否则采到的位整体错位——这正是「数据传输错误」最常见的软件原因。其余排查方向包括片选没拉低或多从机片选接错、时钟频率超过从机上限、模式不匹配、MISO 三态没人驱动、电源与地噪声、走线过长导致的边沿劣化。SPI 没有应答机制,出错了协议层看不出来,所以实际项目里要靠 CRC、长度字段或回读校验兜底,这也是它和 I2C 最大的工程差异。
-
设备树与 platform 模型是一条链:dts 是板级文件,dtsi 是可被 include 的公共部分(SoC 级定义通常放在这里),编译后生成 dtb 交给内核。节点规则要答覆盖原则:同一路径下同名节点会被合并、同名属性以最后加载的为准,板级 dts 因此能覆写 dtsi 里的属性,
status用来开关设备。驱动侧的链路是 platform 设备模型把设备与驱动解耦,驱动用of_match_table里的 compatible 与设备树节点匹配,匹配成功调用 probe,资源(寄存器基址、中断号、时钟、GPIO)都从设备树里取,不再硬编码。字符设备要能说出 cdev、file_operations、设备号的分配与register_chrdev到cdev_add的大致流程。 -
中断下半部与锁的边界是这组题里最容易露馅的地方:硬中断上下文不能睡眠,所以下半部机制按「能不能睡眠」分开——软中断和 tasklet 仍运行在中断上下文不能睡眠,workqueue 交给内核线程执行、可以睡眠,threaded irq 把中断处理线程化也是这个思路。锁的区分同理:自旋锁不睡眠、可用于中断上下文,进临界区前要
spin_lock_irqsave关本地中断避免死锁;互斥锁会睡眠,只能用在进程上下文,不能出现在中断里。启动流程按两段记:uboot 从 SPL 开始做最简初始化、拉起完整 uboot,完成板级与外设初始化后把内核和设备树加载到内存、通过 bootargs 传参并跳转;内核侧解压后进start_kernel,依次建立内存管理与各子系统、初始化驱动、挂载根文件系统,最后启动第一个用户态进程。