《Linux设备驱动程序》(第三版)第十七章以snull网络接口为例讲解了网络驱动程序的设计,本文分析了snull这个示例,并对驱动程序做了一些简化和修改,只保留该程序的核心功能,并在2.6.31版本的内核上测试运行。
1. 入口和出口函数
Linux网络设备驱动基本是围绕net_device这个结构做文章,程序的入口函数中主要是分配并注册net_device结构体,对应的,出口函数则需要注销注销和释放net_device结构体。本实验最终要用ping命令测试两个网络设备的收发,所以先定义两个指向net_device的指针:
struct net_device *snull_devs[2];
1.1 入口函数:
static __init int snull_init_module(void)
{
int result, i, ret = -ENOMEM;
/* Allocate the devices */
snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d", snull_init);
snull_devs[1] = alloc_netdev(sizeof(struct snull_priv), "sn%d", snull_init);
if (snull_devs[0] == NULL || snull_devs[1] == NULL)
goto out;
ret = -ENODEV;
for (i = 0; i < 2; i++)
if ((result = register_netdev(snull_devs[i])))
printk("snull: error %i registering device \"%s\"\n",
result, snull_devs[i]->name);
else
ret = 0;
out:
if (ret)
snull_cleanup();
return ret;
}
module_init(snull_init_module);
alloc_netdev()函数用于分配net_device结构体:
struct net_device *alloc_netdev(int sizeof_priv, const char *name,
void (*setup)(struct net_device *));
该函数的第一个参数是私有数据的大小,第二个参数是设备名称,第三个参数setup指向一个以struct net_device*为参数的函数,用来初始化net_device的一些成员。
1.2 程序的私有数据定义snull_priv(自旋锁成员的用法就不赘述了):
struct snull_priv {
struct net_device_stats stats;
int status;
struct snull_packet *ppool;
struct snull_packet *rx_queue;
int rx_int_enabled;
int tx_packetlen;
u8 *tx_packetdata;
struct sk_buff *skb;
spinlock_t lock;
};
1.2.1 snull_packet结构用于保存收发的数据:
struct snull_packet {
struct snull_packet *next;
struct net_device *dev;
int datalen;
u8 data[ETH_DATA_LEN];
};
为struct snull_packet *ppool定义的一系列操作函数如下:
int pool_size = 8;
module_param(pool_size, int, 0);
/*
* 分配8个snull_packet并将其添加到一个链表
*/
void snull_setup_pool(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
int i;
struct snull_packet *pkt;
priv->ppool = NULL;
for (i = 0; i < pool_size; i++) {
pkt = kmalloc (sizeof (struct snull_packet), GFP_KERNEL);
if (pkt == NULL) {
printk (KERN_NOTICE "Ran out of memory allocating packet pool\n");
return;
}
pkt->dev = dev;
pkt->next = priv->ppool;
priv->ppool = pkt;
}
}
/*
* 释放snull_packet组成的链表
*/
void snull_teardown_pool(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
struct snull_packet *pkt;
while ((pkt = priv->ppool)) {
priv->ppool = pkt->next;
kfree (pkt);
}
}
/*
* 从链表中取出一个snull packet用于发送
*/
struct snull_packet *snull_get_tx_buffer(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
unsigned long flags;
struct snull_packet *pkt;
spin_lock_irqsave(&priv->lock, flags);
pkt = priv->ppool;
priv->ppool = pkt->next;
if (priv->ppool == NULL) {
printk (KERN_INFO "Pool empty\n");
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&priv->lock, flags);
return pkt;
}
/*
* 将一个snull packet放回到链表
*/
void snull_release_buffer(struct snull_packet *pkt)
{
unsigned long flags;
struct snull_priv *priv = netdev_priv(pkt->dev);
spin_lock_irqsave(&priv->lock, flags);
pkt->next = priv->ppool;
priv->ppool = pkt;
spin_unlock_irqrestore(&priv->lock, flags);
if (netif_queue_stopped(pkt->dev) && pkt->next == NULL)
netif_wake_queue(pkt->dev);
}
1.2.2 rx_queue是设备的接收队列,操作函数如下:
void snull_enqueue_buf(struct net_device *dev, struct snull_packet *pkt)
{
unsigned long flags;
struct snull_priv *priv = netdev_priv(dev);
spin_lock_irqsave(&priv->lock, flags);
pkt->next = priv->rx_queue; /* FIXME - misorders packets */
priv->rx_queue = pkt;
spin_unlock_irqrestore(&priv->lock, flags);
}
struct snull_packet *snull_dequeue_buf(struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
struct snull_packet *pkt;
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
pkt = priv->rx_queue;
if (pkt != NULL)
priv->rx_queue = pkt->next;
spin_unlock_irqrestore(&priv->lock, flags);
return pkt;
}
1.2.3 rx_int_enabled用于使能或禁止接收中断(该中断并未真正的硬件中断,而是由软件模拟的),操作函数如下:
/*
* Enable and disable receive interrupts.
*/
static void snull_rx_ints(struct net_device *dev, int enable)
{
struct snull_priv *priv = netdev_priv(dev);
priv->rx_int_enabled = enable;
}
1.2.4 struct skb_buff套接字缓冲区,这个结构体很关键,用于在网络协议不同分层之间传递数据,后面会对skb_buff结构进一步说明;
1.3 alloc_netdevice()的setup参数指向一个函数,用于初始化net_device的其他成员,程序里用snull_init函数来实现:
static int timeout = SNULL_TIMEOUT; //SNULL_TIMEOUT在snull.h中定义为5
module_param(timeout,int,0);
static const struct net_device_ops snull_dev_ops = {
.ndo_open = snull_open,
.ndo_stop = snull_release,
.ndo_start_xmit = snull_tx,
.ndo_do_ioctl = snull_ioctl,
.ndo_get_stats = snull_stats,
.ndo_tx_timeout = snull_tx_timeout,
};
static const struct header_ops snull_header_ops= {
.create = snull_header,
.rebuild = snull_rebuild_header,
.cache = NULL,
};
void snull_init(struct net_device *dev)
{
struct snull_priv *priv = NULL;
ether_setup(dev);
dev->netdev_ops = &snull_dev_ops;
dev->header_ops = &snull_header_ops;
dev->watchdog_timeo = timeout;
dev->flags |= IFF_NOARP; //禁止ARP
dev->features |= NETIF_F_NO_CSUM;
priv = netdev_priv(dev);
memset(priv, 0, sizeof(struct snull_priv));
spin_lock_init(&((struct snull_priv *)priv)->lock);
snull_rx_ints(dev, 1); /* enable receive interrupts */
snull_setup_pool(dev);
return;
}
snull_init()先初始化net_device结构中的成员:调用ether_setup()函数为net_device中许多成员赋予默认值; 接下来设置设备方法,由于内核版本不同,书中的设备方法直接包含在net_device结构中,这里需要修改,从代码可以看到open,release等操作是封装在在netdev_ops,header_ops结构中,后续将进一步说明打开,发送,接收等设备方法的具体实现。watchdog_timeo成员设置了传输的超时周期,接下来是一些标志,表示禁止ARP并部队数据包校验。
设置net_device成员完成后,使用netdev_priv()获取设备的私有数据并设置了自旋锁,接收使能和数据缓冲区。netdev_priv()专用于访问网络设备的私有数据:
void *netdev_priv(struct net_device *dev);
1.4 在分配并初始化net_device结构后,我们还需要用register_netdev注册这个设备,注册注销网络设备的函数原型:
int register_netdev(struct net_device *dev);
void unregister_netdev(struct net_device *dev);
1.5 出口函数:
static __exit void snull_cleanup(void)
{
int i;
for (i=0; i<2;i++)
{
if(snull_devs[i])
{
unregister_netdev(snull_devs[i]);
snull_teardown_pool(snull_devs[i]);
free_netdev(snull_devs[i]);
}
}
return;
}
module_exit(snull_cleanup);
出口函数操作与入口相反,主要是注销设备,释放数据缓冲区并释放设备所占用的内存。
本文基于《Linux设备驱动程序》第三版的第十七章,介绍snull网络接口,探讨其核心功能。文章详细阐述了入口和出口函数,如alloc_netdev()和释放net_device结构体,以及如何在2.6.31内核上测试。内容涵盖net_device结构、snull_packet数据结构、接收队列管理、中断使能控制和skb_buff套接字缓冲区的使用。

400

被折叠的 条评论
为什么被折叠?



