• 【lwip】06-网络接口层分析



    前言

    主要分析网络接口概念、网卡数据结构、网络接口、环回接口实现等等。

    参考:

    6.1 概念引入

    网络接口(以太网接口)是硬件接口(网络接口又可以称之为网卡)。

    LWIP 是软件那么而怎样让硬件和软件无缝连接起来呢?

    而且网卡又多种多样,怎样才能让 LWIP 使用同样的软件兼容不同的硬件平台?

    LWIP 中使用了一个netif结构体来描述网卡但是网卡是直接和硬件平台打交道的:

    • 用户提供最底层接口函数。

    • LWIP 提供统一的 API。

    • 举例:

      • 收:如网卡的初始化和网卡的收发数据,当 LWIP 底层得到数据之后,才会传入到内核中去处理。
      • 发:LWIP 内核需要发送数据包的时候,也需要调用网卡的发送函数。
    • LWIP 中的 etherneif.c 文件的函数通常为硬件打交道的底层函数,当有数据需要通过网卡接收或者发送数据的时候就会被调用,通过 LWIP 的协议栈的内部进行处理后,从应用层就能得到数据或者可以发送数据。

    小总结:

    简单来说,netif 是 LWIP 抽象出来的网卡,LWIP 协议栈可以使用多种不同接口,而 etherneif.c 文件则提供了以太网网卡 netif 的抽象,每个网卡有不同的实现方式,每户只需要修改 ethernetif.c 文件即可。

    小笔记:

    • 网卡就是一个水管接口,把上层tcpip协议栈和底层数据链路对接起来,使其数据流通。

      • 收:底层数据链路的数据,经过网卡,网卡把这些数据解析好,然后格式化为上层协议栈需要的数据格式,再把这些数据交给上层协议栈。
      • 发:上层协议栈的数据,经过网卡,网卡把这些数据解析好,然后格式化为底层数据链路需要的数据格式,再把这些数据交给底层让其发出去。
    • 网卡底层自由

      • 由于网络接口需要对接的上层包含了链路层的以太网帧处理ethnet_input()ethnet_output()
      • 所以网络接口底层对接的可以直接是自由数据,包括UART、SPI、以太网设备、GPRS、其它线程接过来的数据等等任何数据流。

    6.2 网络接口层数据概念流图


    图源自李柱明

    6.3 网卡收包程序流图


    图源自李柱明

    6.4 网卡数据结构

    6.4.1 struct netif源码

    6.4.2 字段分析

    6.4.2.1 网卡链表

    LWIP 使用链表来统一管理同一设备的多个网卡。

    netif.c 文件中定义两个全局指针 struct netif *netif_liststruct netif *netif_default

    • netif_list 就是网卡链表指针,指向网卡链表的首节点(第一个网卡)。
    • netif_default 默认网卡。

    6.4.2.2 网络 IP

    ip_addr:网络中的 IP 地址。

    netmask:子网掩码。

    gw:网关地址。

    6.4.2.3 接收数据函数input()

    该函数为网卡北向出口函数,是把底层数据包往协议栈送的,一般是ethernet_input(),送入arp协议处理,再往上送。

    6.4.2.4 网络IP层发送数据函数output()

    函数由 IP 层调用,在接口上发送数据包。用户需要编写该函数并使 output 指向它。

    通这个函数的处理步骤是首先解析硬件地址,然后发送数据包。

    对于以太网物理层,该函数通常是 etharp_output(),参数是 pbuf,netif,和 ip_addr 类型。

    注意:其中 ipaddr 代表要将数据包发送到的地址,但不一定数据包最终到达的 ip 地址。比如要发送 ip 数据报到一个并不在本网络的主机上,该数据包要被发送到一个路由器上,这里的 ipaddr 就是路由器的 ip 地址。

    6.4.2.5 链路层发送函数linkoutput()

    该函数和 output 类似,也需要用户自己实现一个函数,但是只有两个参数,一般是自定义函数 low_level_output()

    当需要在网卡上发送一个数据包时,该函数会被 ethernet_output() 函数调用,在底层发送数据。

    6.4.2.6 出口回调函数

    当 netif 被删除时调用此函数。

    6.4.2.7 用户私有数据

    由设备驱动程序设置并指向设备的状态信息,主要将网卡的某些私有数据传递给上层。

    6.4.2.8 最大传输单位

    6.4.2.9 链路硬件地址长度&地址

    6.4.2.10 网卡信息状态标志

    网卡状态信息标志位,是很重要的控制字段,它包括网卡的功能使能,广播使能,ARP 使能等重要控制位。

    6.4.2.11 网卡名字

    用于保存每一个网卡的名字,用两个字符的名字来标识。

    网卡使用的设备驱动的种类,名字由设备驱动来设置并且应该反映通过网卡表示的硬件的种类。

    如果两个网卡具有相同的网络名字,我们就用 num 字段来区分相同类别的不同网卡。

    6.4.2.12 网卡标识

    标识使用同种驱动类型的不同网卡。

    6.4.2.13 网卡提示

    网卡提示,有些网卡相关的数据保存空间。

    其数据结构:

    比如addr_hint在ARP协议中的用途就是,当当前网卡发送了一包数据时用到了ARP缓存表某条arp entry时,就会保存这个arp entry的索引到addr_hint中,下次这个网卡发送数据时,通过addr_hint获取索引后,直接从这条索引开始遍历ARP缓存表,加速组包时间。

    tci是用于VLAN组包中的字段值。

    6.5 netif 快速使用

    6.5.1 简要步骤

    1. 定义一个 netif 作为网卡设备结构体。

    2. 挂载到 netif_list 链表中:netif_add();

      1. 需要提供网卡初始化函数和网卡协议栈入口函数作为netif_add()的参数传入。
    3. 使能网卡底层(链路层):netif_set_link_up()

    4. 使能网卡上层(协议栈):netif_set_up()

    6.5.2 与 netif 相关的底层函数

    low_level_output()low_level_input()函数是网卡的南向直接操作函数,是对网卡设备的写、读处理。

    相当于网卡设备的驱动范畴的函数。

    主要 API:

    相关 API:(如以太网)

    6.6 网卡信息状态标志

    在网卡netif->flags;成员中体现。

    • NETIF_FLAG_UP

      • 网络接口上层是否被使能。
      • 属于一个软件(协议栈)就绪标志,表示lwip协议栈已经合法获取到该IP,lwip协议栈已经准备好接收处理这个网卡的数据了。
      • 相当于一个lwip协议栈内部与外部南向通信的开关阀。
    • NETIF_FLAG_BROADCAST:网络接口是否支持广播。

    • NETIF_FLAG_LINK_UP

      • 网络接口的底层链路是否被使能。
      • 属于一个硬件(链路层)就绪标志,表示当前网卡南向接口使用的数据链路硬件就绪。
    • NETIF_FLAG_ETHARP:网络接口是否支持ARP功能。

    • NETIF_FLAG_ETHERNET:网络接口是否是以太网设备。

    • NETIF_FLAG_IGMP:网络接口是否支持IGMP协议功能。

    • NETIF_FLAG_MLD6:网络接口是否支持MLD6功能。

    6.7 添加网卡netif_add()

    主要内容是:

    • 网卡数据结构内容配置;
    • 把这个网卡插入到网卡链表netif_list

    参数:

    • netif:网卡数据结构资源。
    • ipaddr:网卡IP地址。
    • netmask:网卡子网掩码。
    • gw:网卡网关。
    • state:用户自定义的一些数据。
    • init:网卡初始化函数。各种网卡的初始化不完全一样,所以又用户自己实现。
    • input:网卡北方北向接口。是把数据传入TCPIP协议栈的接口。

    6.8 网卡上层协议栈使能netif_set_up()

    netif_set_up()函数用于使能网卡上层协议栈。

    相当于打开网卡北向接口的开关阀,让网卡和协议栈的数据能够流通。

    对应标志位:NETIF_FLAG_UP。表示上层协议栈准备好了,也打通了网卡与协议栈的通道。

    6.9 网卡底层数据链路使能netif_set_link_up()

    netif_set_link_up()函数用于使能网卡底层数据链路。

    相当于打开网卡南向接口的开关阀,让网卡和底层数据链路的数据能够流通。

    对应标志位:NETIF_FLAG_LINK_UP。表示底层数据链路准备好了,也打通了网卡与底层数据链路的通道。

    因为在数据链路层新增了一个设备节点,所以需要在链路层做一些处理,通告下。

    比如发起ARP请求,宣告当前IP被我使用,给为同僚ARP缓存表有空间就存一下吧。

    链路层使能后,还需要通过回调通知其它业务,表明当前网卡状态更新了。

    6.10 以太网网卡伪代码分析

    参考lwip官方提供的ethernetif.c文件。

    6.10.1 相关API说明

    用户需要封装单个网卡底层数据链路相关的函数:

    • low_level_init()

      • 该函数用于初始化底层数据链路硬件设备,使其具备底层通信能力。
    • low_level_output()

      • 该函数用于底层数据链路发送数据,网卡的南向出口。
      • 收到的上层数据包时pbuf类型的。
    • low_level_input()

      • 该函数用于底层数据链路接收数据,网卡的南向入口。
      • 需要组装为pbuf类型再转交给上层。

    ethernetif_init():网卡初始化函数。封装这个接口是给netif_add()添加网卡时初始化当前网卡的。

    ethernetif_input():数据链路接收数据并传入TCPIP协议栈。

    发送的数据是由上层一层一层传下来的,相关接口也是层层调用即可。

    但是接收数据不一样,我们代码不知道数据什么时候来,所以需要创建一条线程,专门用于检索接收到的数据包,然后传入网卡,让网卡一层一层上传处理。

    lwip提供的参考,这个线程就是ethernetif_input()

    6.10.2 ethernetif_init()

    伪代码参考:

    6.10.3 ethernetif_input()

    该函数为应该独立线程,用于检测和接收底层数据链路数据,打包好发往TCPIP协议栈。

    6.10.4 low_level_init()

    6.11环回接口

    6.11.1 环回地址

    ipv4的127 网段的所有地址都称为环回地址,主要用来测试网络协议是否工作正常的作用。

    常用:

    IPv4:127.0.0.1

    IPv6:::1

    默认记录IPv4。

    环回地址表示“我自己”的意思。

    6.11.2 环回数据包流图


    图源自李柱明

    6.11.3 相关宏及API

    ENABLE_LOOPBACK:开启环回功能。

    LWIP_NETIF_LOOPBACK:每个网卡都有环回功能。

    LWIP_HAVE_LOOPIF:可创建独立的环回网卡。

    netif_init():lwip网卡模块初始化。如果开启了LWIP_NETIF_LOOPBACK,则会在该函数里面添加一个环回网卡loop_netif

    netif_loopif_init():环回网卡初始化。

    • 相当于以太网网卡的low_level_init()

    netif_loop_output():环回输出接口。

    • 如果数据包的目标IP和当前网卡的IP一致,则调用内部会调用该函数,而不是netif->output()

    • 如果是环回网卡,netif->output()也是该函数。

      • 所以如果目标IP和环回网卡一致,则直接调用netif_loop_output()
      • 如果不一致,则调用netif->output(),其实也是netif_loop_output()

    netif_poll():环回处理接口。

    • 单线程环境下:在应用程序中循环调用。
    • 多线程环境下:内部会通过消息,把netif_poll()转交给wlip内核线程去跑。
    • 目的是把环回数据包环回发到对应网卡的IP层处理。netif_loop_output()输入的数据传到netif->input()

    6.11.4 环回网卡初始化

    lwip网卡模块初始化调用netif_init()函数,只是在lwip协议栈初始化是调用,如果开启了环回LWIP_HAVE_LOOPIF功能,才会有操作,就是创建一个环回IP的网卡,仅此而已。

    对应到每一个实际网卡的初始化,在netif_add()会讲解。

    回环网卡初始化:netif_loopif_init()

    6.11.5 环回输出接口netif_loop_output()

    传入的pbuf是被以复制的显示缓存到对应网卡的loop_first链表中:

    • 先申请新的pbuf r,用于存储拷贝传入的pbuf p的数据。
    • 预测检查pbuf节点数是否超出限制LWIP_LOOPBACK_MAX_PBUFS
    • 拷贝pbuf数据。
    • 拼接到对应网卡的环回pbuf链表netif->loop_first中。
    • 如果网卡的环回pbuf链表还有数据,说明上次已经触发过netif_poll(),正常按理是不用再触发的。但是如果上次触发失败,这次还是要触发。
    • 如果网卡大的环回链表没有数据,说明当前netif_poll()没有在跑。需要触发netif_poll()
    • 如果netif_poll()触发失败,需要标记下,下次再发环回数据时补回触发。

    6.11.6 环回处理接口netif_poll()

    非可重入函数。

    • pbuf数据从网卡环回pbuf链表netif->loop_first中出队,把数据转交给IP模块入口ip_input()处理。

    __EOF__

  • 本文作者: 李柱明
  • 本文链接: https://www.cnblogs.com/lizhuming/p/16642648.html
  • 关于博主: 嵌入式从业者。RTOS、Linux lwip mbedtls...
  • 版权声明: 版权归博主所有
  • 声援博主: 学习笔记分享
  • 相关阅读:
    path正则匹配MatcherUtil
    【React-Native开发3D应用】React Native加载GLB格式3D模型并打包至Android手机端
    R17 redcap
    HK2学习之基础知识
    【DBA100人】胡中豪:国产分布式数据库DBA炼成记
    告诉大家4个常见的免费备份方法!
    电力电子转战数字IC20220815day60——uvm入门实验2
    图解大数据 | 海量数据库查询-Hive与HBase详解
    单调栈的性质和使用场景
    JAVA:实现Node节点类算法(附完整源码)
  • 原文地址:https://www.cnblogs.com/lizhuming/p/16642648.html