Linux网络设备驱动框架

1. 网络设备驱动框架

1.1网际协议分层

优点: 便于封装;

Linux网络设备驱动框架_第1张图片

1.2 网络设备驱动程序结构分层

Linux网络设备驱动框架_第2张图片

  • 驱动功能层: 编写驱动功能层的相关函数,以填充net_device数据结构的内容,并注册到内核;通过hard_start_xmit ()启动发送,并通过网络设备上的中断触发接收操作,通过中断或POLL机制接收;
  • 设备与媒介层: 完成数据收发的物理实体,网卡被设备驱动层中的函数在物理上驱动;

1.3 网络协议接口层

主要功能: 向网络协议提供统一的数据包发送接口,上层任何形式的协议都通过dev_queue_xmit()发送,通过netif_rx()接收,都使用sk_buff作为数据的载体;

1.3.1 发送接口
/* 定义: net/core/dev.c */
int dev_queue_xmit(struct sk_buff *skb)
{
	return __dev_queue_xmit(skb, NULL);
}

/* 声明: include/linux/netdevice.h */
int dev_queue_xmit(struct sk_buff *skb);
1.3.2 接收接口

函数netif_rx是在网上收到数据包后,通过中断机制通知CPU而间接调用的中断处理例程;

/* 定义: net/core/dev.c */
int netif_rx(struct sk_buff *skb)
{
    trace_netif_rx_entry(skb);
    return netif_rx_internal(skb);
}
1.3.3 sk_buff组成

sk_buff: 套接字缓冲区,用于网络子系统各层之间传递数据,主要包括Data buffer ;

Data buffer 由两部分组成:

  • Packet data: 通过网卡收发的报文数据,包括head_room、data、end_room;
  • skb_shared_info: 作为packet data的补充,用于存储ip分片,其中sk_buff *frag_list是一系列子skbuff链表,而frag[]是由一组单独的page组成的数据缓冲区。

Linux网络设备驱动框架_第3张图片

struct sk_buff 结构体:

/* 定义: include/linux/skbuff.h */
struct sk_buff {
        /* sk_buff是双向链表:sk_buff结构都必须能够很快找到链表头节点 */
        struct sk_buff          *next;            // 指向后面的sk_buff结构体的指针
        struct sk_buff          *prev;            // 指向前一个sk_buff结构体的指针 
    
        ktime_t                 tstamp;
        struct sock             *sk;              // 
        struct net_device       *dev;             // 对应的net_device
        char                    cb[48] __aligned(8);
        unsigned long           _skb_refdst;
        unsigned int            len,              // 表示数据区的长度(tail-data)与分片结构体数据区的长度之和
                                data_len;         // 只表示分片结构体数据区的长度,所以len=(tail - data) + data_len
        __u16                   mac_len,          // mac报头的长度
                                hdr_len;
        __be16                  protocol;         // 包的协议类型,标识是IP包还是ARP包还是其他数据包
        __u16                   inner_transport_header;
        __u16                   inner_network_header;
        __u16                   inner_mac_header;
        __u16                   transport_header; // 指向传输包头
        __u16                   network_header;   // 指向传输层包头
        __u16                   mac_header;       // 指向链路层包头
    
        /* These elements must be at the end, see alloc_skb() for details.  */
        sk_buff_data_t          tail;             // 指向当前数据包的尾地址, 随着各个网络层的加工而变化
        sk_buff_data_t          end;              // 数据缓冲区的结束地址
        unsigned char           *head,            // 数据缓冲区的开始地址
                                *data;            // data指向当前数据包的首地址, 随着各个网路层的加工而变化
		... ...
};

Linux网络设备驱动框架_第4张图片

/* sk_buff链表的表头 */
struct sk_buff_head {
      /* These two members must be first. */
      struct sk_buff *next;  // 链表的下一个元素
      struct sk_buff *prev;  // 链表的上一个元素
      __u32           qlen;  // sk_buff结构的数量
      spinlock_t      lock;  // 自旋锁,防止对表的并发访问
};

sk_buff管理操作相关函数:

Linux网络设备驱动框架_第5张图片

/* 向skb尾部添加数据 */
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
    unsigned char *tmp = skb_tail_pointer(skb); /* 获取当前skb->tail */
    
    SKB_LINEAR_ASSERT(skb);        /* 要求skb数据区必须为线性 */
    skb->tail += len;              /* skb尾部增加len字节 */
    skb->len  += len;              /* skb数据总长度增加len字节 */

    /* 如果增加之后的tail > end ,则打印错误信息并进入oops */
    if (unlikely(skb->tail > skb->end))
        skb_over_panic(skb, len, __builtin_return_address(0));
    
    return tmp;  /* 返回添加数据的第一个字节位置 */
}

