[Linux驱动开发八] 网络设备简介

目录

一、基本概念

1.1 什么是网络设备?

二、网络设备驱动架构

2.1 网络协议接口层

2.2 网络设备接口层

2.3 设备驱动功能层

2.4 网络设备与媒介层

三、重要结构体说明

3.1 sk_buff结构体

3.1.1 sk_buff结构体定义

3.1.2 分配sk_buff

3.1.3 释放sk_buff

3.1.4 变更sk_buff

(1)skb_reserve()

(2)skb_put()

(3)skb_push()

(4) skb_pull()

3.1.5 sk_buff包的形成

3.2 net_device结构体

3.2.1 net_device结构体定义

3.2.2 申请net_device

3.2.3 删除net_device

3.2.4 注册net_device

3.2.5 注销net_device

3.3 net_device_ops结构体

四、总结 


一、基本概念

1.1 什么是网络设备?

        Linux系统中的设备可以分为字符设备、块设备和网络设备三大类。网络设备又叫网络接口,与字符设备一样,网络设备也是内核的特定数据结构中注册自己(字符设备为cdev结构体, 网络设备net_device结构体)。

        字符设备在 /dev 目录下会有对应设备文件节点并且在注册时会有设备号。网络设备没有对应设备节点和设备号,网络设备使用套接字来实现网络数据的接收和发送。

二、网络设备驱动架构

        Linux网络设备驱动程序的体系结构如图中黄色部分所示所示,从上到下可以划分为4层,依次为网络协议接口层、网络设备接口层、提供实际功能的设备驱动功能层以及网络设备与媒介层。

2.1 网络协议接口层

        最主要的功能就是给上层协议提供了透明的数据包发送和接收接口。当上层的协议需要发送数据包时,就会调用 dev_queue_xmit() 函数。当上层需要接收数据包时,就会调用netif_rx()函数。函数定义如下:

/*                   : dev_queue_xmit
 * @description      : 上层协议发送数据包时的调用接口
 * @param – skb      : 套接字缓冲区指针         
 * @return           : 0 成功;其他 失败
 */
int dev_queue_xmit(struct sk_buff *skb)
 
/*                   : netif_rx
 * @description      : 上层协议接收数据包时的调用接口
 * @param – skb      : 套接字缓冲区指针         
 * @return           : 0 成功;其他 失败
 */
int netif_rx(struct sk_buff *skb)

         其中,skb表示要发送的数据,是一个sk_buff的结构体指针。sk_buff是Linux网络驱动中重要的结构体。在发送数据时,网络数据都是以sk_buff保存的,各个协议层都会在sk_buff中添加自己的协议头,最终由底层驱动将sk_buff中的数据发送出去。在接收数据时,网络底层驱动将接收到的原始数据打包成sk_buff,然后发送给上层协议,上层协议会逐步去掉对应头部,然后将最终数据发送给用户。       

2.2 网络设备接口层

        网络设备接口层的主要功能是为千变万化的网络设备定义了统一、 抽象的数据结构 net_device 结构体,实现多种硬件在软件层次上的统一。
        Linux内核使用 net_device结构体表示一个具体的网络设备,网络驱动的核心就是初始化net_device 结构体中的各个成员变量,然后将初始化完成以后的net_device 注册到 Linux内核中。

2.3 设备驱动功能层

        对应net_device结构体中的设备驱动功能函数,例如xxx_open()、xxx_stop()、xxx_tx()等。
另一部分是中断处理函数,它负责接收硬件上的数据包并传给上层协议,主要函数有xxx_interrupt()和xxx_rx(),前者完成中断类型判断及处理,后者则将从硬件获得的数据进行封包并交给上层。

2.4 网络设备与媒介层

        对应实际的硬件设备,用来负责完成数据包发送和接收的物理实体, 设备驱动功能层的函数都在这物理上驱动的。

        对于初学者,在设计网络设备驱动程序时,我们主要的工作就是编写设备驱动功能层的相关函数以填充net_device数据结构并将net_device注册入内核。

三、重要结构体说明

3.1 sk_buff结构体

3.1.1 sk_buff结构体定义

         sk_buff结构体定义在 include/linux/skbuff.h 中,可以将其理解为指针的集合,其重要成员大致如下:

