- 网络驱动的核心:
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 = ð_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 的网络驱动中得到了大量的
应用文章来源:https://www.toymoban.com/news/detail-404142.html -
如何在驱动中使用 NAPI处理机制文章来源地址https://www.toymoban.com/news/detail-404142.html
/*初始化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驱动开发篇)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!