Linux网络设备驱动框架_第6张图片

/* 向skb数据区头部添加数据 */
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{
    skb->data -= len;      /* 数据区data指针前移len字节 */
    skb->len  += len;      /* 数据总长度增加len字节 */

    /* 如果增加之后的tail > end ,则打印错误信息并进入oops */
    if (unlikely(skb->data<skb->head))
        skb_under_panic(skb, len, __builtin_return_address(0));

    return skb->data;     /* 返回新的data指针 */
}

Linux网络设备驱动框架_第7张图片

/* 从数据区头部移除数据 */
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
{
    /* 根据移除数据长度判断函数调用 */
    return skb_pull_inline(skb, len);
}

/* 根据移除数据长度判断函数调用 */
static inline unsigned char *skb_pull_inline(struct sk_buff *skb, unsigned int len)
{
    /* 移除长度 > skb数据总长度,返回NULL; 否则,继续调用_skb_pull函数 */
    return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
}

/* 从skb数据区头部移除数据 */
static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)
{
    skb->len -= len;                  /* 数据总长度减去len字节 */
    BUG_ON(skb->len < skb->data_len); /* 数据总长度是否有异常 */

    /* data指针移动len字节, 返回移除之后新的data指针 */
    return skb->data += len;
}

Linux网络设备驱动框架_第8张图片

/* 创建头空间,只能对空skb使用 */
static inline void skb_reserve(struct sk_buff *skb, int len)
{
    skb->data += len;    /* 数据区data指针增加len字节*/
    skb->tail += len;    /* 数据区tail指针增加len字节 */
}

sk_buff缓冲区分配函数:

Linux网络设备驱动框架_第9张图片

在分配一个缓冲区时,需要分配两块内存(一个是缓冲区,一个是缓冲区的描述结构sk_buff)。

alloc_skb函数分为三部分: ①、从cache中分配内存; ②、初始化分配的skb的相关域; ③、处理fclone

/* 分配一个数据长度为size的network buffer(skb+data_buffer)(net/core/skbuff.c)*/
static inline struct sk_buff *alloc_skb(unsigned int size, gfp_t priority)
{
    return __alloc_skb(size, priority, 0, NUMA_NO_NODE);
}

/*******************************************************************************
 输入参数: size:skb的数据大小
          gfp_mask:不用解释
          fclone:表示从哪个cache中分配 fclone=1:从skbuff_fclone_cache上分配
                                      fclone=0:从skbuff_head_cache上分配 
*******************************************************************************/
struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask, int flags, int node)
{
   struct kmem_cache *cache;
   struct skb_shared_info *shinfo;
   struct sk_buff *skb;
   u8 *data;
   bool pfmemalloc;

   /* 通过flags的值判断是从fclone_cache还是head_cache中分配 */
   cache = (flags & SKB_ALLOC_FCLONE) ? skbuff_fclone_cache : skbuff_head_cache;

   /* 分配skb */
   skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
   if (!skb)  goto out;

   /* 数据对齐:按一级缓存的大小对齐 */
   size = SKB_DATA_ALIGN(size);

   /* 对齐后的数据加上skb_shared_info对齐后的大小 */
   size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));

   /* 分配数据区:大小为size * sizeof(struct skb_shared_info)的大小 */ 
   data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);
   if (!data)  goto nodata;

   /* 除了skb_shared_info以外的数据大小 */
   size = SKB_WITH_OVERHEAD(ksize(data));
   prefetchw(data + size);

   /* 初始化相关域 */
   memset(skb, , offsetof(struct sk_buff, tail));

   /* 总长度 = skb + data + skb_shared_info */
   skb->truesize = SKB_TRUESIZE(size);

   /* PFMEMALLOC分配标记 */
   skb->pfmemalloc = pfmemalloc;

   /* 设置引用计数为1 */
   atomic_set(&skb->users, 1);

   /*head data tail均指向数据区头部*/
   skb->head = data;
   skb->data = data;
   skb_reset_tail_pointer(skb);

   /* end指向数据区尾部 */
   skb->end = skb->tail + size;

   /* 初始化默认各层header偏移值 */
   skb->mac_header = (typeof(skb->mac_header))~0U;
   skb->transport_header = (typeof(skb->transport_header))~0U;
   
   /* 从end开始的区域为skb_shared_info */
   shinfo = skb_shinfo(skb);
   memset(shinfo, , offsetof(struct skb_shared_info, dataref));

   /* 设置引用计数为1 */
   atomic_set(&shinfo->dataref, );
   kmemcheck_annotate_variable(shinfo->destructor_arg);

   /* 如果flags==1,则需要多配一块内存,因此需要设置对应的fclone域(fclone_cache) */
   if (flags & SKB_ALLOC_FCLONE) 
   {
       struct sk_buff_fclones *fclones;
       /* 得到clone结构 */
        fclones = container_of(skb, struct sk_buff_fclones, skb1);
        kmemcheck_annotate_bitfield(&fclones->skb2, flags1);
        skb->fclone = SKB_FCLONE_ORIG;             /* 设置克隆标记 */
        atomic_set(&fclones->fclone_ref, 1);       /* 设置引用为1 */
        fclones->skb2.fclone = SKB_FCLONE_CLONE;   /* 设置skb2的克隆标记 */
    }