struct sk_buff {
       /* These two members must be first. */
       struct sk_buff        *next;      //指向下一个sk_buff结构体
       struct sk_buff        *prev;     //指向前一个sk_buff结构体
    ... ...
       unsigned int          len,         //数据包的总长度,包括线性数据和非线性数据
                            data_len,        //非线性的数据长度
                            mac_len;         //mac包头长度

    __u32          priority;          //该sk_buff结构体的优先级   

    __be16        protocol;           //存放上层的协议类型,可以通过eth_type_trans()来获取
       ... ...

      sk_buff_data_t              transport_header;    //传输层头部的偏移值
      sk_buff_data_t              network_header;     //网络层头部的偏移值
      sk_buff_data_t              mac_header;          //MAC数据链路层头部的偏移值

    sk_buff_data_t              tail;                    //指向缓冲区的数据包末尾
      sk_buff_data_t              end;                     //指向缓冲区的末尾
      unsigned char            *head,                   //指向缓冲区的协议头开始位置
                                  *data;                   //指向缓冲区的数据包开始位置
       ... ...
}

         sk_buff结构体中tail、end、head、data等字段指向的是真正的数据区。head和end指向sk_buff缓冲区的头部和尾部,data和tail指向实际数据的头部和尾部。

        网络协议栈(TCP/IP)每一层(传输层、网络层、网络接口层)都会在head和data之间填充协议头,在tail和end之间添加新的数据协议。在内核中,sk_buff结构体在各层协议之间传输不是通过拷贝sk_buff结构体,而是增加/移除协议头和移动指针来操作。

3.1.2 分配sk_buff

        我们想要使用sk_buff 必须先分配,通过alloc_skb 函数可进行分配,alloc_skb() 原型定义在include/linux/skbuff.h 中,如下所示:

/*                   : alloc_skb
 * @description      : 分配sk_buff时的调用接口
 * @param – size     : 要分配的大小,即skb数据段的大小
 * @param - priority : 为GFP MASK宏,如GFP_KERNEL、GFP_ATOMIC等       
 * @return           : sk_buff首地址 成功;NULL 失败
 */
static inline struct sk_buff *alloc_skb(unsigned int size,
                                        gfp_t priority)
{
        return __alloc_skb(size, priority, 0, NUMA_NO_NODE);
}

         此外,还常常使用netdev_alloc_skb来为设备申请一个用于接收的skb_buff,也定义在include/linux/skbuff.h中,如下所示:

/*                   : netdev_alloc_skb
 * @description      : 为设备分配用于接收数据的sk_buff时的调用接口
 * @param – dev      : 要给哪个设备分配sk_buff
 * @param - length   : 要分配的大小       
 * @return           : sk_buff首地址 成功;NULL 失败
 */
static inline struct sk_buff *netdev_alloc_skb(struct net_device *dev,
                                        unsigned int length)
{
        return __netdev_alloc_skb(dev, length, GFP_ATOMIC);
}

        alloc_skb()是通用的动态分配sk_buff的函数,可以在任何上下文中调用。它可以为sk_buff分配一个新的、空的缓冲区,并返回一个指向该缓冲区的指针。
        netdev_alloc_skb()是专门为网络设备驱动程序设计的sk_buff 分配函数,只能在接收数据包时的中断或软中断上下文中调用。它不仅可以为sk_buff 分配缓冲区,还会为该缓冲区预留额外的头部空间,以便设备驱动程序能够在发送数据包时填写必要的信息,例如 MAC 地址等。
        如果需要在任何上下文中动态分配sk_buff,那么应该使用 alloc_skb();如果需要在网络设备驱动程序中接收数据包时动态分配sk_buff,那么应该使用netdev_alloc_skb()。

3.1.3 释放sk_buff

        当使用完成以后就要释放掉 sk_buff,释放函数可以使用 kfree_skb(),函数定义在 include/linux/skbuff.c 中,如下所示:

/*                   : kfree_skb
 * @description      : 释放sk_buff时的调用接口
 * @param – skb      : 要释放的sk_buff   
 * @return           : 无返回值
 */
