• 网络驱动的核心:
    1、就是初始化 net_device 结构体中的各个成员变量,
    2、然后将初始化完成以后的 net_device 注册到 Linux 内核中

1、网络设备(用net_device结构体)

/*
@  net_device 结构体
*/struct net_device { char                  name[IFNAMSIZ]; /*网络设备名字*/struct hlist_node     name_hlist; char                  *ifalias; unsigned long        mem_end;/*共享内存结束地址*/unsigned long       mem_start;/*共享内存起始地址*/unsigned long         base_addr; /*网络设备IO地址*/int               irq; /*网络设备的中断号*/atomic_t            carrier_changes; unsigned long         state; struct list_head      dev_list; /*全局网络设备列表*/struct list_head      napi_list;/*napi机制==napi设备的列表入口*/ struct list_head      unreg_list;/*注销网络设备的列表入口*/ struct list_head      close_list; /*关闭的网络设备列表入口*/
... const struct net_device_ops *netdev_ops; /*网络设备的操作集函数*/const struct ethtool_ops   *ethtool_ops; /*网络管理工具相关函数集*/#ifdef CONFIG_NET_SWITCHDEV const struct swdev_ops     *swdev_ops; #endif const struct header_ops     *header_ops; /*头部的相关操作函数集,比如创建、解析、缓冲等*/unsigned int          flags; /*网络接口标志*/
...  unsigned char         if_port; /*指定接口的端口类型*/unsigned char         dma; /*网络设备所使用的DMA通道*/unsigned int          mtu; /*网络最大传输单元*/unsigned short        type;/*指定 ARP模块的类型*/ unsigned short        hard_header_len; unsigned short        needed_headroom; unsigned short        needed_tailroom; /* Interface address info. */ unsigned char         perm_addr[MAX_ADDR_LEN]; /*永久的硬件地址*/unsigned char         addr_assign_type; unsigned char         addr_len; /*硬件地址长度。*/
... /* * Cache lines mostly used on receive path (including
eth_type_trans()) */ unsigned long         last_rx; /*最后接收的数据包时间戳,记录的是jiffies*//* Interface address info used in eth_type_trans() */ unsigned char         *dev_addr; /*硬件地址,是当前分配的MAC 地址*/#ifdef CONFIG_SYSFS struct netdev_rx_queue  *_rx; /*接收队列*/unsigned int          num_rx_queues; /*接收队列数量*/unsigned int          real_num_rx_queues; /*当前活动的队列数量*/#endif
... /* * Cache lines mostly used on transmit path */ struct netdev_queue  *_tx  /*发送队列*/ ____cacheline_aligned_in_smp; unsigned int          num_tx_queues; /*发送队列数量*/unsigned int          real_num_tx_queues; /*当前有效的发送队列数量。*/struct Qdisc          *qdisc; unsigned long         tx_queue_len; spinlock_t          tx_global_lock; int               watchdog_timeo;
... /* These may be needed for future network-power-down code. */* * trans_start here is expensive for high speed devices on S* please use netdev_queue->trans_start instead. */ unsigned long         trans_start; /*最后的数据包发送的时间戳,记录的是jiffies*/
... struct phy_device   *phydev; /*对应的PHY 设备*/struct lock_class_key *qdisc_tx_busylock; }; /*
@  申请net_device ===11111111111111111111111111111111
*/
#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函数*//*
@  sizeof_priv:私有数据块大小。
@  name:设备名字
@  setup:回调函数,初始化设备的设备后调用此函数
@  txqs:分配的发送队列数量
@  rxqs:分配的接收队列数量
@  如果申请成功的话就返回申请到的 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); /*事实上网络设备有多种,大家不要以为就只有以太网一种,比如光纤分布式数据接口(FDDI)、以太网设备(Ethernet)、红外数据接口(InDA)、高性能并行接口(HPPI)、CAN 网络等*//*
@  本章讲解的以太网,针对以太网封装的net_device申请函数是alloc_etherdev,这也是一个宏
@  alloc_etherdev 最终依靠的是 alloc_etherdev_mqs 函数
*/#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1) #define alloc_etherdev_mq(sizeof_priv, count) alloc_etherdev_mqs(sizeof_priv, count, count) /*
@  alloc_etherdev 最终依靠的是 alloc_etherdev_mqs 函数
*/struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs, unsigned int rxqs) { return alloc_netdev_mqs(sizeof_priv, "eth%d", NET_NAME_UNKNOWN,ether_setup, txqs, rxqs); /*申请net_device,*/} /*
@  对是申请的设备进行初始化
@  ether_setup函数会对 net_device做初步的初始化
*/void ether_setup(struct net_device *dev) { dev->header_ops       = &eth_header_ops; dev->type           = ARPHRD_ETHER; dev->hard_header_len = ETH_HLEN; dev->mtu              = ETH_DATA_LEN; dev->addr_len         = ETH_ALEN; dev->tx_queue_len     = 1000; /* Ethernet wants good queues *dev->flags          = IFF_BROADCAST|IFF_MULTICAST; dev->priv_flags       |= IFF_TX_SKB_SHARING; eth_broadcast_addr(dev->broadcast); } /*
@  删除net_device(net_device)=====22222222222222222222222222222222222222222
@  注销网络驱动的时候需要释放掉前面已经申请到的 net_device
@  dev:要释放掉的net_device指针。
@  返回值:无
*/
void free_netdev(struct net_device *dev) /*
@  注册net_device ==33333333333333333333333333333333333333
@  net_device申请并初始化完成以后就需要向内核注册net_device,要用到函数register_netdev
@  dev:要注册的net_device指针
@  返回值:0 注册成功,负值 注册失败。
*/
int register_netdev(struct net_device *dev) /*
@  注销net_device ==44444444444444444444444444444444444
@  dev:要注销的net_device指针
@  返回值:无
*/
void unregister_netdev(struct net_device *dev)

2、网络设备的操作集( net_device_ops结构体 )

/*
@  定义在 include/linux/netdevice.h 文件中
@  net_device_ops结构体里面都是一些以“ndo_”开头的函数,这些函数就需要网络驱动编写人员去实现
@  net_device_ops 结构体
*/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属性,设置相应的硬件属性*/
... }; /*
@  :ndo_open 函数,:ndo_stop 函数==以NXP的I.MX 系列SOC 网络驱动为例,会在此函数中做如下工作
*/
·使能网络外设时钟。
·申请网络所使用的环形缓冲区。
·初始化MAC 外设。
·绑定接口对应的 PHY。
·如果使用NAPI 的话要使能NAPI模块,通过 napi_enable函数来使能。
·开启PHY。
·调用netif_tx_start_all_queues来使能传输队列,也可能调用netif_start_queue函数。
·…… do_stop 函数,关闭网络设备的时候此函数会执行,网络驱动程序也需要实现此
函数。以NXP的I.MX 系列SOC 网络驱动为例,会在此函数中做如下工作:
·停止PHY。
·停止NAPI功能。
·停止发送功能。
·关闭MAC。
·断开PHY 连接。
·关闭网络时钟。
·释放数据缓冲区。 ·……

