知识点复习
从内存布局到外设协议,从内核机制到驱动框架------一份面向校招/社招的嵌入式Linux岗位复习指南。
一、内存与指针:别让基础成为你的短板
嵌入式开发离不开对内存的精细控制,面试中指针和数组的区别、存储位置几乎是必考题。
1. 指针变量的存储位置
指针变量本身存放在哪里?这取决于它的存储类型:
| 存储类型 | 存放位置 |
|---|---|
| 局部指针(auto) | 栈(Stack)或寄存器 |
| 已初始化的全局/静态指针 | .data 段 |
| 未初始化的全局/静态指针 | .bss 段 |
⚠️ 常见陷阱 :指针指向的对象不一定是常量。如果指向字符串字面量(如 char *p = "hello"),字面量位于只读数据段(.rodata),修改它会触发段错误(Segmentation Fault) ,属于未定义行为。但指针本身是可以修改的------你可以让 p 指向别处。
2. 数组的存储与特性
以 char a[6] = "hello" 为例:
- 占 6 字节 (包含结尾
\0),编译器会把字符串字面量拷贝到数组自身的可写空间中 - 因此
a[i]可以修改 - 但数组名
a是常量地址,不能被重新赋值
数组的存储位置同样取决于存储类型:局部数组在栈上,全局/静态数组按是否初始化分布在 .data 或 .bss 段。
3. Linux 进程内存布局(经典图示)
高地址
├─────────────────────────────────
│ 内核空间(用户程序不可访问)
├─────────────────────────────────
│ 栈 (Stack) ← 局部变量、函数参数、返回地址
│ ↓ 向低地址增长
│
│ (中间空闲区域)
│
│ ↑ 向高地址增长
│ 堆 (Heap) ← malloc/new 动态分配
├─────────────────────────────────
│ .bss 段 ← 未初始化的全局/静态变量
├─────────────────────────────────
│ .data 段 ← 已初始化的全局/静态变量
├─────────────────────────────────
│ .rodata 段 ← 只读数据(字符串常量等)
├─────────────────────────────────
│ .text 段 ← 程序代码(机器指令)
低地址
4. 内存调试利器:Valgrind Memcheck
面试中常问"你怎么排查内存泄漏?"
Valgrind Memcheck 是答案之一。它的核心机制:
- 拦截
malloc/free等堆内存管理函数 - 在堆块前后插入 redzone(红区),记录每一块内存的可访问范围
- 基于动态二进制插桩 和影子内存技术
能检测的问题覆盖全面:
| 检测项 | 说明 |
|---|---|
| 非法读写 | 越界访问、使用已释放内存 |
| 未初始化值使用 | 读取未初始化的栈/堆变量 |
| 重复释放 | double free |
| 非法释放 | 释放非堆指针 |
| 内存泄漏 | 丢失引用的堆内存 |
二、常见数据结构:环形缓冲区
**环形缓冲区(Ring Buffer / Circular Buffer)** 是嵌入式系统中使用频率极高的数据结构:
- 固定大小、首尾相连的 FIFO(先进先出)结构
- 想象成一个圆环形数组
- 读写指针在缓冲区中循环移动,不会真正删除元素,只是覆盖旧数据
典型应用场景:串口数据接收缓冲、音频流缓冲、生产者-消费者模型。
三、外设通信协议:六大总线逐一击破
这是嵌入式面试的重头戏。面试官喜欢让你对比各种总线,或者让你手画时序图。
1. I2C ------ 两线制多主多从
| 特性 | 说明 |
|---|---|
| 信号线 | SCL(时钟)、SDA(数据) |
| 工作模式 | 半双工、同步、多主多从 |
| 速率 | 100k / 400k / 1M bps |
| 寻址 | 每个从设备有唯一地址 |
| Linux 框架 | I2C 子系统,i2c_client + i2c_driver 匹配 |
| 典型应用 | 传感器、EEPROM、触摸屏控制器 |
时序要点:
- 空闲时 SCL 和 SDA 均为高电平
- START:SCL 高电平时 SDA 由高→低
- STOP:SCL 高电平时 SDA 由低→高
- 数据在 SCL 低电平期间可变化,SCL 高电平期间必须稳定
- 每 8 位数据后跟 1 个 ACK/NACK 采样窗口(第 9 个时钟)
- 支持**重复起始(Repeated START)**切换读写方向
- 从设备可通过时钟拉伸(拉低 SCL)暂停传输
2. SPI ------ 四线制高速全双工
| 特性 | 说明 |
|---|---|
| 信号线 | SCLK、MOSI、MISO、CS |
| 工作模式 | 全双工、同步 |
| 速率 | 可达几十 MHz |
| 寻址 | 无地址概念,靠 CS 片选区分 |
| Linux 框架 | spi_master / spi_device / spi_driver |
| 典型应用 | Flash、LCD 控制器、高速 ADC、WiFi 模块 |
时序要点:
- 无统一起始/停止条件,靠 CPOL (时钟极性)和 CPHA(时钟相位)组合成 4 种模式
- Mode 0:CPOL=0, CPHA=0 → SCLK 空闲低、上升沿采样
- Mode 3:CPOL=1, CPHA=1 → SCLK 空闲高、上升沿采样
- 主从在每个 SCLK 周期同时移位:主发 1 位到 MOSI,从返回 1 位到 MISO
- CS 拉低开始传输,拉高结束;多字节传输时 CS 可保持低
- 无 ACK 机制,靠硬件保证可靠性
3. UART ------ 异步串口
| 特性 | 说明 |
|---|---|
| 信号线 | TX、RX(可选 RTS/CTS 流控) |
| 工作模式 | 全双工、异步 |
| 速率 | 双方约定波特率(误差通常 < 2%) |
| 寻址 | 点对点,无地址/仲裁机制 |
| Linux 框架 | TTY 子系统,/dev/ttyS* 或 /dev/ttyAMA* |
| 典型应用 | 串口终端、GPS、蓝牙模块、调试控制台 |
帧结构:
空闲(高) → 起始位(低, 1bit) → 数据位(5~9bit, LSB优先) → 奇偶校验(可选, 1bit) → 停止位(高, 1~2bit)
- 接收端用本地时钟 16 倍频采样起始位下降沿来同步
- 硬件流控(RTS/CTS)防止接收端溢出
4. CAN ------ 差分信号、高可靠性
| 特性 | 说明 |
|---|---|
| 信号线 | CAN_H、CAN_L(差分传输) |
| 工作模式 | 多主竞争、非破坏性仲裁 |
| 可靠性 | 自带 CRC 校验 + 错误重发 |
| Linux 框架 | SocketCAN,抽象为网络接口(如 can0) |
| 典型应用 | 汽车电子、工业控制、机器人 |
标准帧格式:
| 字段 | 长度 | 说明 |
|---|---|---|
| SOF | 1 bit | 帧起始(显性) |
| ID | 11/29 bit | 标识符,值越小优先级越高 |
| RTR | 1 bit | 远程请求帧标志 |
| IDE | 1 bit | 扩展帧标志 |
| DLC | 4 bit | 数据长度 0~8 |
| Data | 0~64 bit | 有效载荷 |
| CRC | 15+1 bit | 校验 |
| ACK | 2 bit | 接收方显性确认 |
| EOF | 7 bit | 帧结束(全隐性) |
5. GPIO ------ 最底层的引脚控制
| 特性 | 说明 |
|---|---|
| 工作模式 | 输入、输出、中断、PWM 复用 |
| Linux 框架 | GPIO 子系统,gpiod_get() / gpiod_set_value() |
| 用户空间 | /sys/class/gpio 或 libgpiod 工具 |
| 典型应用 | LED、按键、复位/使能信号 |
时序场景:
- 输入模式:实时反映外部电平状态
- 输出模式:写操作立即改变引脚驱动电平
- 中断模式:边沿/电平触发,外部跳变后数个时钟周期内上报
- Bit-bang 模式:通过精确延时软件模拟 I2C/SPI 时序
- PWM 复用:定时器硬件精确控制方波周期和占空比
6. ADC ------ 模拟量采集
| 特性 | 说明 |
|---|---|
| 功能 | 模拟电压 → 数字量 |
| Linux 框架 | IIO(Industrial I/O)子系统 |
| 用户接口 | /sys/bus/iio/devices/iio:device0/in_voltage0_raw |
| 典型应用 | 电池电量、温度采集、电位器输入 |
逐次逼近型(SAR ADC)工作时序:
- 采样阶段:采样开关将输入电压存到采样电容
- 逐位比较:从 MSB 到 LSB 逐位判决,每 1 位对应 1 个时钟周期
- N 位分辨率需 N 个比较周期 + 采样时间
- 转换完成置位 EOC(End of Conversion)
换算公式 :实际电压 = raw_value × (参考电压 / 2^n)
7. PWM ------ 数字方波等效模拟量
原理:硬件定时器驱动计数器累加,计数值 < 阈值输出高,≥ 阈值输出低,溢出归零循环。
- 占空比 = 高电平时间 ÷ 周期 → 决定等效输出电压/功率
- Linux PWM 子系统:
pwm_config()设置周期/占空值,pwm_enable()使能 - 用户空间:
/sys/class/pwm/ - 典型应用:LED 调光、电机调速、蜂鸣器、背光控制
8. 六大外设对比总结
| 维度 | I2C | SPI | UART | CAN | GPIO | ADC |
|---|---|---|---|---|---|---|
| 信号线数 | 2 | 4 | 2 | 2(差分) | 1+ | 1+ |
| 双工 | 半双工 | 全双工 | 全双工 | 半双工 | - | - |
| 速率 | 低速 | 高速 | 中速 | 中速 | - | - |
| 寻址 | 有 | CS片选 | 无(点对点) | ID仲裁 | - | - |
| 可靠性 | ACK | 硬件 | 校验可选 | CRC+重发 | - | - |
| 典型场景 | 传感器 | Flash | 调试终端 | 汽车/工控 | 控制 | 采集 |
四、内核机制:进程上下文 vs 中断上下文
这是嵌入式 Linux 驱动开发的核心概念,面试必问:
| 对比维度 | 进程上下文 | 中断上下文 |
|---|---|---|
| 进程归属 | 属于某个进程 | 无进程归属 |
| 能否睡眠 | ✅ 可以(可调用调度) | ❌ 绝对不能 |
| 访问用户空间 | ✅ 可以 | ❌ 不可以 |
| 调度 | 可能被抢占 | 不可抢占 |
| 典型场景 | 系统调用、驱动 read/write | 中断处理函数(top half) |
口诀:中断上下文"三不能"------不能睡眠、不能访问用户空间、不能调用会阻塞的函数。
📚 推荐参考资料
- 《Linux 设备驱动程序》(LDD3)
- 《ARM System Developer's Guide》
- 内核源码:
drivers/i2c/、drivers/spi/、drivers/tty/ - CSDN / 知乎嵌入式专栏
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 协议,转载请附上原文出处链接。