13-参数设置和统计数据
16.8 参数设置和统计数据
在网络设备的驱动程序中还提供一些方法供系统对设备的参数进行设置或读取设备相关的信息。
当用户调用ioctl()函数,并指定SIOCSIFHWADDR命令时,意味着要设置这个设备的MAC地址。设置网络设备的MAC地址可用如代码清单16.14所示的模板。
代码清单16.14 设置网络设备的MAC地址
1 static int set_mac_address(struct net_device dev, void addr)
2 {
3 if (netif_running(dev))
4 return -EBUSY; / 设备忙 /
5
6 / 设置以太网的MAC地址 /
7 xxx_set_mac(dev, addr);
8
9 return 0;
10 }
上述程序首先用netif_running()宏判断设备是否正在运行,如果是,则意味着设备忙,此时不允许设置MAC地址;否则,调用xxxset mac()函数在网络适配器硬件内写入新的MAC地址。这要求设备在硬件上支持MAC地址的修改,而实际上,许多设备并不提供修改MAC地址的接口。
netif_running()宏的定义为:
define netif_running(dev) (dev->flags & IFF_UP)
当用户调用ioctl()函数时,若命令为SIOCSIFMAP(如在控制台中运行网络配置命令ifconfig就会引发这一调用),系统会调用驱动程序的set_config()函数。
系统会向set_config()函数传递一个ifmap结构体,该结构体中主要包含用户欲设置的设备要使用的I/O地址、中断等信息。注意,并非ifmap结构体中给出的所有修改都是可以被接受的。实际上,大多数设备并不宜包含set_config()函数。set_config()函数的例子如代码清单16.15所示。
代码清单16.15 网络设备驱动的set_config函数模板
1 int xxx_config(struct net_device dev, struct ifmap map)
2 {
3 if (netif_running(dev)) / 不能设置一个正在运行状态的设备 /
4 return - EBUSY;
5
6 / 假设不允许改变 I/O 地址 /
7 if (map->base_addr != dev->base_addr) {
8 printk(KERN_WARNING "xxx: Can't change I/O address\n");
9 return - EOPNOTSUPP;
10 }
11
12 / 假设允许改变 IRQ /
13 if (map->irq != dev->irq)
14 dev->irq = map->irq;
15
16 return 0;
17 }
上述代码中的set_config()函数接受IRQ的修改,拒绝设备I/O地址的修改。具体的设备是否接收这些信息的修改,要视硬件的设计而定。
如果用户调用ioctl()时,命令类型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之间,系统会调用驱动程序的do_ioctl()函数,进行设备专用数据的设置。这个设置大多数情况下也并不需要。
驱动程序还应提供get_stats()函数用以向用户反馈设备状态和统计信息,该函数返回的是一个net_device_stats结构体,如代码清单16.16所示。
代码清单16.16 网络设备驱动的get_stats函数模板
1 struct net_device_stats xxx_stats(struct net_device dev)
2 {
3 struct xxx_priv *priv = netdev_priv(dev);
4 return &priv->stats;
5 }
net_device_stats结构体定义在内核的include/linux/netdevice.h文件中,它包含了比较完整的统计信息,如代码清单16.17所示。
代码清单16.17 net_device_stats结构体
1 struct net_device_stats
2 {
3 unsigned long rx_packets; / 收到的数据包数 /
4 unsigned long tx_packets; / 发送的数据包数 /
5 unsigned long rx_bytes; / 收到的字节数 /
6 unsigned long tx_bytes; / 发送的字节数 /
7 unsigned long rx_errors; / 收到的错误数据包数 /
8 unsigned long tx_errors; / 发生发送错误的数据包数 /
9 ...
10 };
上述代码清单只是列出了net_device_stats包含的主项目统计信息,实际上,这些项目还可以进一步细分,net_device_stats中的其他信息给出了更详细的子项目统计,详见Linux源代码。
net_device_stats结构体适宜包含在设备的私有信息结构体中,而其中统计信息的修改则应该在设备驱动的与发送和接收相关的具体函数中完成,这些函数包括中断处理程序、数据包发送函数、数据包发送超时函数和数据包接收相关函数等。我们应该在这些函数中添加相应的代码,如代码清单16.18所示。
代码清单16.18 net_device_stats结构体中统计信息的维护
1 / 发送超时函数 /
2 void xxx_tx_timeout(struct net_device *dev)
3 {
4 struct xxx_priv *priv = netdev_priv(dev);
5 ...
6 priv->stats.tx_errors++; / 发送错误包数加1 /
7 ...
8 }
9
10 / 中断处理函数 /
11 static void xxx_interrupt(int irq, void *dev_id)
12 {
13 switch (status &ISQ_EVENT_MASK) {
14 ...
15 case ISQ_TRANSMITTER_EVENT: /
16 priv->stats.tx_packets++; / 数据包发送成功,tx_packets信息加1 /
17 netif_wake_queue(dev); / 通知上层协议 /
18 if ((status &(TX_OK | TX_LOST_CRS | TX_SQE_ERROR |
19 TX_LATE_COL | TX_16_COL)) != TX_OK) { /读取硬件上的出错标志/
20 / 根据错误的不同情况,对net_device_stats的不同成员加1 /
21 if ((status &TX_OK) == 0)
22 priv->stats.tx_errors++;
23 if (status &TX_LOST_CRS)
24 priv->stats.tx_carrier_errors++;
25 if (status &TX_SQE_ERROR)
26 priv->stats.tx_heartbeat_errors++;
27 if (status &TX_LATE_COL)
28 priv->stats.tx_window_errors++;
29 if (status &TX_16_COL)
30 priv->stats.tx_aborted_errors++;
31 }
32 break;
33 case ISQ_RX_MISS_EVENT:
34 priv->stats.rx_missed_errors += (status >> 6);
35 break;
36 case ISQ_TX_COL_EVENT:
37 priv->stats.collisions += (status >> 6);
38 break;
39 }
40 }
上述代码的第6行意味着在发送数据包超时时,将发生发送错误的数据包数增1。而第13~38行则意味着当网络设备中断产生时,中断处理程序读取硬件的相关信息以决定修改netdevice stats统计信息中的哪些项目和子项目,并将相应的项目增1。