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 驱动模型分三层,我画了张图帮你理解:

Linux CAN 子系统分层架构 应用层 (User Space) cansend / candump / cangen / 自定义应用 套接字层 (Socket Layer) PF_CAN / RAW / BCM / ISO-TP 协议族 CAN 核心层 (Core Layer) 设备管理 / 协议注册 / 收发队列 / 错误处理 CAN 设备驱动层 (Device Driver) net_device_ops / 中断处理 / 寄存器操作 / DMA 硬件层 (CAN Controller + Transceiver) 数据流向:应用 → 内核 → 硬件

这张图你得记住。每次写驱动,脑子里都要过一遍这个分层。我见过有人把硬件操作逻辑直接写在应用层,那不乱套了吗?

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 驱动开发者的关注点

作为驱动开发者,你主要跟 设备驱动层 打交道。你需要实现:

  1. net_device_ops 回调:包括 open、stop、start_xmit、do_ioctl 等
  2. 中断处理函数:接收完成中断、发送完成中断、错误中断
  3. CAN 控制器初始化:波特率设置、滤波器配置、工作模式切换
  4. 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、中断、控制器初始化