2. Linux CAN 子系统架构:SocketCAN 框架、驱动模型与核心层
好,咱们进入第二章。这一章我打算把 Linux 下 CAN 总线的软件架构给你拆开揉碎了讲清楚。很多刚接触 CAN 驱动的朋友,上来就翻芯片手册、写寄存器,结果连数据怎么从应用层到物理层都理不清。我个人习惯是,先看框架,再动手。框架清楚了,代码怎么写都不会偏。
2.1 SocketCAN 框架:为什么是它?
早期 Linux 内核里,CAN 设备驱动各自为政,有的用字符设备,有的用网络设备,接口五花八门。后来社区统一了思路——把 CAN 总线当作一个网络设备来管理。这就是 SocketCAN 的核心理念。
说白了,你操作 CAN 接口,就像操作一个以太网卡一样。用 socket() 创建套接字,用 bind() 绑定接口,用 read()/write() 收发数据。应用层程序员几乎零学习成本。
SocketCAN 的核心优势:
- 统一的套接字接口(PF_CAN 协议族)
- 支持多个协议族(RAW、BCM、ISO-TP 等)
- 内核态与用户态数据隔离,安全性好
- 支持虚拟 CAN 设备(vcan),方便调试
我在项目中遇到过客户抱怨,说他们的 CAN 卡在 Windows 下用得好好的,换到 Linux 就抓瞎。其实就是没理解 SocketCAN 这套模型。一旦接受了「CAN 就是网络设备」这个设定,一切就顺了。
2.2 CAN 设备驱动模型:分层与职责
Linux CAN 驱动模型分三层,我画了张图帮你理解:
这张图你得记住。每次写驱动,脑子里都要过一遍这个分层。我见过有人把硬件操作逻辑直接写在应用层,那不乱套了吗?
2.3 CAN 核心层:承上启下的枢纽
核心层在 net/can/ 目录下,主要文件是 af_can.c 和 proc.c。它干的事包括:
- 协议注册:允许 RAW、BCM 等协议模块动态注册到核心层
- 设备管理:维护系统中所有 CAN 网络设备的链表
- 收发路由:根据 CAN ID 将接收到的帧路由到对应的套接字
- 统计与 procfs:通过
/proc/net/can/提供统计信息
调试小技巧: 我调试 CAN 驱动时,第一件事就是看 /proc/net/can/stats。如果接收计数不涨,说明中断或 DMA 有问题;如果错误计数狂飙,那多半是波特率或终端电阻没配好。
2.4 CAN 协议层:RAW、BCM 与 ISO-TP
SocketCAN 支持多种协议,最常用的三个:
| 协议 | 套接字类型 | 典型用途 | 特点 |
|---|---|---|---|
| RAW | SOCK_RAW | 原始 CAN 帧收发 | 最底层,直接操作 CAN ID 和数据 |
| BCM | SOCK_DGRAM | 广播管理报文 | 支持周期性发送、变化检测 |
| ISO-TP | SOCK_SEQPACKET | UDS 诊断、长数据分包 | 支持 ISO 15765-2 协议,自动分包/组包 |
你想想看,如果只是做简单的数据采集,RAW 协议就够了。但如果你要做 UDS 诊断,那就必须用 ISO-TP,因为它能自动处理超过 8 字节的长数据分包。我曾经在一个车载项目中,客户坚持用 RAW 协议发诊断报文,结果每次超过 8 字节就得自己拼包,折腾了半个月才搞定。后来换成 ISO-TP,一天就调通了。
2.5 驱动开发者的关注点
作为驱动开发者,你主要跟 设备驱动层 打交道。你需要实现:
- net_device_ops 回调:包括 open、stop、start_xmit、do_ioctl 等
- 中断处理函数:接收完成中断、发送完成中断、错误中断
- CAN 控制器初始化:波特率设置、滤波器配置、工作模式切换
- DMA 或 FIFO 管理:高效传输数据,避免 CPU 轮询
避坑指南: 我曾经在移植 MCP2515 驱动时,忽略了 SPI 时钟极性配置,导致 CAN 控制器间歇性丢帧。查了两天才发现是 SPI 模式不匹配。所以,硬件接口的时序参数一定要跟芯片手册逐位核对,别想当然。
2.6 核心数据结构速览
驱动里你会频繁遇到这几个结构体,我列个表给你:
| 结构体 | 所在头文件 | 作用 |
|---|---|---|
struct net_device |
linux/netdevice.h | 网络设备通用结构,CAN 设备也用它 |
struct can_priv |
linux/can/dev.h | CAN 设备私有数据,包含波特率、状态、时钟等 |
struct can_frame |
linux/can.h | CAN 帧结构,包含 ID、DLC、数据 |
struct sk_buff |
linux/skbuff.h | 套接字缓冲区,网络子系统通用数据载体 |
嗯,这里要注意 struct can_priv。它是 CAN 驱动开发者最常打交道的结构体。里面有个 struct can_bittiming,配置波特率全靠它。我习惯在驱动初始化时,先打印出这个结构体的值,确认硬件寄存器写对了没。
2.7 收发流程一句话总结
发送:应用层 write() → 套接字层封装 → 核心层路由 → 驱动层 start_xmit() → 硬件发送。
接收:硬件中断 → 驱动层读取 FIFO → 核心层分发 → 套接字层排队 → 应用层 read() 拿到数据。
这个流程你闭上眼睛能默写出来,才算真正入门了。
本章核心要点:
- SocketCAN 把 CAN 设备当作网络设备管理,接口统一
- 驱动模型分四层:应用 → 套接字 → 核心 → 驱动 → 硬件
- 核心层负责协议注册、设备管理、收发路由
- 协议层选 RAW 还是 ISO-TP,看业务需求
- 驱动开发者重点关注 net_device_ops、中断、控制器初始化