Linux网络协议栈(三)——网络设备(1)
网络设备(network device)是内核对网络适配器(硬件)的抽象与封装,并为各个协议实例提供统一的接口,它是硬件与内核的接口,它有两个特征:
(1) 作为基于硬件的网络适配器与基于软件的协议之间的接口;
(2) 内核协议栈异步输入输出点。
记住:网络设备软件对硬件的抽象
网络设备与协议和网络适配器的关系如下:
1、 net_device接口(net_device Interface)
网络设备是内核中除了字符设备、块设备之外第三类主要设备,它的主要特征之一就是在设备文件系统/dev/没有相应的表示,即不存在/dev/eth0等,这就意味着不能通过简单的读写操作来访问它们。
net_device结构保存与网络设备相关的所有信息。每一个网络设备都对应一个这样的结构,包括真实设备(例如以太网卡)和虚拟设备(比如 bonding 或 VLAN)。
所有设备的 net_device 结构都放在一个全局链表中,链表的头指针是 dev_base。net_device结构的定义在include/linux/netdevice.h中。与 sk_buff 类似,net_device 结构比较大,而且包含了很多特性相关的参数,这些参数在不同的协议层中使用。出于这个原因,net_device 结构的组织会有一些改变,用于优化协议栈的性能。 网络设备可以分为不同的类型,比如以太网卡和令牌环网卡。net_device 结构中的某些变量对同一类型的设备来说, 取值是相同的; 而某些变量在同一设备的不同工作模式下,取值必须不同。因此,对几乎所有类型的设备,linux内核提供了一个通用的函数用于初始化那些在所有模式下取值相同的变量。每一个设备驱动在调用这个函数的同时,还初始化那些在当前模式下取值不同的变量。设备驱动同样可以覆盖那些由内核初始化的变量(例如,在优化设备性能时)。
net_device的定义:
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
//include/linux/netdevice.h
/*
* The DEVICE structure.
* Actually, this whole structure is a big mistake. It mixes I/O
* data with strictly "high-level" data, and it has to know about
* almost every data structure used in the INET module.
*
* FIXME: cleanup struct net_device such that network protocol info
* moves out.
*/
struct net_device
{
/*
* This is the first field of the "visible" part of this structure
* (i.e. as seen by users in the "Space.c" file). It is the name
* the interface.
*/
char name[IFNAMSIZ];//网络设备名称
/*
* I/O specific fields
* FIXME: Merge these and struct ifmap into one
*/
unsigned long mem_end; /* shared mem end */
unsigned long mem_start; /* shared mem start */
unsigned long base_addr; /* device I/O address */
unsigned int irq; /* device IRQ number */
/*
* Some hardware also needs these fields, but they are not
* part of the usual set specified in Space.c.
*/
unsigned char if_port; /* Selectable AUI, TP,..*/
unsigned char dma; /* DMA channel */
unsigned long state;
struct net_device *next;
/* The device initialization function. Called only once. */
int (*init)(struct net_device *dev);
/* ------- Fields preinitialized in Space.c finish here ------- */
struct net_device *next_sched;
/* Interface index. Unique device identifier */
int ifindex;
int iflink;
struct net_device_stats* (*get_stats)(struct net_device *dev);
struct iw_statistics* (*get_wireless_stats)(struct net_device *dev);
/* List of functions to handle Wireless Extensions (instead of ioctl).
* See <net/iw_handler.h> for details. Jean II */
const struct iw_handler_def * wireless_handlers;
/* Instance data managed by the core of Wireless Extensions. */
struct iw_public_data * wireless_data;
struct ethtool_ops *ethtool_ops;
/*
* This marks the end of the "visible" part of the structure. All
* fields hereafter are internal to the system, and may change at
* will (read: may be cleaned up at will).
*/
/* These may be needed for future network-power-down code. */
unsigned long trans_start; /* Time (in jiffies) of last Tx */
unsigned long last_rx; /* Time of last Rx */
unsigned short flags; /* interface flags (a la BSD) */
unsigned short gflags;
unsigned short priv_flags; /* Like 'flags' but invisible to userspace. */
unsigned short unused_alignment_fixer; /* Because we need priv_flags,
* and we want to be 32-bit aligned.
*/
unsigned mtu; /* interface MTU value */
unsigned short type; /* interface hardware type */
unsigned short hard_header_len; /* hardware hdr length */
void *priv; /* pointer to private data */
struct net_device *master; /* Pointer to master device of a group,
* which this device is member of.
*/
/* Interface address info. */
unsigned char broadcast[MAX_ADDR_LEN]; /* hw bcast add */
unsigned char dev_addr[MAX_ADDR_LEN]; /* hw address */
unsigned char addr_len; /* hardware address length */
unsigned short dev_id; /* for shared network cards */
struct dev_mc_list *mc_list; /* Multicast mac addresses */
int mc_count; /* Number of installed mcasts */
int promiscuity;
int allmulti;
int watchdog_timeo;
struct timer_list watchdog_timer;
/* Protocol specific pointers */
void *atalk_ptr; /* AppleTalk link */
void *ip_ptr; /* IPv4 specific data */
void *dn_ptr; /* DECnet specific data */
void *ip6_ptr; /* IPv6 specific data */
void *ec_ptr; /* Econet specific data */
void *ax25_ptr; /* AX.25 specific data */
struct list_head poll_list; /* Link to poll list */
int quota;
int weight;
struct Qdisc *qdisc;
struct Qdisc *qdisc_sleeping;
struct Qdisc *qdisc_ingress;
struct list_head qdisc_list;
unsigned long tx_queue_len; /* Max frames per queue allowed */
/* ingress path synchronizer */
spinlock_t ingress_lock;
/* hard_start_xmit synchronizer */
spinlock_t xmit_lock;
/* cpu id of processor entered to hard_start_xmit or -1,
if nobody entered there.
*/
int xmit_lock_owner;
/* device queue lock */
spinlock_t queue_lock;
/* Number of references to this device */
atomic_t refcnt;
/* delayed register/unregister */
struct list_head todo_list;
/* device name hash chain */
struct hlist_node name_hlist;
/* device index hash chain */
struct hlist_node index_hlist;
/* register/unregister state machine */
enum { NETREG_UNINITIALIZED=0,
NETREG_REGISTERING, /* called register_netdevice */
NETREG_REGISTERED, /* completed register todo */
NETREG_UNREGISTERING, /* called unregister_netdevice */
NETREG_UNREGISTERED, /* completed unregister todo */
NETREG_RELEASED, /* called free_netdev */
} reg_state;
/* Net device features */
int features;
#define NETIF_F_SG 1 /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM 2 /* Can checksum only TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM 4 /* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM 8 /* Can checksum all the packets. */
#define NETIF_F_HIGHDMA 32 /* Can DMA to high memory. */
#define NETIF_F_FRAGLIST 64 /* Scatter/gather IO. */
#define NETIF_F_HW_VLAN_TX 128 /* Transmit VLAN hw acceleration */
#define NETIF_F_HW_VLAN_RX 256 /* Receive VLAN hw acceleration */
#define NETIF_F_HW_VLAN_FILTER 512 /* Receive filtering on VLAN */
#define NETIF_F_VLAN_CHALLENGED 1024 /* Device cannot handle VLAN packets */
#define NETIF_F_TSO 2048 /* Can offload TCP/IP segmentation */
#define NETIF_F_LLTX 4096 /* LockLess TX */
/* Called after device is detached from network. */
void (*uninit)(struct net_device *dev);
/* Called after last user reference disappears. */
void (*destructor)(struct net_device *dev);
/* Pointers to interface service routines. */
int (*open)(struct net_device *dev);
int (*stop)(struct net_device *dev);
int (*hard_start_xmit) (struct sk_buff *skb,
struct net_device *dev);
#define HAVE_NETDEV_POLL
int (*poll) (struct net_device *dev, int *quota);
int (*hard_header) (struct sk_buff *skb,
struct net_device *dev,
unsigned short type,
void *daddr,
void *saddr,
unsigned len);
int (*rebuild_header)(struct sk_buff *skb);
#define HAVE_MULTICAST
void (*set_multicast_list)(struct net_device *dev);
#define HAVE_SET_MAC_ADDR
int (*set_mac_address)(struct net_device *dev,
void *addr);
#define HAVE_PRIVATE_IOCTL
int (*do_ioctl)(struct net_device *dev,
struct ifreq *ifr, int cmd);
#define HAVE_SET_CONFIG
int (*set_config)(struct net_device *dev,
struct ifmap *map);
#define HAVE_HEADER_CACHE
int (*hard_header_cache)(struct neighbour *neigh,
struct hh_cache *hh);
void (*header_cache_update)(struct hh_cache *hh,
struct net_device *dev,
unsigned char * haddr);
#define HAVE_CHANGE_MTU
int (*change_mtu)(struct net_device *dev, int new_mtu);
#define HAVE_TX_TIMEOUT
void (*tx_timeout) (struct net_device *dev);
void (*vlan_rx_register)(struct net_device *dev,
struct vlan_group *grp);
void (*vlan_rx_add_vid)(struct net_device *dev,
unsigned short vid);
void (*vlan_rx_kill_vid)(struct net_device *dev,
unsigned short vid);
int (*hard_header_parse)(struct sk_buff *skb,
unsigned char *haddr);
int (*neigh_setup)(struct net_device *dev, struct neigh_parms *);
int (*accept_fastpath)(struct net_device *, struct dst_entry*);
#ifdef CONFIG_NETPOLL
int netpoll_rx;
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
void (*poll_controller)(struct net_device *dev);
#endif
/* bridge stuff */
struct net_bridge_port *br_port;
#ifdef CONFIG_NET_DIVERT
/* this will get initialized at each interface type init routine */
struct divert_blk *divert;
#endif /* CONFIG_NET_DIVERT */
/* class/net/name entry */
struct class_device class_dev;
/* how much padding had been added by alloc_netdev() */
int padded;
};
net_device结构主要分为以下几部分:
1.1、 通用字段
name:
网络适配器的名称,比如eth0。在注册网络设备时可以为设备分配一个名称,便必须唯一。
next:
所有的网络设备组成一个由dev_base开头的链表。
int ifindex :
全局唯一的设备ID。在每个设备注册时,调用dev_new_index 生成。
int iflink:
这个变量主要被(虚拟)隧道设备使用,用于标识隧道的真实设备。
state:
它包含一组被网络队列子系统使用的标记。这些标记的值是枚举类型netdev_state_t中的索引值,这个类型的定义在 include/linux/netdevice.h 中,每个标记都是诸如__LINK_STATE_XOFF 这样的常量。每一位都可以通过函数 set_bit 和 clear_bit 设置或清除,但通常情况下,都会有一个包装函数来隐藏标记位的信息。例如,在网络队列子系统停止一个设备队列时,它调用函数 netif_stop_queue,这个函数的定义如下:
static inline void netif_stop_queue(struct net_device *dev)
{
...
set_bit(_ _LINK_STATE_XOFF, &dev->state);
}
trans_start:
最后一个帧开始发送的时间(用jiffies度量)。设备驱动在发送之前设置这个变量。这个变量用来检测网卡是否在给定的时间内把帧发送了出去。 太长的发送时间意味
着有错误发生,在这种情况下,设备驱动会重置网卡。
last_rx :
接收到最后一个包的时间(用jiffies度量)。
xmit_lock 和xmit_lock_owner :
xmit_lock 用来序列化对设备驱动函数hard_start_xmit的调用。这意味着,每个cpu每次只能调用设备完成一次发送。xmit_lock_owner 是拥有锁的 CPU 的 ID。在单cpu 系统上,这个值是 0;在多 cpu 系统中,如果锁没有被占用,这个值是-1。内核同样允许不加锁的发送,前提条件是设备驱动必须支持这个功能。
struct hlist_node name_hlist
struct hlist_node index_hlist
把net_device结构链接到两个哈希表中。
1.2、 硬件相关
unsigned int irq
设备中断号。它可以被多个设备共享。设备驱动调用request_irq来分配这个值,并
调用free_irq来释放它。
unsigned char if_port
接口的端口类型。有些设备可以支持多种接口(最常见的组合是 BNC+RJ45),用户可以根据需要来选择使用哪种接口。这个变量用来设置设备的接口类型。如果配置命令没有指定设备的接口类型,设备驱动就使用缺省的类型。在某些情况下,一个设备驱动可以处理多种接口类型;在这种情况下,设备驱动可以按一定的顺序来测试每个接口的类型。下面的代码片断展示了一个设备驱动如何根据配置来设置接口的类型:
switch (dev->if_port) {
case IF_PORT_10BASE2:
writeb((readb(addr) & 0xf8) | 1, addr);
break;
case IF_PORT_10BASET:
writeb((readb(addr) & 0xf8), addr);
break;
}
unsigned char dma
设备所使用的 DMA 通道。为获取和释放一个 DMA 通道,内核在 kernel/dma.c 中定义了两个函数request_dma和free_dma。为了在获取dma通道后,启用或者停止dma通道,内核定义了两个函数enable_dma和disable_dma。这两个函数的实现与
体系结构相关,所以在 include/asm-architecture 下有相关的文件(例如include/asm-i386)。这些函数被 ISA 设备使用;PCI 设备不使用这些函数,它们使
用其他函数。并不是所有的设备都可以使用dma,因为有些总线不支持dma。
unsigned long mem_start
unsigned long mem_end
这两个变量描述设备与内核通信所用到的内存边界。它们由设备驱动初始化,并且只能被设备驱动访问;高层协议不需要关心这块内存。
unsigned long base_addr
映射到设备内存空间中I/O 内存起始地址。
1.3、 物理层相关
unsigned mtu
MTU 的意思是最大传输单元,它表示设备可以处理帧的最大长度。不同设备的MTU值:
unsigned short type
设备类型(以太网,帧中继等)。在include/linux/if_arp.h 中有完整的类型列表。
unsigned short hard_header_len
以字节为单位的帧头部长度。例如,以太网帧的头是 14 字节。某种设备所支持帧的头部长度在相应的设备头文件中定义。对以太网来说,ETH_HLEN 在
<include/linux/if_ether.h>中定义。
unsigned char broadcast[MAX_ADDR_LEN]
链路层广播地址。
unsigned char dev_addr[MAX_ADDR_LEN]
unsigned char addr_len
dev_addr是设备的链路层地址,不要把它和IP 地址或者L3 地址混淆了。链路层地址的长度是 addr_len,以字节为单位。addr_len 的大小与设备类型有关。以太网地址的长度是8。
int promiscuity
promiscuity计数器来标识设备是否工作在混杂模式。之所以使用计数器而不是一个标志位的原因是:可能有多个用户程序设置设备工作在混杂模式下。因此,每次进入混杂模式,计数器加一;退出混杂模式,计数器减一。只有计数器为0 时,设备才退出混杂模式。这个变量通常调用函数dev_set_promiscuity 来设置。
struct dev_mc_list *mc_list
指向dev_mc_list结构
int mc_count
设备多播地址的数量,它同样表示mc_list所指向链表的长度。
int allmulti
如果是非零值,那么设备将监听所有的多播地址。和 promiscuity 一样,这个变量是一个计数器而不仅仅是一个布尔值。这是因为多个设备(比如VLAN和bonding
设备)可能独立地要求监听所有地址。如果这个变量的值从0变为非零,内核会调用函数dev_set_allmulti通知设备监听所有的多播地址。如果这个值变为0,则停止监听所有的多播地址。
1.4、 协议相关
void *atalk_ptr
void *ip_ptr
void *dn_ptr
void *ip6_ptr
void *ec_ptr
void *ax25_ptr
这六个变量指向特定协议的数据结构,每个数据结构都包含协议私有的参数。例如,ip_ptr 指向一个 in_device 类型的结构(尽管 ip_ptr 的类型是 void*),它包含 IPv4相关的参数,其中包括设备的 IP 地址列表等。
1.5、 流量管理
Linux 流量控制子系统的功能已经非常强大,并且已经成为 Linux 内核中的一个重要组件。相关的内核选项是 “Device drivers ->Networking support ->Networking options ->QoS and/or fair queueing”。net_device中的相关变量包括:
struct net_device *next_sched
被内核软中断使用。
struct Qdisc *qdisc
struct Qdisc *qdisc_sleeping
struct Qdisc *qdisc_ingress
struct list_head qdisc_list
这些变量管理设备的接收,发送队列,并且可以被不同的cpu访问。
spinlock_t queue_lock
spinlock_t ingress_lock
流量控制子系统为每个网络设备定义了一个私有的发送队列。 queue_lock用于避免并发的访问(参见第11章)。ingress_lock 用于保护接收队列。
unsigned long tx_queue_len
设备发送队列的长度。如果内核中包含了流量控制子系统,这个变量可能没有什么用(只有几个排队策略会使用它)。常见设备的 tx_queue_len 值(这个值可以通过sysfs文件系统修改(在/sys/class/net/device_name/目录下)):
1.6、 设备驱动程序相关
int (*init)(...)
void (*uninit)(...)
void (*destructor)(...)
int (*open)(...)
int (*stop)(...)
用于初始化,清除,销毁,启用和停止一个设备。这些函数并不是每个设备都会用到。
int (*hard_start_xmit)(...)
发送一个帧。
int (*hard_header)(...)
根据源和目标的第2层地址创建第2层报文头。
int (*rebuild_header)(...)
负责在传送包之前重建第2导报文头。
int (*set_mac_address)(...)
修改设备的 MAC 地址。如果设备不提供这个功能(比如网桥设备),可以把这个指针设置为NULL。
int (*change_mtu)(...)
修改设备的MTU,修改mtu 不会对设备驱动有任何影响,它只是让协议栈软件可以根据新的mtu 正确地处理分片。