1. 嵌入式Linux网络基础:TCP/IP协议栈回顾、Linux网络设备模型、网络驱动架构概览

各位同学,咱们今天正式开课。做嵌入式Linux网络开发,很多人一上来就撸驱动代码,结果遇到问题抓瞎。我个人的习惯是,先花点时间把地基打牢。这一章,我们就来聊聊网络基础——TCP/IP协议栈、Linux网络设备模型,还有驱动架构的全貌。

说白了,你写的网络驱动,最终是要跟协议栈对话的。你不了解协议栈的脾气,驱动写得再花哨,也是白搭。

1.1 TCP/IP协议栈回顾

先问大家一个问题:一个数据包从你的网卡发出去,到对端应用程序收到,中间经历了什么?

嗯,这就是TCP/IP协议栈干的事。它分四层,每一层各司其职。

层级 核心协议 职责 嵌入式中的典型体现
应用层 HTTP, MQTT, CoAP 为用户提供网络服务 你的业务逻辑代码
传输层 TCP, UDP 端到端连接、流量控制 Socket编程接口
网络层 IP, ICMP, ARP 路由、寻址、分片 net/ipv4 目录下的代码
链路层 以太网, MAC 帧封装、介质访问 你的网卡驱动

我在项目中遇到过一个问题:一个IoT设备,用UDP发送数据,偶尔丢包。查了半天,发现是应用层发的包太大,IP层分片后,某个分片丢了,整个UDP报文就废了。你看,不懂网络层,你连问题都定位不了。

关键点:在嵌入式Linux中,协议栈是内核的一部分。驱动开发者最常打交道的是链路层和网络层。你写的 hard_start_xmit 函数,就是链路层把帧交给硬件的最后一步。

1.2 Linux网络设备模型

Linux内核怎么管理网络设备?它有一套统一的模型。你想想看,一个USB网卡和一个PCIe网卡,驱动完全不同,但内核怎么把它们统一管理起来的?

答案就是 struct net_device。这是所有网络设备的抽象。

// 一个简化的 net_device 结构体视角
struct net_device {
    char                name[IFNAMSIZ];     // 设备名,比如 eth0
    unsigned long       mem_end;            // 内存区域
    unsigned long       mem_start;
    unsigned long       base_addr;          // I/O 基地址
    unsigned int        irq;                // 中断号
    unsigned char       dev_addr[MAX_ADDR_LEN]; // MAC 地址

    const struct net_device_ops *netdev_ops; // 驱动操作函数集
    // ... 还有几百个字段
};

这个结构体很大,但别怕。你只需要关注几个核心字段:

  • name:设备名,用户空间看到的那个。
  • netdev_ops:驱动要实现的函数指针表,比如打开、关闭、发送、设置MAC等。
  • dev_addr:MAC地址,驱动初始化时要填好。

我曾经接手过一个项目,板子上有两个网卡,一个有线一个无线。结果系统启动后,两个设备名随机分配,导致应用层配置脚本总是配错。后来我查了内核文档,才知道可以通过 devicetree 的 aliases 节点固定设备名。嗯,这里要注意,设备模型不仅仅是数据结构,还涉及用户空间的udev规则和设备树。

我的建议:调试网络设备时,先 cat /proc/net/dev 看看设备列表和统计信息。这比看代码快多了。

1.3 网络驱动架构概览

好,现在我们把目光聚焦到驱动本身。一个典型的网络驱动,从上到下分三层:

  1. 内核协议栈接口层:通过 net_device_ops 与协议栈交互。
  2. 核心逻辑层:处理硬件寄存器、DMA描述符、中断处理。
  3. 硬件接口层:直接操作MAC/PHY芯片。

我画了一张图,帮你理清这个关系。

Linux 网络驱动架构分层图 内核 TCP/IP 协议栈 struct net_device (设备抽象 + net_device_ops) 驱动核心逻辑层 中断处理 | DMA 描述符管理 | 寄存器读写 NAPI 轮询 | 统计计数 硬件接口层 (MAC / PHY 操作) MII/RMII 总线 | SPI 接口 | MDIO 读写 物理硬件 (PHY / MAC 芯片) 数据发送路径 从上往下调用 数据接收路径 从下往上中断/轮询

这张图很直观。数据发送时,协议栈调用驱动的 ndo_start_xmit,驱动把数据塞进DMA描述符,硬件自动发出去。数据接收时,硬件产生中断,驱动在中断处理里把数据收上来,通过 netif_rx 或 NAPI 送给协议栈。

避坑指南:我曾经在中断处理里直接调用 netif_rx,结果在高负载下系统卡死。后来改用NAPI,问题解决。记住:中断上下文里别做耗时操作,能用NAPI就别用纯中断。

1.4 关键数据结构速览

写驱动,离不开几个核心数据结构。我列个表,你心里有个数。

数据结构 所在头文件 作用
struct net_device linux/netdevice.h 网络设备抽象,驱动的主结构体
struct net_device_ops linux/netdevice.h 驱动函数表,协议栈通过它调用驱动
struct sk_buff linux/skbuff.h 套接字缓冲区,数据包在内核中的载体
struct napi_struct linux/netdevice.h NAPI轮询结构体,用于高效收包
struct ethtool_ops linux/ethtool.h 用户空间 ethtool 工具的操作接口

这里我要特别提一下 sk_buff。这玩意儿是网络数据包在内核里的“快递箱”。你驱动收包时,要分配一个 sk_buff,把硬件收到的数据拷进去,然后发给协议栈。发包时,协议栈给你一个 sk_buff,你把数据取出来,塞给硬件。

核心原则:驱动不负责解析协议,只负责搬运数据。协议栈才是干脑力活的。

1.5 本章小结

这一章我们回顾了TCP/IP协议栈的四层模型,重点看了链路层和网络层。然后深入了Linux网络设备模型的核心——net_device。最后,用一张架构图理清了网络驱动的三层结构。

说白了,网络驱动就是协议栈和硬件之间的翻译官。你理解了协议栈的脾气,掌握了设备模型的套路,写驱动就是填函数表、配寄存器的事。