3、sk_buff结构体

  • 网络是分层的,对于应用层而言不用关系具体的底层是如何工作的,只需要按照协议将要发送或接收的数据打包好即可
  • 打包好以后都通过dev_queue_xmit函数将数据发送出去,接收数据的话使用netif_rx函数即可
/*
@  定义在 include/linux/netdevice.h 中
@  函数用于将网络数据发送出去
@  skb: 要发送的数据,这是一个sk_buff结构体指针,sk_buff是Linux网络驱动中一个非常重要的结构体,网络数据就是以 sk_buff 保存的,各个协议层在 sk_buff 中添加自己的协议头,最终由底层驱动讲sk_buff中的数据发送出去
@          网络数据的接收过程恰好相反,网络底层驱动将接收到的原始数据打包成 sk_buff,然后发送给上层协议,上层会取掉相应的头部,然后将最终的数据发送给用户
@  返回值:0 发送成功,负值 发送失败。
*/
static inline int dev_queue_xmit(struct sk_buff    *skb) /*
@  上层接收数据的话使用netif_rx函数.但是最原始的网络数据一般是通过轮询、中断或NAPI的方式来接收
@  skb:保存接收数据的 sk_buff
@  返回值:NET_RX_SUCCESS 成功,NET_RX_DROP 数据包丢弃。
*/
int netif_rx(struct sk_buff *skb) 
  • sk_buff 是 Linux 网络重要的数据结构,用于管理接收或发送数据包