void kfree_skb(struct sk_buff *skb)

         此外,我们还可以使用dev_kfree_skb()来释放sk_buff,函数定义如下:

/*                   : dev_kfree_skb
 * @description      : 释放sk_buff时的调用接口
 * @param – skb      : 要释放的sk_buff   
 * @return           : 无返回值
 */
void dev_kfree_skb(struct sk_buff *skb)

         kfree_skb() 是通用的 sk_buff 释放函数,可以用于释放任何类型的sk_buff,包括网络协议栈中的、用户空间传输到内核的以及设备驱动程序中的。它会调用sk_buff的析构函数skb->destructor 来清理sk_buff相关的资源。

        dev_kfree_skb()是专门为设备驱动程序设计的sk_buff 释放函数,仅可以用于释放由设备驱动程序通过 netif_receive_skb() 或者 netif_rx() 接收到的 sk_buff。它不会调用 skb->destructor 函数,因为设备驱动程序通常不需要对sk_buff 中的数据进行特殊处理。

        如果需要释放网络协议栈中的 sk_buff,或者从用户空间传输到内核的 sk_buff,那么应该使用 kfree_skb();如果需要释放设备驱动程序接收到的 sk_buff,那么应该使用 dev_kfree_skb()

3.1.4 变更sk_buff

(1)skb_reserve()

        改函数用于调整缓冲区的头部大小,将data和tail同时向后移动n个字节,原型如下:

/*                   : skb_reserve
 * @description      : 同时后移tail和data指针的调用接口
 * @param – skb      : 要操作的sk_buff
 * @param – len      : 要增加的缓冲区头部大小     
 * @return           : 无
 */
static inline void skb_reserve(struct sk_buff *skb, int len);

(2)skb_put()

        该函数用于在尾部扩展skb_buff的数据区,将skb_buff的tail后移n个字节,使得skb_buff的len增加n个字节,原型如下:

/*                   : skb_push
 * @description      : 在尾部扩展sk_buff时的调用接口
 * @param – skb      : 要操作的sk_buff
 * @param – len      : 要增加的字节长度      
 * @return           : 扩展出来的那一段数据区首地址 */
unsigned char *skb_push(struct sk_buff *skb, unsigned int len);

(3)skb_push()

        该函数用于在头部扩展skb_buff的数据区,原型如下:

/*                   : skb_push
 * @description      : 在头部扩展skb_buff数据区
 * @param – skb      : 要操作的sk_buff
 * @param – len      : 要增加的字节数 
 * @return           : 扩展后新数据区的首地址
*/
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)

(4) skb_pull()

        该函数用于从skb_buff的数据区起始位置删除数据,原型如下:

/*                   : skb_pull
 * @description      : 从skb_buff数据区起始位置删除数据
 * @param – skb      : 要操作的sk_buff
 * @param – len      : 要删除的字节数 
 * @return           : 删除以后新的数据区首地址
*/
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)

3.1.5 sk_buff包的形成

        step1:  sk_buff数据区刚申请。此时head指针、data指针以及tail指针指向同一个地方。下一步就是预留协议头空间,使head、tail、data指针分离。

        step2 :调用skb_reserve()来使data和tail指针向下移动,空出部分空间来为后期添加协议信息。skb_reserve()的作用是预留空间(尽可能大的预留)。因为很多头部都有可选项,我们并不清楚头部可选项有多大,因此只能按照最大分配,预留的头部空间也不一定必须用完。当我们添加协议头信息时,data指针向上移动,增加数据时tail指针向下移动。此时还没有数据存储,所以data指针和tail指针相同。

        step3:存储数据。调用skb_put()来使tail指针下移空出空间添加数据,data指针和tail指针之间存放数据信息,即数据区向下扩大len个字节,并更新数据区长度len。

        step4:增加头部空间的协议头。调用skb_push()使data指针向上移动,添加协议头信息(TCP层添加TCP首部, IP层添加IP首部,链路层添加链路层首部)。

         通过上述分析可知:

        (1)head指向缓冲区的首地址,作为上边界;

        (2)end指向缓冲区尾地址,作为下边界;

        (3)data指针在数据包头封装和解封装的过程中移动指向各层的协议头;

        (4)skb_push函数将data指针向低地址方向移动(向上),完成协议头空间的占据;        

        (5)skb_pull函数将data指针向高地址方向移动(向下),完成协议头空间的解封装;

        (6)tail指针在增加应用层用户缓冲数据时移动,skb_put函数将tail指针向高地址方向移动(向下),完成用户数据空间的占据。

