我们都知道可以从/proc/net/dev下去读取网络设备收发包时相关的数据,但之前从来没有关注这些文件的来源,直到前几天遇到一个wifi的tx和rx等数据都为0的问题不得不去探索原因,起初以为/proc/net/dev是按照文件操作的方式写进去的,结果在应用层和驱动中找了一遍没有发现相关的代码,于是到linux内核代码里一探究竟,果不其然。(我使用的linux版本为linux4.1.25)。
进入linux-lsk-v4.1.25\net\core\net_procfs.c,看到这个名字是不是恍然大悟呢?对,这就是产生/proc/net/dev文件的入口,下面将逐一进行分析:
int __init dev_proc_init(void)
{
int ret = register_pernet_subsys(&dev_proc_ops);
if (!ret)
return register_pernet_subsys(&dev_mc_net_ops);
return ret;
}
这里将dev_proc_ops进行了注册,驱动加载后,或将网络设备注册到linux内核,这时候linux内核就会调用这个接口,执行dev_proc_ops定义的函数,该函数如下:
static struct pernet_operations __net_initdata dev_proc_ops = {
.init = dev_proc_net_init,
.exit = dev_proc_net_exit,
};
不用我说吧,这里分别是开始和结束时执行的函数,我们只关注开始的dev_proc_net_init函数,结束时的函数有兴趣的可自己去看,具体如下:
static int __net_init dev_proc_net_init(struct net *net)
{
int rc = -ENOMEM;
if (!proc_create("dev", S_IRUGO, net->proc_net, &dev_seq_fops))
goto out;
if (!proc_create("softnet_stat", S_IRUGO, net->proc_net,&softnet_seq_fops))
goto out_dev;
if (!proc_create("ptype", S_IRUGO, net->proc_net, &ptype_seq_fops))
goto out_softnet;
if (wext_proc_init(net))
goto out_ptype;
rc = 0;
out:
return rc;
out_ptype:
remove_proc_entry("ptype", net->proc_net);
out_softnet:
remove_proc_entry("softnet_stat", net->proc_net);
out_dev:
remove_proc_entry("dev", net->proc_net);
goto out;
}
想必大家都看到了,函数创建/proc/net/dev,/proc/net/softnet_stat,/proc/net/ptype文件,然后指定了文件的操作接口的函数指针,我们现在只关注/proc/net/dev的文件操作指针dev_seq_fops。具体如下:
static const struct file_operations dev_seq_fops = {
.owner = THIS_MODULE,
.open = dev_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_net,
};
我们知道实际上对/proc下的文件进行操作时,cat操作对应的是.open函数,echo操作对应的是.write函数,这里我们只关注open函数,对应的函数指针是dev_seq_open,如下:
static int dev_seq_open(struct inode *inode, struct file *file)
{
return seq_open_net(inode, file, &dev_seq_ops,
sizeof(struct seq_net_private));
}
这里又来了一个dev_seq_ops,经常看linux内核代码的都知道,实际上我们不需要注seq_open_net函数,直接进入dev_seq_ops即可,具体如下:
static const struct seq_operations dev_seq_ops = {
.start = dev_seq_start,
.next = dev_seq_next,
.stop = dev_seq_stop,
.show = dev_seq_show,
};
翻了这么多大山,这里终于看了了山顶,那就是这个dev_seq_show函数了,我想大家看到的时候应该都会第一时间去看这个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;
}
上面的if分之就是用来打印/proc/net/dev头部的,你可以cat /proc/net/dev仔细对照看一下是不是酱紫。else分之是用来打印/proc/net/dev的身体的,为什么是对立的分支呢?因为linux起来后刚开始没有检测到网络设备,此时的v就是SEQ_START_TOKEN,管他有没有呢,先把头搞出来再说。下面看一下身体吧,这才是应该重点关注的。
static void dev_seq_printf_stats(struct seq_file *seq, structnet_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);
}
这里总算看明白了吧?这个net_device就是驱动中注册网络设备时的结构体,至于里面的这些收发包的数据,当然是在驱动中产生的了,具体就不详细叙述了。