1)ifconfig 是通过解析 /proc/net/dev 文件来获得网卡的统计信息的,如下所示:
$ cat /proc/net/dev
Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
enp3s0: 9666434 145598 0 0 0 0 0 20 28796033 143998 0 0 0 0 0 0
enp0s3f0: 89753829 1339304 0 0 0 0 0 0 220083622 1303148 0 0 0 0 0 0
lo: 8551535 24076 0 0 0 0 0 0 8551535 24076 0 0 0 0 0 0
virbr0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
virbr0-nic: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2)/proc/net/dev 里面的内容是如何产生的?
/proc/net/dev 是由内核 dev_seq_show() 函数输出的,它的实现如下所示:
static int dev_seq_show(struct seq_file *seq, void *v)
{
if (v == SEQ_START_TOKEN)
seq_puts(seq, "Inter-| Receive "
" | Transmit\n"
" face |bytes packets errs drop fifo frame "
"compressed multicast|bytes packets errs "
"drop fifo colls carrier compressed\n");
else
dev_seq_printf_stats(seq, v);
return 0;
}
其中,dev_seq_printf_stats() 函数完成了最终的输出,它的实现如下所示:
static void dev_seq_printf_stats(struct seq_file *seq, struct net_device *dev)
{
struct rtnl_link_stats64 temp;
const struct rtnl_link_stats64 *stats = dev_get_stats(dev, &temp);
seq_printf(seq, "%6s: %7llu %7llu %4llu %4llu %4llu %5llu %10llu %9llu "
"%8llu %7llu %4llu %4llu %4llu %5llu %7llu %10llu\n",
dev->name, stats->rx_bytes, stats->rx_packets,
stats->rx_errors,
stats->rx_dropped + stats->rx_missed_errors,
stats->rx_fifo_errors,
stats->rx_length_errors + stats->rx_over_errors +
stats->rx_crc_errors + stats->rx_frame_errors,
stats->rx_compressed, stats->multicast,
stats->tx_bytes, stats->tx_packets,
stats->tx_errors, stats->tx_dropped,
stats->tx_fifo_errors, stats->collisions,
stats->tx_carrier_errors +
stats->tx_aborted_errors +
stats->tx_window_errors +
stats->tx_heartbeat_errors,
stats->tx_compressed);
}
其中,dev_get_stats() 函数是用来获取网卡统计信息的,它的实现如下所示:
struct rtnl_link_stats64 *dev_get_stats(struct net_device *dev,
struct rtnl_link_stats64 *storage)
{
const struct net_device_ops *ops = dev->netdev_ops;
if (ops->ndo_get_stats64) {
memset(storage, 0, sizeof(*storage));
ops->ndo_get_stats64(dev, storage);
} else if (ops->ndo_get_stats) {
netdev_stats_to_stats64(storage, ops->ndo_get_stats(dev));
} else {
netdev_stats_to_stats64(storage, &dev->stats);
}
storage->rx_dropped += (unsigned long)atomic_long_read(&dev->rx_dropped);
storage->tx_dropped += (unsigned long)atomic_long_read(&dev->tx_dropped);
storage->rx_nohandler += (unsigned long)atomic_long_read(&dev->rx_nohandler);
return storage;
}
从上面可以看到,dev_get_stats() 函数首先判断网卡驱动是否实现了 dev->netdev_ops->ndo_get_stats64()、dev->netdev_ops->ndo_get_stats() 这两个接口,如果实现了,则通过这两个接口来获取网卡的统计信息,如果没有实现,则使用 netdev_stats_to_stats64() 函数来获取网卡的统计信息,该函数的实现如下所示:
void netdev_stats_to_stats64(struct rtnl_link_stats64 *stats64,
const struct net_device_stats *netdev_stats)
{
#if BITS_PER_LONG == 64
BUILD_BUG_ON(sizeof(*stats64) < sizeof(*netdev_stats));
memcpy(stats64, netdev_stats, sizeof(*netdev_stats));
/* zero out counters that only exist in rtnl_link_stats64 */
memset((char *)stats64 + sizeof(*netdev_stats), 0,
sizeof(*stats64) - sizeof(*netdev_stats));
#else
size_t i, n = sizeof(*netdev_stats) / sizeof(unsigned long);
const unsigned long *src = (const unsigned long *)netdev_stats;
u64 *dst = (u64 *)stats64;
BUILD_BUG_ON(n > sizeof(*stats64) / sizeof(u64));
for (i = 0; i < n; i++)
dst[i] = src[i];
/* zero out counters that only exist in rtnl_link_stats64 */
memset((char *)stats64 + n * sizeof(u64), 0,
sizeof(*stats64) - n * sizeof(u64));
#endif
}
可以看到,netdev_stats_to_stats64() 函数只是做了一个数据拷贝,其数据源来自于是网卡对应的 struct net_device 结构体中的 stats 成员,它的类型是 struct net_device_stats。以 Gmac 为例,它是在处理单个包的过程中,顺便更新了该结构体里的各个成员。例如,在驱动的发包函数 stmmac_xmit() 中更新发送字节数:
static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
{
...
dev->stats.tx_bytes += skb->len;
...
}