1)网络协议接口层 向网络层协议提供统一的数据包收发接口,不论上层协议是ARP,还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据。这一层的存在,使得上层协议独立于具体的设备。
2)网络设备接口层 向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,给结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件设备驱动功能层的结构。
3)设备驱动功能层的各函数是网络设备接楼层net_device数据结构体的具体成员,是驱动网络设备硬件完成相应动作的程序,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。
4)网络设备与媒介层是完成数据包发送和接收的物理实现,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数在物理上驱动。对Linux系统而言,网络设备和媒介都可以是虚拟的。
在设计具体的网络设备驱动程序时,需要完成的主要工作是编写设备驱动功能层的相关函数以填充net_device数据结构体的内容并将net_device注册入内核。
发送大致流程:
Linux 内核中,用 sk_buff(skb)来描述一个缓存,所谓分配缓存空间,就是建立一定数量的 sk_buff。sk_buff 是 Linux 内核网络协议栈实现中最重要的结构体,它是网络数据报文在内核中的表现形式。用户态应用程序(应用层)可以通过系统调用接口访问 BSD Socket 层,传递给 Socket 的数据首先会保存在 sk_buff 对应的缓冲区中,sk_buff 的结构定义在 include/linux/skbuff.h 文件中。它保存数据报文的结构为一个双向链表,如下所示:
当数据被储存到了 sk_buff 缓存区中,网卡驱动的发送函数 hard_start_xmit 也随之被调用,流程图如下所示:
include/linux/netdevice.h中定义了net_device结构体,他是网络设备驱动程序中最重要的结构。
该结构体,存储着网络设备的所有信息,每个网络设备都有这种结构。所有设备的net_device结构放在一个全局变量dev_base所有全局列表中。结构体中有一个next指针,用来连接系统中所有网络设备。内核把这些连接起来的设备组成一个链表,并由全局变量dev_base指向链表的第一个元素。
每个网络设备都会有一个对应的实例,然后调用register_netdevie()(定义与文件net/core/dev.c)注册到系统中,注销可以通过unregister_netdevice()。
struct net_device {
/* 设备名称,对应 ifconfig 输出的网卡名称,例如 eth0,字母代表网络设备的类型,数字代表此类网络设备的数量 */
char name[IFNAMSIZ];
/* 名称hash */
struct hlist_node name_hlist;
/* 别名,用于 SNMP 协议 */
char *ifalias;
/*
* I/O specific fields
* FIXME: Merge these and struct ifmap into one
*/
/*
描述设备所用的共享内存,用于设备与内核沟通
其初始化和访问只会在设备驱动程序内进行
*/
unsigned long mem_end;
unsigned long mem_start;
/* 设备自有内存映射到I/O内存的起始地址 */
unsigned long base_addr;
/*
设备与内核对话的中断编号,此值可由多个设备共享
驱动程序使用request_irq函数分配此变量,使用free_irq予以释放
*/
int irq;
/* 侦测网络状态的改变次数 */
atomic_t carrier_changes;
/*
* Some hardware also needs these fields (state,dev_list,
* napi_list,unreg_list,close_list) but they are not
* part of the usual set specified in Space.c.
*/
/*
网络队列子系统使用的一组标识
由__LINK_STATE_xxx标识
*/
unsigned long state;
struct list_head dev_list;
struct list_head napi_list;
struct list_head unreg_list;
struct list_head close_list;
/* 当前设备所有协议的链表 */
struct list_head ptype_all;
/* 当前设备特定协议的链表 */
struct list_head ptype_specific;
struct {
struct list_head upper;
struct list_head lower;
} adj_list;
/*
用于存在其他一些设备功能
可报告适配卡的功能,以便与CPU通信
使用NETIF_F_XXX标识功能特性
*/
netdev_features_t features;
netdev_features_t hw_features;
netdev_features_t wanted_features;
netdev_features_t vlan_features;
netdev_features_t hw_enc_features;
netdev_features_t mpls_features;
netdev_features_t gso_partial_features;
/* 网络设备索引号 */
int ifindex;
/* 设备组,默认都属于0组 */
int group;
struct net_device_stats stats;
atomic_long_t rx_dropped;
atomic_long_t tx_dropped;
atomic_long_t rx_nohandler;
#ifdef CONFIG_WIRELESS_EXT
const struct iw_handler_def *wireless_handlers;
struct iw_public_data *wireless_data;
#endif
/* 设备操作接口,主要用来操作网卡硬件 */
const struct net_device_ops *netdev_ops;
/* ethtool操作接口 */
const struct ethtool_ops *ethtool_ops;
#ifdef CONFIG_NET_SWITCHDEV
const struct switchdev_ops *switchdev_ops;
#endif
#ifdef CONFIG_NET_L3_MASTER_DEV
const struct l3mdev_ops *l3mdev_ops;
#endif
#if IS_ENABLED(CONFIG_IPV6)
const struct ndisc_ops *ndisc_ops;
#endif
#ifdef CONFIG_XFRM
const struct xfrmdev_ops *xfrmdev_ops;
#endif
/* 头部一些操作,如链路层缓存,校验等 */
const struct header_ops *header_ops;
/* 标识接口特性,IFF_XXX,如IFF_UP */
unsigned int flags;
/*
用于存储用户空间不可见的标识
由VLAN和Bridge虚拟设备使用
*/
unsigned int priv_flags;
/* 几乎不使用,为了兼容保留 */
unsigned short gflags;
/* 结构对齐填充 */
unsigned short padded;
/* 与interface group mib中的IfOperStatus相关 */
unsigned char operstate;
unsigned char link_mode;
/*
接口使用的端口类型
*/
unsigned char if_port;
/*
设备使用的DMA通道
并非所有设备都可以用DMA,有些总线不支持DMA
*/
unsigned char dma;
/*
最大传输单元,标识设备能处理帧的最大尺寸
Ethernet-1500
*/
unsigned int mtu;
/* 最小mtu,Ethernet-68 */
unsigned int min_mtu;
/* 最大mut,Ethernet-65535 */
unsigned int max_mtu;
/* 设备所属类型
ARP模块中,用type判断接口的硬件地址类型
以太网接口为ARPHRD_ETHER
*/
unsigned short type;
/*
设备头部长度
Ethernet报头是ETH_HLEN=14字节
*/
unsigned short hard_header_len;
unsigned char min_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;
unsigned short neigh_priv_len;
unsigned short dev_id;
unsigned short dev_port;
spinlock_t addr_list_lock;
/* 设备名赋值类型,如NET_NAME_UNKNOWN */
unsigned char name_assign_type;
bool uc_promisc;
struct netdev_hw_addr_list uc;
struct netdev_hw_addr_list mc;
struct netdev_hw_addr_list dev_addrs;
#ifdef CONFIG_SYSFS
struct kset *queues_kset;
#endif
/* 混杂模式开启数量 */
unsigned int promiscuity;
/* 非零值时,设备监听所有多播地址 */
unsigned int allmulti;
/* Protocol-specific pointers */
/* 特定协议的指针 */
#if IS_ENABLED(CONFIG_VLAN_8021Q)
struct vlan_info __rcu *vlan_info;
#endif
#if IS_ENABLED(CONFIG_NET_DSA)
struct dsa_switch_tree *dsa_ptr;
#endif
#if IS_ENABLED(CONFIG_TIPC)
struct tipc_bearer __rcu *tipc_ptr;
#endif
void *atalk_ptr;
/* ip指向in_device结构 */
struct in_device __rcu *ip_ptr;
struct dn_dev __rcu *dn_ptr;
struct inet6_dev __rcu *ip6_ptr;
void *ax25_ptr;
struct wireless_dev *ieee80211_ptr;
struct wpan_dev *ieee802154_ptr;
#if IS_ENABLED(CONFIG_MPLS_ROUTING)
struct mpls_dev __rcu *mpls_ptr;
#endif
/*
* Cache lines mostly used on receive path (including eth_type_trans())
*/
/* Interface address info used in eth_type_trans() */
unsigned char *dev_addr;
#ifdef CONFIG_SYSFS
/* 接收队列 */
struct netdev_rx_queue *_rx;
/* 接收队列数 */
unsigned int num_rx_queues;
unsigned int real_num_rx_queues;
#endif
struct bpf_prog __rcu *xdp_prog;
unsigned long gro_flush_timeout;
/* 如网桥等的收包回调 */
rx_handler_func_t __rcu *rx_handler;
/* 回调参数 */
void __rcu *rx_handler_data;
#ifdef CONFIG_NET_CLS_ACT
struct tcf_proto __rcu *ingress_cl_list;
#endif
struct netdev_queue __rcu *ingress_queue;
#ifdef CONFIG_NETFILTER_INGRESS
/* netfilter入口 */
struct nf_hook_entry __rcu *nf_hooks_ingress;
#endif
/* 链路层广播地址 */
unsigned char broadcast[MAX_ADDR_LEN];
#ifdef CONFIG_RFS_ACCEL
struct cpu_rmap *rx_cpu_rmap;
#endif
/* 接口索引hash */
struct hlist_node index_hlist;
/*
* 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;
#ifdef CONFIG_NET_SCHED
DECLARE_HASHTABLE (qdisc_hash, 4);
#endif
/*
可在设备发送队列中排队的最大数据包数
*/
unsigned long tx_queue_len;
spinlock_t tx_global_lock;
/* 网络层确定传输超时,
调用驱动程序tx_timeout接口的最短时间
*/
int watchdog_timeo;
#ifdef CONFIG_XPS
struct xps_dev_maps __rcu *xps_maps;
#endif
#ifdef CONFIG_NET_CLS_ACT
struct tcf_proto __rcu *egress_cl_list;
#endif
/* These may be needed for future network-power-down code. */
/* watchdog定时器 */
struct timer_list watchdog_timer;
/* 引用计数 */
int __percpu *pcpu_refcnt;
/* 网络设备的注册和除名以两步进行,
该字段用于处理第二步
*/
struct list_head todo_list;
struct list_head link_watch_list;
/* 设备的注册状态 */
enum { NETREG_UNINITIALIZED=0,
NETREG_REGISTERED, /* completed register_netdevice */
NETREG_UNREGISTERING, /* called unregister_netdevice */
NETREG_UNREGISTERED, /* completed unregister todo */
NETREG_RELEASED, /* called free_netdev */
NETREG_DUMMY, /* dummy device for NAPI poll */
} reg_state:8;
/* 设备要被释放标记 */
bool dismantle;
enum {
RTNL_LINK_INITIALIZED,
RTNL_LINK_INITIALIZING,
} rtnl_link_state:16;
bool needs_free_netdev;
void (*priv_destructor)(struct net_device *dev);
#ifdef CONFIG_NETPOLL
struct netpoll_info __rcu *npinfo;
#endif
possible_net_t nd_net;
/* mid-layer private */
union {
void *ml_priv;
struct pcpu_lstats __percpu *lstats;
struct pcpu_sw_netstats __percpu *tstats;
struct pcpu_dstats __percpu *dstats;
struct pcpu_vstats __percpu *vstats;
};
#if IS_ENABLED(CONFIG_GARP)
struct garp_port __rcu *garp_port;
#endif
#if IS_ENABLED(CONFIG_MRP)
struct mrp_port __rcu *mrp_port;
#endif
struct device dev;
const struct attribute_group *sysfs_groups[4];
const struct attribute_group *sysfs_rx_queue_group;
const struct rtnl_link_ops *rtnl_link_ops;
/* for setting kernel sock attribute on TCP connection setup */
#define GSO_MAX_SIZE 65536
unsigned int gso_max_size;
#define GSO_MAX_SEGS 65535
u16 gso_max_segs;
#ifdef CONFIG_DCB
const struct dcbnl_rtnl_ops *dcbnl_ops;
#endif
u8 num_tc;
struct netdev_tc_txq tc_to_txq[TC_MAX_QUEUE];
u8 prio_tc_map[TC_BITMASK + 1];
#if IS_ENABLED(CONFIG_FCOE)
unsigned int fcoe_ddp_xid;
#endif
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
struct netprio_map __rcu *priomap;
#endif
struct phy_device *phydev;
struct lock_class_key *qdisc_tx_busylock;
struct lock_class_key *qdisc_running_key;
bool proto_down;
};
Struct header_ops包含以一系列函数指针,主要是对链路层协议头的操作,比如协议头的创建、解析、重建协议头。
struct header_ops {
/*协议头的创建函数指针*/
int (*create) (struct sk_buff *skb, struct net_device *dev,
unsigned short type, const void *daddr,
const void *saddr, unsigned len);
/*协议头解析函数指针*/
int (*parse)(const struct sk_buff *skb, unsigned char *haddr);
/*协议头重构函数指针*/
int (*rebuild)(struct sk_buff *skb);
int (*cache)(const struct neighbour *neigh, struct hh_cache *hh);
void (*cache_update)(struct hh_cache *hh,
const struct net_device *dev,
const unsigned char *haddr);
};
网卡操作函数,如up,down,发包等(ifconfig XXX up,ifconfig XXX down等都是调用的这个结构体中的api)。net_device_ops的成员都是函数指针,是网络驱动程序实现的功能函数。
struct net_device_ops {
int (*ndo_init)(struct net_device *dev); //提供网络设备的初始化、创建网络设备struct net_device 数据结构实例、初始化struct net_device的相关数据 域如设备名、i/o端口地址、中断号、向内核注册设备
void (*ndo_uninit)(struct net_device *dev); //注销设备的时候用
int (*ndo_open)(struct net_device *dev); //打开网络设备,主要注册的设备才能打开
int (*ndo_stop)(struct net_device *dev); //停止网络设备,注销的时候调用和open相反
netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb,
struct net_device *dev); //初始化数据包发送过程,初始化成功后数据包就放入网络适配器的发哦送你个数据缓冲区
u16(*ndo_select_queue)(struct net_device *dev,
struct sk_buff *skb); //当网络设备支持多个发送队列时用于选择发送队列
................
}
ndo_select_queue函数选择数据包发送队列。设备驱动程序通过函数ndo_start_xmit从任意发送队列中得到发送的数据包,通过函数static inline u16 skb_get_queue_mapping(const struct sk_buff *skb)得到数据包所在队列
ndo_open函数中需要做如下工作:
ndo_close函数需要完成如下工作:
/*
* 网络设备打开和释放函数模板
*/
static int xxx_open(struct net_device *dev)
{
/* 申请端口、IRQ等,类似于fops->open */
ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev);
···
netif_start_queue(dev);
···
}
static int xxx_release(struct net_device *dev)
{
/* 释放端口、IRQ 等,类似于fops->close */
free_irq(dev->irq, dev);
···
netif_stop_queue(dev);
···
}
ndo_start_xmit函数会启动数据包的发送,当系统调用驱动程序的xmit函数时,需要向其传入一个sk_buff结构体指针,以使得驱动程序能从上层传递下来的数据包。
struct netdev_queue *_tx ____cacheline_aligned_in_smp;
____cacheline_aligned_in_smp是一个宏定义,参见:
linux网络源码分析(1) - TIANCJ - 博客园
____cacheline_aligned和____cacheline_aligned_in_smp_傲世阿龍的博客-CSDN博客_____cacheline_aligned
网络协议栈把数据包放入设备的队列后调用网络设备驱动程序的函数hard_start_xmit来通知网络设备数据包已经准备好了可以发送了。一个网络设备可能有多个发送队列,根据队列优先级的不同调度不同的队列,由struct netdev_queue来管理队列,*_tx指向struct netdev_queue数组的首地址,
struct netdev_queue {
/*
* read-mostly part
*/
struct net_device *dev;//队列所属的网络设备
netdevice_tracker dev_tracker;
struct Qdisc __rcu *qdisc;//队列操作函数
struct Qdisc *qdisc_sleeping;
unsigned long tx_maxrate;
/*
* Number of TX timeouts for this queue
* (/sys/class/net/DEV/Q/trans_timeout)
*/
atomic_long_t trans_timeout;
/*
* write-mostly part
*/
spinlock_t _xmit_lock ____cacheline_aligned_in_smp;
int xmit_lock_owner;
/*
* Time (in jiffies) of last Tx
*/
unsigned long trans_start;
unsigned long state;//队列状态
} ____cacheline_aligned_in_smp;
ref:
理解Linux内部网络实现之关键数据结构 sk_buff – Yang Blog
网卡适配器收发数据帧流程 - 云物互联 - 博客园
netdevice.h - include/linux/netdevice.h - Linux source code (v3.3) - Bootlin
Linux网络设备驱动专题_火锅娃的博客-CSDN博客_网络设备驱动
网络设备驱动介绍(浅析)_小嵌同学的博客-CSDN博客_网络设备驱动
网络设备之net_device结构与操作 - AlexAlex - 博客园
网络设备结构体net_device介绍_TCH_world的博客-CSDN博客_net_device结构体
net_device 结构详情_wilber的技术博客_51CTO博客
https://www.csdn.net/tags/NtDaMg0sNTI2NzctYmxvZwO0O0OO0O0O.html
linux 内核协议栈 网络设备抽象 net_device_老王不让用的博客-CSDN博客
linux网络子系统分析(三)—— 设备无关层_whenloce的博客-CSDN博客_netdev_pick_tx
内核net_device设备框架的一个缺陷 - 云+社区 - 腾讯云
Linux内核中的struct net_device的dev_addr和perm_addr之间有什么区别 - VoidCC