如何在Ubuntu 22.1中修改dev结构体stats的rx_bytes和tx_bytes字段
解决Linux内核网络设备统计值(rx_bytes/tx_bytes)重启后恢复的问题
核心思路
要实现重启后恢复统计值,需分两步完成:关机前将当前统计值保存到持久化存储,开机后读取备份值并修改内核中对应字段。
步骤1:扩展现有模块,添加统计值保存功能
修改内核模块,在卸载或系统关机时,把各网卡的rx_bytes、tx_bytes写入文件系统(示例路径为/var/lib/net_stats_backup)。
修改后的保存模块代码
#include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/netdevice.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/version.h> #include <linux/reboot.h> #define BACKUP_PATH "/var/lib/net_stats_backup" static void save_net_stats(void) { struct net_device *dev = first_net_device(&init_net); struct rtnl_link_stats64 stats64; struct file *file; loff_t pos = 0; char buf[256]; int ret; file = filp_open(BACKUP_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (IS_ERR(file)) { printk(KERN_ERR "Failed to open backup file: %ld\n", PTR_ERR(file)); return; } while (dev) { if (netif_running(dev)) { dev_get_stats(dev, &stats64); snprintf(buf, sizeof(buf), "%s %llu %llu\n", dev->name, stats64.rx_bytes, stats64.tx_bytes); ret = kernel_write(file, buf, strlen(buf), &pos); if (ret < 0) { printk(KERN_ERR "Failed to write stats for %s: %d\n", dev->name, ret); } } dev = next_net_device(dev); } filp_close(file, NULL); printk(KERN_INFO "Net stats saved to %s\n", BACKUP_PATH); } static int shutdown_notify(struct notifier_block *nb, unsigned long code, void *unused) { if (code == SYS_DOWN || code == SYS_HALT || code == SYS_POWER_OFF) { save_net_stats(); } return NOTIFY_OK; } static struct notifier_block shutdown_nb = { .notifier_call = shutdown_notify, }; static int mymodule_init(void) { struct net_device *dev = first_net_device(&init_net); struct rtnl_link_stats64 stats64; printk(KERN_INFO "Loading net stats backup module\n"); register_reboot_notifier(&shutdown_nb); while (dev) { if (netif_running(dev)) { dev_get_stats(dev, &stats64); printk(KERN_INFO "%s: rx_bytes=%llu, tx_bytes=%llu\n", dev->name, stats64.rx_bytes, stats64.tx_bytes); } dev = next_net_device(dev); } return 0; } static void mymodule_exit(void) { unregister_reboot_notifier(&shutdown_nb); save_net_stats(); printk(KERN_INFO "Backup module unloaded, stats saved\n"); } module_init(mymodule_init); module_exit(mymodule_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Kiavash Rocky"); MODULE_DESCRIPTION("Read and backup network device stats");
步骤2:编写开机恢复模块
实现一个模块,开机加载时读取备份文件,修改对应网卡的统计值。注意:不同驱动的统计存储结构可能有差异,以下是通用修改逻辑,需根据实际驱动调整。
恢复模块代码
#include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/netdevice.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/version.h> #include <linux/string.h> #include <linux/spinlock.h> #define BACKUP_PATH "/var/lib/net_stats_backup" static int restore_net_stats(void) { struct net_device *dev; struct file *file; loff_t pos = 0; char buf[256]; char dev_name[IFNAMSIZ]; unsigned long long rx_bytes, tx_bytes; int ret; file = filp_open(BACKUP_PATH, O_RDONLY, 0); if (IS_ERR(file)) { printk(KERN_ERR "Failed to open backup file: %ld\n", PTR_ERR(file)); return PTR_ERR(file); } while ((ret = kernel_read(file, buf, sizeof(buf)-1, &pos)) > 0) { buf[ret] = '\0'; char *line = buf; while (*line) { while (*line && isspace(*line)) line++; if (!*line) break; char *name_end = line; while (*name_end && !isspace(*name_end)) name_end++; if (name_end == line) break; strncpy(dev_name, line, name_end - line); dev_name[name_end - line] = '\0'; line = name_end; while (*line && isspace(*line)) line++; if (!*line) break; rx_bytes = simple_strtoull(line, &line, 10); while (*line && isspace(*line)) line++; if (!*line) break; tx_bytes = simple_strtoull(line, &line, 10); dev = dev_get_by_name(&init_net, dev_name); if (!dev) { printk(KERN_WARNING "Device %s not found, skip restore\n", dev_name); continue; } if (netif_running(dev)) { spin_lock_bh(&dev->stats_lock); dev->stats.rx_bytes = rx_bytes; dev->stats.tx_bytes = tx_bytes; // 若驱动使用64位统计结构,需打开以下注释(根据实际驱动调整) // #ifdef CONFIG_NETSTATS_INC_USE_STATS64 // dev->stats64.rx_bytes = rx_bytes; // dev->stats64.tx_bytes = tx_bytes; // #endif spin_unlock_bh(&dev->stats_lock); printk(KERN_INFO "Restored %s: rx_bytes=%llu, tx_bytes=%llu\n", dev_name, rx_bytes, tx_bytes); } dev_put(dev); } } filp_close(file, NULL); return 0; } static int mymodule_init(void) { printk(KERN_INFO "Loading net stats restore module\n"); return restore_net_stats(); } static void mymodule_exit(void) { printk(KERN_INFO "Restore module unloaded\n"); } module_init(mymodule_init); module_exit(mymodule_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Kiavash Rocky"); MODULE_DESCRIPTION("Restore network device stats from backup");
关键注意事项
- 权限配置:提前创建
/var/lib/net_stats_backup文件并设置权限为644,确保内核模块能读写该文件。 - 驱动兼容性:部分网络驱动使用私有统计结构体而非
dev->stats,需查看对应驱动源码(如e1000、virtio_net)确定正确的修改字段。 - 加载时机:恢复模块需在网络设备初始化完成后加载,可通过
/etc/modules-load.d/net_stats_restore.conf配置开机自启。
编译与测试
- 编写Makefile:
obj-m += net_stats_backup.o obj-m += net_stats_restore.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
- 编译模块:执行
make - 加载备份模块:
insmod net_stats_backup.ko - 关机或卸载备份模块:
rmmod net_stats_backup,此时统计值会保存到备份文件 - 重启后加载恢复模块:
insmod net_stats_restore.ko,通过dmesg | grep Restored查看恢复结果
内容的提问来源于stack exchange,提问作者Kiavash Rocky
相关产品推荐
相关产品推荐