/*
@  定义在 include/linux/skbuff.h 中
@   sk_buff 结构体
*/struct sk_buff { union { struct { /* These two members must be first. */ struct sk_buff        *next; struct sk_buff        *prev; /*构成一个双向链表*/union { ktime_t         tstamp; /*数据包接收时或准备发送时的时间戳*/struct skb_mstamp skb_mstamp; }; }; struct rb_node  rbnode; /* used in netem & tcp stack */ }; struct sock         *sk; /*当前 sk_buff所属的Socket*/struct net_device     *dev; /*当前 sk_buff从哪个设备接收到或者发出的*/char                cb[48] __aligned(8); /*cb 为控制缓冲区,不管哪个层都可以自由使用此缓冲区,用于放置私有数据。 */unsigned long         _skb_refdst; void                (*destructor)(struct sk_buff *skb); /*当释放缓冲区的时候可以在此函数里面完成某些动作*/
.... unsigned int          len, data_len; /*:len为实际的数据长度*/__u16               mac_len, hdr_len; /*:mac_len 为连接层头部长度,也就是 MAC 头的长度*/
.... __be16              protocol; /*protocol协议*/__u16               transport_header; /*传输层头部*/__u16               network_header; /*网络层头部*/__u16               mac_header; /*链接层头部*//* private: */ __u32               headers_end[0]; /*缓冲区的尾部*//* public: */ /* 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; /*:head 指向缓冲区的头部,data 指向实际数据的头部*/unsigned int          truesize; atomic_t            users; };
  • 再上述结构体中的缓冲区的head、end和实际数据区的data、tail
