面灵AI→

影石 嵌入式软件开发实习一面凉经:45 分钟八股连问

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

《面试题目》

一、C 语言基础

  1. const、static、volatile 关键字作用
  2. 编译的四个过程,分别做什么
  3. 内存对齐是什么,有什么方法可以达成

二、通信协议

  1. I2C 通信原理
  2. I2C 可以挂多少个从机外设?
  3. I2C 总线速度
  4. I2C 通信过程中出现 NACK 现象,怎么排查?
  5. I2C tools 相关工具有哪些?有什么用?
  6. I2C 为什么默认电平为高?
  7. I2C 总线拉死怎么办?
  8. 什么场景会出现 I2C 总线拉死?
  9. I2C 读取从机的某个寄存器数据的流程
  10. SPI 通信原理
  11. 介绍 SPI 的 4 种模式
  12. SPI 出现数据传输错误,可能的原因有哪些?

三、Linux 底层

  1. 平台设备总线原理
  2. 字符设备驱动原理,有写过吗?框架是什么样的?
  3. cdev 介绍
  4. 设备树的结构
  5. dts、dtsi 分别是什么文件?
  6. 设备树节点的覆盖原则
  7. 底层驱动的中断机制是什么?
  8. 中断下半部能不能睡眠?
  9. 有做过中断下半部相关的函数实现吗?怎么做?
  10. 自旋锁、互斥锁是什么?
  11. uboot 启动流程
  12. Linux 内核启动流程

《参考解析》

  1. C 语言基础题要按「作用域、存储期、优化约束」三条线答:const 是只读约束(编译期检查,不是真正常量;修饰指针时要分清 const char * 与 char * const 的区别),static 管的是链接性与存储期(文件作用域内私有、函数内变量只初始化一次),volatile 抑制编译器优化、保证每次都去内存读。嵌入式语境下 volatile 的三类典型场景要能说全:硬件寄存器、中断与任务共享的变量、无锁场景下的共享标志;紧接着必然被追问「加了 volatile 就线程安全吗」——不是,它不提供原子性,计数器这类读改写操作还要靠临界区或原子操作。

  2. 编译四过程是送分题,但追问会打到细节:预处理负责宏展开、头文件包含和条件编译(对应 -E),编译把预处理后的源文件做词法语法分析、优化并生成汇编(-S),汇编把汇编代码转成目标文件、生成符号表和重定位信息(-c),链接做符号解析与地址重定位、合并段、解析静态库动态库依赖,最终产出可执行文件或镜像。能顺口说出「未定义符号报错发生在链接期而不是编译期」「内联函数没被调用时不生成符号」,就说明真用过工具链,而不是背流程名。

  3. 内存对齐要「能算 + 能解释为什么 + 知道怎么控制」:规则是成员的偏移量必须是自身对齐数的整数倍,结构体总大小要补齐到最大对齐数的整数倍。控制手段有:按对齐数从大到小排列成员以省空间、必要时显式加 padding 字段、用 #pragma pack 或 __attribute__((packed)) 压缩(代价是访问可能变成多次总线读写、在部分架构上触发对齐异常,还会破坏 DMA 与 cache line 对齐)。反过来,做 DMA 缓冲区时往往要按 cache line 对齐,这是同一件事的另一面。

  4. I2C 会被串起来问一长串,答案要成体系:物理层是 SDA、SCL 两根开漏线加上拉电阻,所以「默认电平为高」——器件只能把线拉低,靠上拉回到高,多个器件同时拉低就是线与;协议层是起始条件、7 位地址加读写位、每 8 位数据后第 9 个时钟的 ACK / NACK、停止条件,以及从机拉低 SCL 实现的时钟拉伸。可挂从机数量受两部分限制:7 位地址空间里去掉保留地址大约 112 个可用地址,工程上还要看总线电容(通常 400pF 上限)和地址冲突,所以实际常见是几个到十几个。速度分标准 100kHz、快速 400kHz、快速加 1MHz、高速 3.4MHz 几档。读寄存器流程是:起始 → 发从机地址(写)→ 发寄存器地址 → 重复起始 → 发从机地址(读)→ 连续读 → 非应答加停止。

  5. NACK 和总线拉死是 I2C 最实际的两问:NACK 分几种情形——地址阶段没人应答(从机没上电、地址配错、器件损坏)、数据阶段从机没接住(从机忙、写进了只读寄存器、时序太快)、读操作最后一个字节主机主动给 NACK 表示结束。排查顺序是先确认地址与上电,再查上拉电阻和电平,再上逻辑分析仪看时序和有没有时钟拉伸,最后换一个从机排除器件问题。总线拉死的典型场景是从机复位时序错乱或供电掉电后把 SDA 拖在低位不放,恢复手段依次是从机复位脚复位、主机补最多 9 个时钟把它顶出去、重新初始化总线驱动,实在不行就整板断电。工具层面要能说出 i2cdetect(扫地址)、i2cget / i2cset(读写寄存器)、i2cdump(整片 dump)、i2ctransfer(组合报文)各自干什么。

  6. SPI 四种模式是按 CPOL 和 CPHA 两两组合来的:CPOL 决定空闲时钟是高还是低,CPHA 决定在第一个还是第二个边沿采样,四组配置双方必须一致,否则采到的位整体错位——这正是「数据传输错误」最常见的软件原因。其余排查方向包括片选没拉低或多从机片选接错、时钟频率超过从机上限、模式不匹配、MISO 三态没人驱动、电源与地噪声、走线过长导致的边沿劣化。SPI 没有应答机制,出错了协议层看不出来,所以实际项目里要靠 CRC、长度字段或回读校验兜底,这也是它和 I2C 最大的工程差异。

  7. 设备树与 platform 模型是一条链:dts 是板级文件,dtsi 是可被 include 的公共部分(SoC 级定义通常放在这里),编译后生成 dtb 交给内核。节点规则要答覆盖原则:同一路径下同名节点会被合并、同名属性以最后加载的为准,板级 dts 因此能覆写 dtsi 里的属性,status 用来开关设备。驱动侧的链路是 platform 设备模型把设备与驱动解耦,驱动用 of_match_table 里的 compatible 与设备树节点匹配,匹配成功调用 probe,资源(寄存器基址、中断号、时钟、GPIO)都从设备树里取,不再硬编码。字符设备要能说出 cdev、file_operations、设备号的分配与 register_chrdev 到 cdev_add 的大致流程。

  8. 中断下半部与锁的边界是这组题里最容易露馅的地方:硬中断上下文不能睡眠,所以下半部机制按「能不能睡眠」分开——软中断和 tasklet 仍运行在中断上下文不能睡眠,workqueue 交给内核线程执行、可以睡眠,threaded irq 把中断处理线程化也是这个思路。锁的区分同理:自旋锁不睡眠、可用于中断上下文,进临界区前要 spin_lock_irqsave 关本地中断避免死锁;互斥锁会睡眠,只能用在进程上下文,不能出现在中断里。启动流程按两段记:uboot 从 SPL 开始做最简初始化、拉起完整 uboot,完成板级与外设初始化后把内核和设备树加载到内存、通过 bootargs 传参并跳转;内核侧解压后进 start_kernel,依次建立内存管理与各子系统、初始化驱动、挂载根文件系统,最后启动第一个用户态进程。