3.2 net_device结构体

3.2.1 net_device结构体定义

         net_device结构体定义在include/linux/netdevice.h 中。net_device重要成员如下所示:

struct net_device
{
       char               name[IFNAMSIZ];              //网卡设备名称
       unsigned long              mem_end;             //该设备的内存结束地址
       unsigned long              mem_start;            //该设备的内存起始地址
       unsigned long              base_addr;            //该设备的内存I/O基地址
       unsigned int          irq;                       //该设备的中断号

       unsigned char        if_port;                  //多端口设备使用的端口类型
    unsigned char        dma;                     //该设备的DMA通道
       unsigned long              state;                    //网络设备和网络适配器的状态信息

      struct net_device_stats* (*get_stats)(struct net_device *dev); //获取流量的统计信息                        //运行ifconfig便会调用该成员函数,并返回一个net_device_stats结构体获取信息

      struct net_device_stats  stats;      //用来保存统计信息的net_device_stats结构体

 
       unsigned long              features;        //接口特征,     
       unsigned int          flags; //flags指网络接口标志,以IFF_(Interface Flags)开头
//当flags =IFF_UP( 当设备被激活并可以开始发送数据包时, 内核设置该标志)、 IFF_AUTOMEDIA(设置设备可在多种媒介间切换)、IFF_BROADCAST( 允许广播)、IFF_DEBUG( 调试模式, 可用于控制printk调用的详细程度) 、 IFF_LOOPBACK( 回环)、IFF_MULTICAST( 允许组播) 、 IFF_NOARP( 接口不能执行ARP,点对点接口就不需要运行 ARP) 和IFF_POINTOPOINT( 接口连接到点到点链路) 等。

 
       unsigned        mtu;        //最大传输单元,也叫最大数据包

       unsigned short  type;     //接口的硬件类型

       unsigned short   hard_header_len;     //硬件帧头长度,一般被赋为ETH_HLEN,即14

 
    unsigned char   dev_addr[MAX_ADDR_LEN];      //存放设备的MAC地址

       unsigned long              last_rx;    //接收数据包的时间戳,调用netif_rx()后赋上jiffies即可

       unsigned long              trans_start;     //发送数据包的时间戳,当要发送的时候赋上jiffies即可

       unsigned char        dev_addr[MAX_ADDR_LEN];                //MAC地址

 
       int                 (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);
                                   //数据包发送函数, sk_buff就是用来收发数据包的结构体


    void  (*tx_timeout) (struct net_device *dev); //发包超时处理函数
    ... ...
}

         

3.2.2 申请net_device

        编写网络驱动的时候首先要申请 net_device,使用 alloc_netdev 函数来申请 net_device,这

是一个宏,宏定义如下:

#define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \ 
 alloc_netdev_mqs(sizeof_priv,name,name_assign_type,setup,1,1)

        alloc_netdev本质是 alloc_netdev_mqs()函数,该函数定义如下:

/*                        : alloc_netdev_mqs
 * @description           : 申请net_deivce
 * @param – sizeof_priv   : 私有数据块大小
 * @param – name          : 设备名 
 * @param – setup         : 回调函数,初始化设备后调用此函数 
 * @param – txqs          : 分配的发送队列数量
 * @param – rxqs          : 分配的接收队列数量
 * @return                : 成功返回申请到的net_device指针,失败返回NULL
*/
struct net_device *alloc_netdev_mqs(int      sizeof_priv,
                                    const    char *name,
                                    void     (*setup)(struct net_device *),
                                    unsigned int txqs,
                                    unsigned int rxqs)

3.2.3 删除net_device

        当我们注销网络驱动时需要释放掉之前已经申请过的net_deivce,释放函数为free_netdev()。原型如下:

