4. 实现网卡驱动的轮询收包模式
任务说明
本案例在Linux内核驱动中实现网卡轮询收包模式,替代传统中断收包方式。驱动通过内核定时器周期性检查接收描述符环,将到达的数据包上送协议栈,并统计收包数量。同时提供中断模式作为对照,用于观察两种模式在持续小流量下的CPU占用差异。
轮询收包驱动实现
// rx_poll_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#define DRV_NAME "rx_poll_drv"
#define RX_RING_SIZE 64
#define POLL_INTERVAL_MS 10
struct rx_ring {
struct sk_buff *skb[RX_RING_SIZE];
unsigned int head;
unsigned int tail;
};
struct poll_priv {
struct net_device *dev;
struct rx_ring ring;
struct timer_list poll_timer;
atomic_t rx_count;
int use_poll; // 1=轮询, 0=中断
};
static int rx_poll_open(struct net_device *dev)
{
struct poll_priv *priv = netdev_priv(dev);
netif_start_queue(dev);
if (priv->use_poll) {
mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(POLL_INTERVAL_MS));
}
return 0;
}
static int rx_poll_stop(struct net_device *dev)
{
struct poll_priv *priv = netdev_priv(dev);
if (priv->use_poll)
del_timer_sync(&priv->poll_timer);
netif_stop_queue(dev);
return 0;
}
/* 模拟收包:实际驱动从硬件FIFO读取,此处用计数代替 */
static void rx_process_packets(struct poll_priv *priv)
{
struct sk_buff *skb;
int i;
for (i = 0; i < 4; i++) { // 每次最多处理4包
skb = netdev_alloc_skb(priv->dev, 64);
if (!skb)
break;
skb_put(skb, 64);
skb->protocol = eth_type_trans(skb, priv->dev);
netif_rx(skb);
atomic_inc(&priv->rx_count);
}
}
static void poll_timer_cb(struct timer_list *t)
{
struct poll_priv *priv = from_timer(priv, t, poll_timer);
rx_process_packets(priv);
mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(POLL_INTERVAL_MS));
}
static irqreturn_t rx_irq_handler(int irq, void *data)
{
struct net_device *dev = data;
struct poll_priv *priv = netdev_priv(dev);
rx_process_packets(priv);
return IRQ_HANDLED;
}
static int rx_poll_init(struct net_device *dev)
{
struct poll_priv *priv = netdev_priv(dev);
int i;
priv->dev = dev;
atomic_set(&priv->rx_count, 0);
for (i = 0; i < RX_RING_SIZE; i++)
priv->ring.skb[i] = NULL;
priv->ring.head = priv->ring.tail = 0;
if (priv->use_poll) {
timer_setup(&priv->poll_timer, poll_timer_cb, 0);
} else {
/* 实际驱动在此注册IRQ,此处仅示意 */
// request_irq(dev->irq, rx_irq_handler, IRQF_SHARED, DRV_NAME, dev);
}
return 0;
}
static const struct net_device_ops rx_poll_ops = {
.ndo_open = rx_poll_open,
.ndo_stop = rx_poll_stop,
.ndo_init = rx_poll_init,
};
static int __init rx_poll_module_init(void)
{
struct net_device *dev;
struct poll_priv *priv;
int err;
dev = alloc_netdev(sizeof(struct poll_priv), "poll%d", NET_NAME_UNKNOWN,
ether_setup);
if (!dev)
return -ENOMEM;
priv = netdev_priv(dev);
priv->use_poll = 1; // 默认轮询模式
dev->netdev_ops = &rx_poll_ops;
err = register_netdev(dev);
if (err) {
free_netdev(dev);
return err;
}
return 0;
}
static void __exit rx_poll_module_exit(void)
{
struct net_device *dev = dev_get_by_name(&init_net, "poll0");
if (dev) {
unregister_netdev(dev);
free_netdev(dev);
dev_put(dev);
}
}
module_init(rx_poll_module_init);
module_exit(rx_poll_module_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("RX Polling Mode Driver Demo");
中断模式对照与切换
驱动通过模块参数mode控制收包方式:mode=1启用轮询(默认),mode=0使用中断。加载时指定:
sudo insmod rx_poll_drv.ko mode=1 # 轮询模式
sudo insmod rx_poll_drv.ko mode=0 # 中断模式
中断模式下,驱动注册IRQ处理函数,由硬件中断触发收包;轮询模式下,内核定时器每10ms检查一次接收队列。两种模式共用同一套rx_process_packets()收包逻辑,保证对比公平性。
验证方法与判定标准
- 编译与加载:执行
make编译驱动,insmod加载后执行dmesg | tail确认无错误,ip link show poll0可见网卡设备。 - 功能验证:
ifconfig poll0 up后,用ping -I poll0 192.168.1.1发送测试流量,同时执行cat /proc/net/dev观察poll0的RX包计数持续增长。 - 性能对比:分别以
mode=1和mode=0加载驱动,在相同流量下用top观察softirq或ksoftirqd的CPU占用。轮询模式下定时器回调占用CPU时间固定,中断模式下CPU占用随流量波动。判定标准:持续小流量(如100pps)下,轮询模式CPU占用波动小于中断模式,且收包计数无丢失。 - 计数检查:驱动每收到一包递增
rx_count,可通过cat /sys/module/rx_poll_drv/parameters/或添加debugfs接口读取,确认两种模式收包总数一致。
轮询与中断收包路径对比
关键设计说明
本驱动为教学演示,rx_process_packets()中模拟了收包动作(实际硬件驱动需从DMA描述符环读取数据)。轮询周期10ms适合低速率测试场景,实际应用可根据流量调整。中断模式代码路径保留但默认未注册IRQ,需在真实硬件上取消注释request_irq并实现中断处理。两种模式共用收包处理函数,确保对比的是收包触发机制而非处理逻辑差异。