out:
    return skb;
nodata:
    kmem_cache_free(cache, skb);

    skb = NULL;
    goto out;
}

sk_buff缓冲区释放函数:

 /* 释放skb: kfree_skb用于失败时丢包释放 */
void kfree_skb(struct sk_buff *skb)
{
    if (unlikely(!skb))
        return;

    /* 引用为1,可直接释放 */
    if (likely(atomic_read(&skb->users) == 1))
	{
        smp_rmb();
	}
    else if (likely(!atomic_dec_and_test(&skb->users))) /* 对引用减1,并且判断,如果结果不为0,说明还有引用,返回 */
	{
        return;
	}
	
    trace_kfree_skb(skb, __builtin_return_address());

    //真正的skb释放
    __kfree_skb(skb);
}

/* 正常释放skb */
void consume_skb(struct sk_buff *skb)
{
    if (unlikely(!skb))
        return;

    if (likely(atomic_read(&skb->users) == 1))
	{
        smp_rmb();
    }
	else if (likely(!atomic_dec_and_test(&skb->users)))
	{
        return;
	}
	
    trace_consume_skb(skb);
    
    __kfree_skb(skb);
}

#define dev_kfree_skb(a)    consume_skb(a)

/* 释放skb */
void __kfree_skb(struct sk_buff *skb)
{
    skb_release_all(skb);  /* 释放skb附带的所有数据 */
    kfree_skbmem(skb);     /* 释放skb */
}

1.4 网络设备接口层

主要功能: 向协议接口层提供统一的用于描述具体设备(网络设备属性和操作的结构体struct net_device),这个结构从整体规划了具体操作硬件的设备驱动功能层的结构,是设备驱动功能层的各个函数的容器;

1.4.1 struct net_device

Linux内核中使用net_device来描述一个网络设备,net_device是设备接口层的核心, 也是编写网络驱动核心的对象。

/* include/linux/netdevice.h */
struct net_device {
    char                    name[IFNAMSIZ]; /* 网络设备的名称, 默认大小为32字符 */
    
	unsigned long           mem_end;        /* 网络设备所使用的共享内存起始地址 */
	unsigned long           mem_start;      /* 网络设备所使用的共享内存结束地址 */
	unsigned long           base_addr;      /* 网络设备的IO基地址 */
	int                     irq;            /* 设备使用的中断号   */
    
	unsigned long           state;
	
	struct list_head        dev_list;
	struct list_head        napi_list;
	struct list_head        unreg_list;
	struct list_head        close_list;
	netdev_features_t       features;        /* 用户层可以修改的特征 */
	netdev_features_t       hw_features;     /* 用户层不能修改的特征 */
	netdev_features_t       wanted_features;
    
	const struct net_device_ops *netdev_ops; /* 网络设备的操作函数*/
    
	const struct ethtool_ops *ethtool_ops;   /* ethtool的方法集 */
	const struct forwarding_accel_ops *fwd_ops;
	const struct header_ops *header_ops;    /* 协议头操作集 */
    