/*                   : free_netdev
 * @description      : 删除net_device
 * @param – dev      : 要释放的net_device指针
 * @return           : 无
*/
void free_netdev(struct net_device *dev)

3.2.4 注册net_device

        net_device申请并初始化完成以后就要向内核注册net_device,要用函数register_netdev(),原型如下:

/*                   : register_netdev
 * @description      : 向内核注册net_device
 * @param – dev      : 要注册的net_device指针
 * @return           : 0 注册成功,负值 注册失败
*/
int register_netdev(struct net_device *dev)

3.2.5 注销net_device

        若要对注册的设备进行注销,则使用unregister_netdev(),原型如下:

/*                   : unregister_netdev
 * @description      : 向内核注销net_device
 * @param – dev      : 要注销的net_device指针
 * @return           : 无
*/
int unregister_netdev(struct net_device *dev)

3.3 net_device_ops结构体

        netdev_ops是net_device中重要的成员变量,是net_device_ops结构体指针类型,是网络设备的操作集,net_device_ops结构体定义在 include/linux/netdevice.h 中,net_device_ops结构体里面都是一些以"ndo_"开头的函数,需要我们自己去实现,其核心内容如下:

  struct net_device_ops { 
      int         (*ndo_init)(struct net_device *dev);     /*当第一次注册网络设备的时候此函数会执行*/
      void        (*ndo_uninit)(struct net_device *dev);     /*卸载网络设备的时候此函数会执行*/
      int         (*ndo_open)(struct net_device *dev);     /*打开网络设备的时候此函数会执行,网络驱动程序需要实现此函数,非常重要*/
      int         (*ndo_stop)(struct net_device *dev); 
      netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, 
                             struct net_device *dev);    /*当需要发送数据的时候此函数就会执行,此函数有一个参数为 sk_buff结构体指针,sk_buff结构体在Linux的网络驱动中非常重要,sk_buff保存了上层传递给网络驱动层的数据。也就是说,要发送出去的数据都存在了sk_buff中*/
      u16         (*ndo_select_queue)(struct net_device *dev, struct sk_buff *skb, void *accel_priv,select_queue_fallback_t fallback);     /*设备支持多传输队列的时候选择使用哪个队列*/
      void       (*ndo_change_rx_flags)(struct net_device *dev, 
                                 int flags); 
      void        (*ndo_set_rx_mode)(struct net_device *dev);    /*此函数用于改变地址过滤列表*/ 
      int         (*ndo_set_mac_address)(struct net_device *dev,  void *addr);     /*此函数用于修改网卡的 MAC 地址*/
      int         (*ndo_validate_addr)(struct net_device *dev);     /*验证 MAC 地址是否合法,*/
      int         (*ndo_do_ioctl)(struct net_device *dev, 
                              struct ifreq *ifr, int cmd);     /*用户程序调用 ioctl 的时候此函数就会执行,*/
      int         (*ndo_set_config)(struct net_device *dev, 
                                struct ifmap *map); 
      int         (*ndo_change_mtu)(struct net_device *dev, 
                            int new_mtu); /*更改MTU大小。*/
      int         (*ndo_neigh_setup)(struct net_device *dev, 
                                  struct neigh_parms *); 
     void       (*ndo_tx_timeout) (struct net_device *dev);     /*当发送超时的时候产生会执行,一般都是网络出问题了导
致发送超*/
... 
 #ifdef CONFIG_NET_POLL_CONTROLLER 
     void       (*ndo_poll_controller)(struct net_device *dev);     /*使用查询方式来处理网卡数据的收发。*/
     int         (*ndo_netpoll_setup)(struct net_device *dev, 
                              struct netpoll_info *info); 
     void       (*ndo_netpoll_cleanup)(struct net_device *dev); 
 #endif 
... 
     int         (*ndo_set_features)(struct net_device *dev, etdev_features_t features);     /*修改net_device的 features属性,设置相应的硬件属性*/
... 
 }; 

四、总结 

        本文简单介绍了网络设备驱动的基本概念,随后对网络设备开发过程中的一些重要结构体(net_device、sk_buff、net_device_ops)的定义以及相关用法作出了大致说明,为网络设备驱动的开发奠定了一定的基础。

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值