net_device结构保存与网络设备相关的所有信息。每一个网络设备都对应一个这样的结构,包括真实设备(例如以太网卡)和虚拟设备(比如bonding或VLAN)。
Bonding,也被称作EtherChannel(Cisco的术语)和trunking(Sun的术语),允许把一定数量的接口组合起来当作一个新的设备。这个特性在系统需要把多个点对点设备组合起来以获取更高带宽时有用。新设备的速度可以成倍增加,一般来说, 新设备的吞吐量是单个设备吞吐量的总和。
VLAN代表虚拟局域网。VLAN的作用是在二层交换机上划分不同的广播域,从而把不同广播域的流量隔离开。它通过在链路层上增加一个标记来实现这个功能。你可以在http://www.linuxjournal.com/article/7268找到VLAN的简介以及它在LINUX中的使用方法。
所有设备的net_device结构都放在一个全局链表中,链表的头指针是dev_base。net_device结构的定义在include/linux/netdevice.h中。
与sk_buff类似,net_device结构比较大,而且包含了很多特性相关的参数,这些参数在不同的协议层中使用。出于这个原因,net_device结构的组织会有一些改变,用于优化协议栈的性能。
网络设备可以分为不同的类型,比如以太网卡和令牌环网卡。net_device结构中的某些变量对同一类型的设备来说,取值是相同的;而某些变量在同一设备的不同工作模式下,取值必须不同。因此,对几乎所有类型的设备,linux内核提供了一个通用的函数用于初始化那些在所有模式下取值相同的变量。每一个设备驱动在调用这个函数的同时,还初始化那些在当前模式下取值不同的变量。设备驱动同样可以覆盖那些由内核初始化的变量(例如,在优化设备性能时)。
net_device结构中的变量可以分为以下几类:
1. 标识符
net_device结构包括三个标识符:
int ifindex
全局唯一的设备ID。在每个设备注册时,调用dev_new_index生成。
int iflink
这个变量主要被(虚拟)隧道设备使用,用于标识隧道的真实设备。
unsigned short dev_id
IPV6中使用zSeries OSA网卡时用到这个变量。这个变量用于区分同一设备的不同虚拟实体,这些虚拟实体可以在不同的虚拟操作系统中共享。详细内容,请参阅net/ipv6/addrconf.c中的注释。
2. Configuration
有些参数可以在内核初始化此类设备时设置一个缺省值,而有些参数就需要留给设备驱动来填充。设备驱动可以更改缺省值,这个在前面已经说过。有些变量甚至可以在运行时通过命令如ifconfig或ip等来更改。实际上,有些变量,比如base_addr,if_port,dma和irq等通常都是由用户在加载设备驱动模块时设置的。但是,这些变量不能被虚拟设备使用。
char name[IFNAMSIZ]
设备名称(比如,eth0)
unsigned long mem_start
unsigned long mem_end
这两个变量描述设备与内核通信所用到的内存边界。它们由设备驱动初始化,并且只能被设备驱动访问;高层协议不需要关心这块内存。
unsigned long base_addr
映射到设备内存空间中I/O内存起始地址。
unsigned int irq
设备中断号。它可以被多个设备共享。设备驱动调用request_irq来分配这个值,并调用free_irq来释放它。
unsigned char if_port
接口的端口类型。
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 short flags
unsigned short gflags
unsigned short priv_flags
flags变量中的某些位表示网络设备所支持的功能(例如是否支持多播IFF_MULTICAST等),其他位表示设备状态的变化(例如IFF_UP或者 IFF_RUNNING)。你可以在include/linux/if.h中找到flags 的完整列表。设备驱动通常在设备初始化时设置设备所支持的功能,而设备状态则由内核根据某些外部事件来设置。flags的值可以通过命令ifconfig 查看:
bash# ifconfig lo
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:3924 Metric:1
RX packets:198 errors:0 dropped:0 overruns:0 frame:0
TX packets:198 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
在这个例子中,UP LOOPBACK RUNNING等名词分别对应标记IFF_UP, IFF_LOOPBACK,IFF_RUNNING。
priv_flags存储一些对用户空间程序不可见的标记。目前,这个变量被VLAN和桥虚拟设备使用。
gflags几乎不会被使用,保留它只是为了保持兼容。标记可以通过dev_change_flags函数修改。
int features
另一个使用比特位来表示设备所支持的功能的变量。这个变量与上述的三个变量并不重复。features变量向CPU报告设备所支持的功能,例如设备是否支持高端内存的DMA,是否支持用硬件计算包的校验和等。所有可能支持的功能列表都在net_device结构中定义。这个变量由设备驱动初始化。你可以在 net_device结构的定义中找到相应的NETIF_F_XXX列表,每个宏都有很好的注释。
unsigned mtu
MTU的意思是最大传输单元,它表示设备可以处理帧的最大长度。表1列出了一些常见网络设备所支持的MTU值。
Table 1. MTU values for different device types
Device type | MTU |
---|
PPP | 296 |
SLIP | 296 |
Ethernet | 1,500 |
ISDN | 1,500 |
PLIP | 1,500 (ether_setup) |
Wavelan | 1,500 (ether_setup) |
EtherChannel | 2,024 |
FDDI | 4,352 |
Token Ring 4 MB/s (IEEE 802.5) | 4,464 |
Token Bus (IEEE 802.4) | 8,182 |
Token Ring 16 MB/s (IBM) | 17,914 |
Hyperchannel | 65,535 |
在1998年,Alteon Networks(2000年被Nortel Networks收购)提议将以太网帧的负载大小增加到9K字节。这个提案后来成为IETF的因特网草案,但是IEEE却没有接受它。IEEE规范中超过 1500字节的帧通常被成为jumbo帧,这种帧用在千兆以太网中以增加通吐量。这是因为更大的帧意味着用更少的帧可以传输更多的数据,从而减少中断的数量,减少CPU的使用量,并减少头部数据的占用率。关于增加以太网的MTU可以带来的好处,以及为什么IEEE没有接受这个扩展的原因可以在白皮书“在以太网中使用扩展的帧大小”中找到。这篇文章可以在网上搜索, 或者使用这个链接:
http://www.ietf.org/proceedings/01aug/I-D/draft-ietf-isis-ext-eth-01.txt
unsigned short type
设备类型(以太网,帧中继等)。在include/linux/if_arp.h中有完整的类型列表。
unsigned short hard_header_len
以字节为单位的帧头部长度。例如,以太网帧的头是14字节。某种设备所支持帧的头部长度在相应的设备头文件中定义。对以太网来说,ETH_HLEN在中定义。
unsigned char broadcast[MAX_ADDR_LEN]
链路层广播地址。
unsigned char dev_addr[MAX_ADDR_LEN]
unsigned char addr_len
dev_addr是设备的链路层地址,不要把它和IP地址或者L3地址混淆了。链路层地址的长度是addr_len,以字节为单位。
int promiscuity
混杂模式
2.1. Interface types and ports
有些设备可以支持多种接口(最常见的组合是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; }
2.2. 混杂模式
有些网络管理任务需要系统接收所有经过共享线缆的帧,而不仅是发给本机的帧;如果一个设备可以接收共享线缆上的所有帧,那么就可以说它工作在混杂模式下。如果一个应用程序需要测试本地网的网络性能或者检查网络的安全漏洞时,就需要使用混杂模式。混杂模式同样被网桥代码使用。当然,这对那些恶意的监听者也很有用,所有,在本地网上传输的数据都是不安全的,除非数据已被加密。
net_device结构中包含一个promiscuity计数器来标识设备是否工作在混杂模式。之所以使用计数器而不是一个标志位的原因是:可能有多个用户程序设置设备工作在混杂模式下。因此,每次进入混杂模式,计数器加一;退出混杂模式,计数器减一。只有计数器为0 时,设备才退出混杂模式。这个变量通常调用函数dev_set_promiscuity来设置。
如果promiscuity不为0(调用函数dev_set_promiscuity设置),那么flags变量里面的 IFF_PROMISC标志位会被置位,这个标志位会在配置接口属性的函数中被检查。下面是一段摘自drivers/net/3c59x.c的代码,它展示了根据flags中的标志位来设置不同接收模式的过程:
static void set_rx_mode(struct net_device *dev)
{
int ioaddr = dev->base_addr;
int new_mode;
if (dev->flags & IFF_PROMISC) {
if (corqscreq_debug > 3)
printk("%s: Setting promiscuous mode./n", dev->name);
new_mode = SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm;
} else if ((dev->mc_list) || (dev->flags & IFF_ALLMULTI)) {
new_mode = SetRxFilter | RxStation | RxMulticast | RxBroadcast;
} else
new_mode = SetRxFilter | RxStation | RxBroadcast;
outw(new_mode, ioaddr + EL3_CMD); }
如果设置了IFF_PROMISC标志,new_mode变量会被初始化为接收发给本机的帧(RxStation),多播帧(RxMulticast),广播帧(RxBroadcast),和其他所有的帧(RxProm)。EL3_CMD是距离设备内存起始地址的偏移量,它是内核发送给设备的命令所存储的地址。
3. Statistics
net_device结构里面没有包含描述各种统计信息的变量,相反,它包含一个名为priv的指针,这个指针指向一个设备私有的,描述设备统计信息的数据结构。这个私有数据结构中包括了诸如设备发包数,设备收包数,以及出错包数等统计信息的变量。
priv所指向的数据结构的定义与设备类型和设备的工作模式有关:因此,不同的以太网卡可能拥有不同的私有数据结构。但是,几乎所有的结构都包含一个类型是net_device_stats的变量(在include/linux/netdevice.h中定义),它包含了所有设备都具有的常用统计信息,这些信息可以通过函数get_stats来获取。
无线设备的行为与有线设备的行为不同,因此,在无线设备里没有net_device_stats类型的变量。相反,无线设备有一个类型是iw_statistics的变量,这个变量的值可以通过函数get_wireless_stats来获取,这个函数会在后面描述。
priv所指向的数据结构的名称有时会反映设备的类型(例如,vortex_private代表Vortex和 Boomerang系列,有时也被称作3c59x系列)。一般情况下,它的名字就是简单的net_local。但是,net_local中的变量对不同的设备来说,都是不同的。
私有数据结构的复杂度与设备所支持的功能,以及设备驱动作者希望支持多么复杂的统计功能,或者多么复杂的设计以提高设备性能有关。例如,drivers/net/3c507.c中的3c507驱动所定义的net_local就比drivers/net/3c59x.c中的 3c59x驱动所定义的vortex_private简单的多。当然,它们都包含一个类型是net_device_stats的变量。
4. Device Status
为了控制与设备的交互,每个设备驱动都会维护一些信息,比如:时间戳,接口支持哪些功能的标记等。在SMP系统中,内核需要保证不同cpu对同一设备的并发访问能够正确地执行。net_device结构中的以下几个变量实现这些功能:
unsigned long 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);
}
enum {...} reg_state
设备的注册状态。
unsigned long trans_start
最后一个帧开始发送的时间(用jiffies度量)。设备驱动在发送之前设置这个变量。这个变量用来检测网卡是否在给定的时间内把帧发送了出去。太长的发送时间意味着有错误发生,在这种情况下,设备驱动会重置网卡。
unsigned long last_rx
-
接收到最后一个包的时间(用jiffies度量)。目前,这个变量没有什么特殊的用途,但是,将来有可能会用到。
struct net_device *master
-
有些协议允许多个设备组合到一起当做一个设备使用。这些协议包括EQL(串行网络的复载均衡),Bonding(又被称作EtherChannel和trunking),和TEQL(trueequalizer,它是流量控制子系统中的一个排队策略)。在设备组中,有一个设备被选出来当作主设备,它有特殊的作用。这个变量是一个指向组中主设备的指针。如果设备不是一个组的成员,这个指针被置为NULL。
spinlock_t xmit_lock
-
int xmit_lock_owner
-
xmit_lock用来序列化对设备驱动函数hard_start_xmit的调用。这意味着,每个cpu每次只能调用设备完成一次发送。 xmit_lock_owner是拥有锁的CPU的ID。在单cpu系统上,这个值是0;在多cpu系统中,如果锁没有被占用,这个值是-1。内核同样允许不加锁的发送,前提条件是设备驱动必须支持这个功能
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地址列表等。
5. List Management
net_device结构放在一个全局的链表和两个哈希表中。以下变量用于组成这些链表和哈希表:
struct net_device *next
-
指向全局链表中的下一个节点
struct hlist_node name_hlist
-
struct hlist_node index_hlist
-
把net_device结构链接到两个哈希表中。
6. Link Layer Multicast
多播是一种将数据传递给多个接收者的技术。多播既可以在L3(如ip)上实现,也可以在L2(如以太网)上实现。在这一节,我们关注的是后面一种实现。
链路层多播可以通过特殊的多播地址或者在链路层头部填入特殊的控制信息来实现(如果链路层协议不支持多播,可以采用模拟的方式实现)。以太网本身就支持多播。多播地址通过一个特定的位与其他地址区分开。这就意味着,有50%的地址是多播地址,它的数量是2的48次方,这是一个巨大的数字。如果一个接口要加入一个多播组(一个多播地址就是一个多播组),它可以简单地接收所有多播地址的帧,而不是维护一个多播地址列表,并且过滤那些不在列表中的地址。net_device结构中的flags变量中有一位来控制设备是否监听所有的地址。设备驱动设置或清除这个标记的动作由 all_multi变量控制。
每个net_device结构都会包含一个dev_mc_list类型的变量,这个变量维护设备监听的多播地址表。添加和删除多播地址可以分别调用函数dev_mc_add和dev_mc_delete完成。在net_device中,相关的变量还包括:
struct dev_mc_list *mc_list
-
Pointer to the head of this device's list of dev_mc_list structures.
int mc_count
-
The number of multicast addresses for this device, which is also the length of the list to which mc_list points.
int allmulti
-
如果是非零值,那么设备将监听所有的多播地址。和promiscuity一样,这个变量是一个计数器而不仅是一个布尔值。这是因为多个设备(比如VLAN和bonding设备)可能独立地要求监听所有地址。如果这个变量的值从0变为非零,内核会调用函数 dev_set_allmulti通知设备监听所有的多播地址。如果这个值变为0,则停止监听所有的多播地址。
7. Traffic Management
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用于避免并发的访问ingress_lock用于保护接收队列。
unsigned long tx_queue_len
-
设备发送队列的长度。如果内核中包含了流量控制子系统,这个变量可能没有什么用(只有几个排队策略会使用它)。表2列出了常见设备的tx_queue_len值。这个值可以通过sysfs文件系统修改(在/sys/class/net/device_name/目录下)。
Table 2. tx_queue_len values for different device types
Device type | tx_queue_len |
---|
Ethernet | 1,000 |
Token Ring | 100 |
EtherChannel | 100 |
Fibre Channel | 100 |
FDDI | 100 |
TEQL (true link equalizer) | 100 |
ISDN | 30 |
HIPPI | 25 |
PLIP | 10 |
SLIP | 10 |
AX25 | 10 |
EQL (Equalizer load balancer for serial network interfaces) | 5 |
Generic PPP | 3 |
Bonding | 0 |
Loopback | 0 |
Bridge | 0 |
VLAN | 0 |
a TEQL is one of the queuing disciplines you can configure with Traffic Control (the QoS layer). |
用不用tx_queue_len与设备的接收,发送队列的排队策略有关。一般情况下都使用FIFO
队列(先进先出)或者其他一些简单的队列。
虚拟设备的队列长度一般都是0:它们依赖于真实设备来完成包的缓存(loopback设备是一个特例,它们不需要队列,因为发给它们的包一般都是在内核中立即发送的)。
8. Feature Specific
就像我们在sk_buff中看到的一样,net_device中的有些变量是与特性相关的,只有内核包含了这个特性,这些变量才会有效。[]这些变量只有在内核包含某些特性是才有效,比如br_port。
struct divert_blk *divert
-
Diverter功能允许你修改进入包的源地址和目的地址。它可以把特定包转发到某个特定接口或主机。为了使用这个功能,内核必须包含其他一些功能,比如网桥。这个指针指向的数据结构中包含了这个功能所使用的变量。相应的内核选项是“Device drivers ->Networking support ->Networking options ->Frame Diverter”。
struct net_bridge_port *br_port
-
设备被配置成网桥接口时所需要的附加信息。相应的内核选项是“Device drivers ->Networking support ->Networking options->802.1d Ethernet Bridging”。
void (*vlan_rx_register)(...)
-
void (*vlan_rx_add_vid)(...)
-
void (*vlan_rx_kill_vid)(...)
-
这三个函数指针被VLAN设备用来注册设备可以处理的VLAN标记(参见net/8021q/vlan.c),它可以向设备增加一个VLAN或者从设备上删除一个VLAN。相应的内核选项是“Device drivers ->Networking support ->Networking options->802.1Q VLAN Support”
int netpoll_rx
-
void (*poll_controller)(...)
9. Generic
除了前面提到的维护设备链表的变量外,还有一些变量用于维护net_device结构。需要注意的是,如果这些变量不再有什么作用,就会被删掉:
atomic_t refcnt
-
引用计数。如果计数不为0,设备就不能被卸载
int watchdog_timeo
-
struct timer_list watchdog_timer
-
这些变量,包括前面提到的tx_timeout变量,用于实现 “Watchdog timer”
int (*poll)(...)
-
struct list_head poll_list
-
int quota
-
int weight
-
const struct iw_handler_def *wireless_handlers
-
struct iw_public_data *wireless_data
-
Additional parameters and function pointers used by wireless devices. See also get_wireless_stats.
struct list_head todo_list
-
注册和卸载一个网络设备需要两个步骤,todo_list在第二个步骤中使用。
struct class_device class_dev
-
Used by the new generic kernel driver infrastructure.
10. Function Pointers
内核网络代码使用了很多函数指针。net_device结构中同样包括了许多函数指针。这些函数指针主要完成以下几个功能:
-
Transmit and receive a frame
-
Add or parse the link layer header on a buffer
-
Change a part of the configuration
-
Retrieve statistics
-
Interact with a specific feature
A few function pointers were already introduced in the previous sections when describing the fields used to accomplish a specific task. Here are the generic ones:
struct ethtool_ops *ethtool_ops
-
设置或获取不同设备参数的一组函数指针。
int (*init)(...)
-
void (*uninit)(...)
-
void (*destructor)(...)
-
int (*open)(...)
-
int (*stop)(...)
-
Used to initialize, clean up, destroy, enable, and disable a device. Not all of them are always used.
struct net_device_stats* (*get_stats)(...)
-
struct iw_statistics* (*get_wireless_stats)(...)
-
有些设备统计信息可以通过用户空间程序显示,比如ifconfig和ip;而其他统计信息只能被内核使用。有两个函数用于获取设备统计信息。get_stats用于操作一个普通设备,而get_wireless_stats
用于操作一个无线设备。
int (*hard_start_xmit)(...)
-
Used to transmit a frame.
int (*hard_header)(...)
-
int (*rebuild_header)(...)
-
int (*hard_header_cache)(...)
-
void (*header_cache_update)(...)
-
int (*hard_header_parse)(...)
-
int (*neigh_setup)(...)
-
Used by the neighboring layer.
int (*do_ioctl)(...)
-
ioctl is the system call used to issue commands to devices . This method is called to process some of the ioctl commands .
void (*set_multicast_list)(...)
-
mc_list和mc_count用于维护L2的多播地址表。这个方法用于设置设备驱动监听哪些多播地址。通常情况下,它不会被直接调用,而是通过一个包装函数,比如dev_mc_upload或者不加锁版本__dev_mc_upload调用。如果一个设备不能维护一个多播地址表,那么可以简单地设置它监听所有的多播地址。
int (*set_mac_address)(...)
-
Changes the device MAC address. When the device does not provide this capability (as in the case of Bridge virtual devices), it is set to NULL.
int (*set_config)(...)
-
设置设备参数,比如irq,io_addr,和if_port等。高层参数(比如协议地址)由do_ioctl设置。只有很少的设备使用这个函数。新的设备一般都采用自动探测的方式获取这些参数。你可以在drivers/net/sis900.c中找到 sis900_set_config,这是一个很好的例子。
int (*change_mtu)(...)
-
修改设备的MTU。修改mtu不会对设备驱动有任何影响,它只是让协议栈软件可以根据新的 mtu正确地处理分片。
void (*tx_timeout)(...)
-
在watchdog定时器超时后调用这个函数。它用来确定发送一个帧的时间是否太长。如果这个函数没有定义,watchdog定时器就不会被启用。
int (*accept_fastpath)(...)
-
快速交换(又被称作FASTROUTE)是内核的一个功能,它允许设备驱动在中断上下文中用一个小的缓存来快速路由进入的包(跳过协议层软件)。从2.6.8版开始, 内核已经不支持这个功能了。这个函数用于测试一个设备是否支持快速交换功能。