	unsigned int            flags;          /* 网络接口标志? */
	unsigned int            priv_flags;     /* Like 'flags' but invisible to userspace. See if.h for definitions. */
	unsigned short          gflags;                   
	unsigned short          padded;         /* How much padding added by alloc_netdev() */
	unsigned char           operstate;      /* RFC2863 operstate */
	unsigned char           link_mode;      /* mapping policy to operstate */
	unsigned char           if_port;          /* 指定多端口设备使用那一个端口 */
	unsigned char           dma;              /* 指定分配给设备的DMA通道 */
	unsigned int            mtu;              /* 最大传输单元(MTU)*/
	unsigned short          type;             /* 接口的硬件类型 */
	unsigned short          hard_header_len;  /* 以太网二层头长度(默认为14) */
	unsigned short          needed_headroom;
	unsigned short          needed_tailroom;
	unsigned char           perm_addr[MAX_ADDR_LEN]; /* permanent hw address */
	unsigned char           addr_assign_type; /* hw address assignment type */
	unsigned char           addr_len;       /* hardware address length      */
	struct kset             *queues_kset;
	int                     watchdog_timeo; /* used by dev_watchdog() */
};

(3)struct net_device_ops

网络设备的操作方法集。

struct net_device_ops {
	int          (*ndo_init)(struct net_device *dev);
	void         (*ndo_uninit)(struct net_device *dev);
	
	/* 打开/关闭网络接口设备,获得设备需要的I/O地址、IRQ、DMA通道等 */
	int          (*ndo_open)(struct net_device *dev);
	int          (*ndo_stop)(struct net_device *dev);
    
    /* 启动数据包的发送,当系统调用驱动程序的xmit函数时,需要向其传入一个sk_buff结构体指针,
       以使得驱动程序能获取从上层传递下来的数据包 */
	netdev_tx_t  (*ndo_start_xmit) (struct sk_buff *skb,struct net_device *dev);
	void         (*ndo_change_rx_flags)(struct net_device *dev,int flags);
	void         (*ndo_set_rx_mode)(struct net_device *dev);
    
    /* 用于设置设备的MAC地址 */
	int          (*ndo_set_mac_address)(struct net_device *dev,void *addr);
	int          (*ndo_validate_addr)(struct net_device *dev);
    
    /* 用于进行设备特定的I/O控制 */
	int          (*ndo_do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
    /* 用于配置接口,也可用于改变设备的I/O地址和中断号 */
	int          (*ndo_set_config)(struct net_device *dev, struct ifmap *map);
	int          (*ndo_change_mtu)(struct net_device *dev, int new_mtu);
	int          (*ndo_neigh_setup)(struct net_device *dev,struct neigh_parms *);
    
    /* 当数据包的发送超时时,ndo_tx_timeout()函数会被调用,
      该函数需采取重新启动数据包发送过程或重新启动硬件等措施来恢复网络设备到正常状态 */
	void         (*ndo_tx_timeout) (struct net_device *dev);
};

相关API

(1)分配/释放net_device

/*******************************************************************************
 功能描述: 分配及初始化net_device对象(linux/etherdevice.h)
 输入参数: sizeof_priv:私有数据大小(单位:字节数)
 输出参数:
 返 回 值: 失败:NULL    成功:net_device对象的首地址
*******************************************************************************/
struct net_device *alloc_etherdev(int sizeof_priv);

/*******************************************************************************
 功能描述: 分配及初始化net_device对象(linux/netdevice.h)
 输入参数: sizeof_priv:私有数据大小(单位:字节数)
          name:物理接口名("名称%d")
          name_assign_type:NET_NAME_UNKNOWN
          setup:初始化函数
 输出参数:
 返 回 值: 失败:NULL    成功:net_device对象的首地址
*******************************************************************************/
struct net_device *alloc_netdev(int sizeof_priv, const char *name,
                                unsigned char name_assign_type, void (*setup)(struct net_device *));

/* 释放 */
void free_netdev(struct net_device *dev);

(2)以太网的初始化

在初始化一个以太网设备的时候应该被调用,主要作用是针对以太网标准对net_device对象进行初始化。

void ether_setup(struct net_device *dev);

(3)注册/注销net_device

int register_netdev(struct net_device *dev);    /* 注册 */
void unregister_netdev(struct net_device *dev); /* 注销 */

(4)开始/停止发送队列

void netif_start_queue(struct net_device *dev); /* 开启发送队列 */
void netif_stop_queue(struct net_device *dev);  /* 停止发送队列 */

4. 网络设备的中断处理函数

中断处理函数

是网络设备媒介层相设备驱动功能层发送数据的接口, 网卡接收到数据是通过中断的方式上报的, 所以网络驱动中的中断处理函数就是第一时间队接收到的数据进行处理的地方,这个函数最终一定要调用netif_rx()将收到的数据上报到协议接口层。

你可能感兴趣的:(Linux,linux,网络,运维)