/*===11111111111111111111111111111111111111111111111111
@  分配sk_buff
@  定义在include/linux/skbuff.h中
@  size:要分配的大小,也就是skb数据段大小
@  priority:为GFP MASK宏,比如GFP_KERNEL、GFP_ATOMIC 等
@  返回值:分配成功的话就返回申请到的 sk_buff首地址,失败的话就返回 NULL
*/
static inline struct sk_buff *alloc_skb(unsigned int    size, gfp_t      priority)
/*
@  定义在include/linux/skbuff.h中
@  在网络设备驱动中常常使用 netdev_alloc_skb 来为某个设备申请一个用于接收的 skb_buff,
@  dev:要给哪个设备分配 sk_buff。
@  length:要分配的大小。
@  返回值:分配成功的话就返回申请到的 sk_buff首地址,失败的话就返回 NULL
*/
static inline struct sk_buff *netdev_alloc_skb(struct net_device   *dev, unsigned int      length) /*=======222222222222222222222222222222222222222222222222
@  释放sk_buff
@  定义在include/linux/skbuff.c中
@  skb:要释放的sk_buff。
@  返回值:无
*/
void kfree_skb(struct sk_buff *skb) /*=======33333333333333333333333333333333333333333333333
@  skb_put、skb_push、sbk_pull和 skb_reserve 四个函数用于变更 sk_buff
*/
/*函数用于在尾部扩展 skb_buff的数据区,也就将 skb_buff 的 tail 后移 n 个字节,从而导致 skb_buff 的 len 增加 n 个字节
@  skb:要操作的sk_buff
@  len:要增加多少个字节。
@  返回值:扩展出来的那一段数据区首地址
*/
unsigned char *skb_put(struct sk_buff   *skb, unsigned int   len)
/*  skb_push函数用于在头部扩展 skb_buff的数据区
@  skb:要操作的sk_buff
@  要增加多少个字节
@  返回值:扩展完成以后新的数据区首地址。
*/
unsigned char *skb_push(struct sk_buff    *skb, unsigned int   len)
/*  sbk_pull函数用于从 sk_buff的数据区起始位置删除数据
@  skb:要操作的sk_buff
@  len:要删除的字节数。
@    返回值:删除以后新的数据区首地址
*/
unsigned char *skb_pull(struct sk_buff    *skb, unsigned int   len
/*  sbk_reserve函数用于调整缓冲区的头部大小
@  skb:要操作的sk_buff
@  len:要增加的缓冲区头部大小
@  返回值:无。
*/
static inline void skb_reserve(struct sk_buff    *skb, int    len)
  • 为了更好的解释上面四个函数,下面是结构示意图:



    4、网络NAPI处理机制 =====很重要的

  • Linux 里面的网络数据接收也轮询和中断两种,
    1、中断的好处就是响应快,数据量小的时候处理及时,速度、快,但是一旦当数据量大,而且都是短帧的时候会导致中断频繁发生,消耗大量的CPU处理时间在中断自身处理上

    2、轮询恰好相反,响应没有中断及时,但是在处理大量数据的时候不需要消耗过多的CPU 处理时间。

  • linux在这两个处理方式的基础上,提出了另外一种网络数据接收的处理方法:NAPI(New API),NAPI 是一种高效的网络处理技术.

  • 核心思想:中断(用来唤醒数据接收服务程序)+ 轮询(在接受服务程序中采用POLL的方法来轮询处理数据)

  • 目前 NAPI 已经在 Linux 的网络驱动中得到了大量的
    应用

  • 如何在驱动中使用 NAPI处理机制

/*初始化NAPI==1111111111111111111111111111111111111111111111
@  定义在 net/core/dev.c中
@  初始化一个 napi_struct实例
@  dev:每个NAPI 必须关联一个网络设备,此参数指定NAPI要关联的网络设备。
@  napi:要初始化的 NAPI实例
@  poll: NAPI所使用的轮询函数,非常重要,一般在此轮询函数中完成网络数据接收的工作。
@  weight:NAPI默认权重(weight),一般为NAPI_POLL_WEIGHT。
@  返回值:无。
*/
void netif_napi_add(struct net_device   *dev,   struct napi_struct    *napi, int (*poll)(struct napi_struct *int        weight) /*  删除NAPI ==2222222222222222222222222222222222222222222222@ napi:要删除的NAPI@  返回值:无。 */void netif_napi_del(struct napi_struct    *napi) /*  使能NAPI ==333333333333333333333333333333333333333333333@ n:要使能的NAPI@ 返回值:无。 */
inline void napi_enable(struct napi_struct    *n) /*关闭NAPI ===444444444444444444444444444444444444444444444444
@  n:要关闭的NAPI。
@  返回值:无。
*/
void napi_disable(struct napi_struct *n) /* 检查NAPI是否可以进行调度 ==555555555555555555555555555555555@  n:要检查的NAPI@ 返回值:如果可以调度就返回真,如果不可调度就返回假。 */
inline bool napi_schedule_prep(struct napi_struct *n) /*NAPI调度 ===6666666666666666666666666666666666666666666666666
@  n:要调度的NAPI。
@  返回值:无。
*/
void __napi_schedule(struct napi_struct *n) /*是否可以调度+调度 ===5555555555555555+666666666666666666666
@  n:要调度的NAPI。
*/static inline void napi_schedule(struct napi_struct *n) { if (napi_schedule_prep(n)) __napi_schedule(n); } /*NAPI处理完成 ===77777777777777777777777777777777777777777777777
@  n:处理完成的NAPI。
@  返回值:无。
*/
inline void napi_complete(struct napi_struct *n)

linux内核网络驱动框架(linux驱动开发篇)相关推荐

  1. linux内核网络新特性,Linux内核4.4版本带来的网络新特性

    本文题目有点大,但其实我只想描述一些我个人一直比较关注的特性,并且不会太详细,跟往常一样,主要是帮忙理清思路的,不会分析源码.这主要是为了哪一天突然忘了的时候,一目十行扫一眼就能记忆当时的理解,不然写 ...

  2. Linux内核网络协议栈流程及架构

    文章目录 Linux内核网络报文处理流程 Linux内核网络协议栈架构 Linux内核网络报文处理流程 linux网络协议栈是由若干个层组成的,网络数据的处理流程主要是指在协议栈的各个层之间的传递. ...

  3. 走进Linux内核之Netfilter框架

    走进Linux内核之Netfilter框架 - 掘金笔者此前对Linux内核相关模块稍有研究,实现内核级通信加密.视频流加密等.话不多说直接上才艺,现在带你走进Linux内核之Netfilter框架. ...

  4. 深入浅出Linux内核网络协议栈|结构sk_buff|Iptables|Netfilter丨内核源码丨驱动开发丨内核开发丨C/C++Linux服务器开发

    深入浅出Linux内核网络协议栈 视频讲解如下,点击观看: 深入浅出Linux内核网络协议栈|结构sk C/C++Linux服务器开发高级架构师知识点精彩内容包括:C/C++,Linux,Nginx, ...

  5. Linux内核USB总线--设备控制器驱动框架分析

    正文 1.概述 如下图所示,USB控制器可以呈现出两种不同的状态.USB控制器作为Host时,称为USB主机控制器,使用USB主机控制器驱动.USB控制器作为Device时,称为USB设备控制器,使用 ...

  6. linux内核培训广州,嵌入式Linux驱动开发高级培训班-华清远见嵌入式培训中心

    课程目标 本课程以案例教学为主,系统地介绍Linux下有关FrameBuffer.MMC卡.USB设备的驱动程序开发.参加本课程学习的学员,因为具备了Linux设备驱动开发基础,所以本课程针对性较强, ...

  7. Linux内核网络数据发送(六)——网络设备驱动

    Linux内核网络数据发送(六)--网络设备驱动 1. 前言 2. 驱动回调函数注册 3. `ndo_start_xmit` 发送数据 4. `igb_tx_map` 1. 前言 本文主要介绍设备通过 ...

  8. 显示驱动包含在Linux内核层,驱动程序层(上) - Linux内核--网络栈实现分析_Linux编程_Linux公社-Linux系统门户网站...

    经过前面两篇博文的分析,已经对Linux的内核网络栈的结构有了一个模糊的认识,这里我们开始从底层开始详细分析Linux内核网络栈的实现.由于这是早期版本,代码的层次隔离做的还不是很好,这里说是从底层分 ...

  9. i.MX 6ULL 驱动开发 二十九:向 Linux 内核中添加自己编写驱动

    一.概述 Linux 内核编译流程如下: 1.配置 Linux 内核. 2.编译 Linux 内核. 说明:进入 Linux 内核源码,使用 make help 参看相关配置. 二.make menu ...

  10. NanoPi NEO Air使用九:使用Linux内核自带的LED驱动

    NanoPi NEO Air使用一:介绍 NanoPi NEO Air使用二:固件烧录 NanoPi NEO Air使用三:OverlayFS.CPU温度和频率.wifi.蓝牙.npi-config ...

最新文章

  1. 制作一个老旧C118的GSM便携式测试设备
  2. 在分页状态下删除纪录的问题
  3. linux下perl命令行参数,Perl One-Liners | Perl命令行学习1 -e参数
  4. CUDA Eclipse Nsight 不能打开工程 an error has occurred see the log file
  5. 设计模式示例_命令设计模式示例
  6. DFT(离散傅里叶变换)与FFT(快速傅里叶变换)初识
  7. JQGrid 在页面加载时展开SubGrid
  8. 【软件工程】基准配置(基线配置)
  9. 微信终端跨平台组件 mars 开源
  10. 强烈推荐asp.net数据访问的官方指南系列 (Data Access Tutorials)
  11. jdk LinkedList源码解析
  12. mysql 存储过程案列一个。
  13. c++ 使用gdiplus
  14. 9.2. service
  15. 《机器学习算法竞赛实战》学习笔记1.竞赛简介
  16. ov5640帧率配置_基于OV5640的FPGARAM HDMI显示
  17. 定位模组 ppm CEP 等参数 说明
  18. excel软件做折线图
  19. vue-cli脚手架卡在 ‘98%’ after emitting CopyPlugin 报错,无法运行
  20. BZOJ 4216 Pig 分块乱搞

热门文章

  1. UE4 AssetManager
  2. 反编译APK分析APP的加密算法
  3. 如何申请创建数据分析项目组合
  4. 美团面试官:说说什么是单点登录?什么是SSO?什么是CAS
  5. Python实验舱谢尔宾斯基地毯绘制教程
  6. RFID打印机故障解决方法
  7. android分屏模式_Android的分屏模式开发注意事项
  8. 「实战教程」如何使用POI读取模板PPT填充数据并拼接至目标文件
  9. 解决:Command line is too long. Shorten command line for xxx or also for Application default configurat
  10. 我的word菜单栏和工具栏不见了