04-网络设备接口层
16.1.2 网络设备接口层
网络设备接口层的主要功能是为千变万化的网络设备定义了统一、抽象的数据结构net_device结构体,以不变应万变,实现多种硬件在软件层次上的统一。
net_device结构体在内核中指代一个网络设备,定义于include/linux/netdevice.h文件,网络设备驱动程序只需通过填充net_device的具体成员并注册net_device即可实现硬件操作函数与内核的挂接。
net_device是一个巨大的结构体,包含网络设备的属性描述和操作接口,下面介绍其中的一些关键成员。
(1)全局信息。
char name[IFNAMESIZ];
name是网络设备的名称。
int (init)(struct net_device dev);
init为设备初始化函数指针,如果这个指针被设置了,则网络设备被注册时将调用该函数完成对net_device结构体的初始化。但是,设备驱动程序可以不实现这个函数并将其赋值为NULL。
(2)硬件信息。
unsigned long mem_end;
unsigned long mem_start;
mem_start和mem_end 分别定义了设备所使用的共享内存的起始和结束地址。
unsigned long base_addr;
unsigned char irq;
unsigned char if_port;
unsigned char dma;
base_addr为网络设备I/O基地址。
irq为设备使用的中断号。
if_port指定多端口设备使用哪一个端口,该字段仅针对多端口设备。例如,如果设备同时支持IF_PORT_10BASE2(同轴电缆)和IF_PORT_10BASET(双绞线),则可使用该字段。
dma指定分配给设备的DMA通道。
(3)接口信息。
unsigned short hard_header_len;
hard_header_len是网络设备的硬件头长度,在以太网设备的初始化函数中,该成员被赋为ETH_HLEN,即14。
unsigned short type;
type是接口的硬件类型。
unsigned mtu;
mtu指最大传输单元(MTU)。
unsigned char dev_addr[MAX_ADDR_LEN];
unsigned char broadcast[MAX_ADDR_LEN];
dev_addr[ ]、broadcast[ ]无符号字符数组,分别用于存放设备的硬件地址和广播地址。对于以太网而言,这两个地址的长度都为6个字节。以太网设备的广播地址为6个0xFF,而MAC地址需由驱动程序从硬件上读出并填充到dev_addr[ ]中。
unsigned short flags;
flags指网络接口标志,以IFF_(interface flags)开头,部分标志由内核来管理,其他的在接口初始化时被设置以说明设备接口的能力和特性。接口标志包括IFF_UP(当设备被激活并可以开始发送数据包时,内核设置该标志)、IFF_AUTOMEDIA(设备可在多种媒介间切换)、IFF_BROADCAST(允许广播)、IFF_DEBUG(调试模式,可用于控制printk调用的详细程度)、IFF_LOOPBACK(回环)、IFF_MULTICAST(允许组播)、IFF_NOARP(接口不能执行ARP)、IFF_POINTOPOINT(接口连接到点到点链路)等。
(4)设备操作函数。
int (open)(struct net_device dev);
int (stop)(struct net_device dev);
open()函数的作用是打开网络接口设备,获得设备需要的I/O地址、IRQ、DMA通道等。stop()函数的作用是停止网络接口设备,与open()函数的作用相反。
int (hard_start_xmit) (struct sk_buff skb,struct net_device *dev);
hard_start_xmit()函数会启动数据包的发送,当系统调用驱动程序的hard_start_xmit()函数时,需要向其传入一个sk_buff结构体指针,以使得驱动程序能获取从上层传递下来的数据包。
void (tx_timeout)(struct net_device dev);
当数据包的发送超时时,tx_timeout ()函数会被调用,该函数需采取重新启动数据包发送过程或重新启动硬件等措施来恢复网络设备到正常状态。
int (hard_header) (struct sk_buff skb,
struct net_device *dev,
unsigned short type,
void *daddr,
void *saddr,
unsigned len);
hard_header()函数完成硬件帧头填充,返回填充的字节数。传入该函数的参数包括sk_buff指针、设备指针、协议类型、目的地址、源地址以及数据长度。对于以太网设备而言,将内核提供的eth_header()函数赋值给hard_header指针即可。
struct net_device_stats (get_stats)(struct net_device *dev);
get_stats()函数用于获得网络设备的状态信息,它返回一个net_device_stats结构体。net_device_stats结构体保存了网络设备详细的流量统计信息,如发送和接收到的数据包数、字节数等,详见16.8节。
int (do_ioctl)(struct net_device dev, struct ifreq *ifr, int cmd);
int (set_config)(struct net_device dev, struct ifmap *map);
int (set_mac_address)(struct net_device dev, void *addr);
do_ioctl()函数用于进行设备特定的I/O控制。
set_config()函数用于配置接口,可用于改变设备的I/O地址和中断号。
set_mac_address()函数用于设置设备的MAC地址。
const struct ethtool_ops *ethtool_ops;
上述结构体中的一系列成员函数用于更改或报告网络设备的设置,主要包括get_settings、set_settings、get_wol、set_wol、get_eeprom、get_ringparam、set_ringparam等成员函数。
void (poll_controller)(struct net_device dev);
在内核配置了NET_POLL_CONTROLLER的情况下,为了支持纯粹的netconsole(如用于kgdb调试),可以为网络设备驱动实现poll_controller()成员函数,它完全以轮询方式接收数据包。
net_device结构体的上述成员需要根据对应网络设备的具体情况在设备初始化时被填充,详见16.3节。
(5)辅助成员。
unsigned long trans_start;
unsigned long last_rx;
trans_start记录最后的数据包开始发送时的时间戳,last_rx记录最后一次接收到数据包时的时间戳,这两个时间戳记录的都是jiffies,驱动程序应维护这两个成员。
void *priv;
priv为设备的私有信息指针,与filp->private_data的地位相当。设备驱动程序中应该以netdev_priv()函数获得该指针。
通常情况下,网络设备驱动以中断方式接收数据包,而poll_controller()则采用纯轮询方式,另外一种数据接收方式是NAPI(New API)其数据接收流程如下为“接收中断来临—关闭接收中断——以轮询方式接收所有数据包直到收空——开启接收中断——接收中断来临……”。内核中提供了如下与NAPI相关的API:
static inline void netif_napi_add(struct net_device *dev,
struct napi_struct *napi,
int (poll)(struct napi_struct , int),
int weight);
static inline void netif_napi_del(struct napi_struct *napi);
以上两个函数分别用于初始化和移除一个NAPI,netif_napi_add()的poll参数是NAPI要调度执行的轮询函数。
static inline void napi_enable(struct napi_struct *n);
static inline void napi_disable(struct napi_struct *n);
以上两个函数分别用于使能和禁止NAPI调度。
static inline int napi_schedule_prep(struct napi_struct *n);
该函数用于检查NAPI是否可以调度,而napi_schedule()函数用于调度轮询实例的运行,其原型为:
static inline void napi_schedule(struct napi_struct *n);
在NAPI处理完成的时候应该调用:
static inline void napi_complete(struct napi_struct *n);