Linux网络驱动示例之snull网络接口(一):总体框架

本文基于《Linux设备驱动程序》第三版的第十七章,介绍snull网络接口,探讨其核心功能。文章详细阐述了入口和出口函数,如alloc_netdev()和释放net_device结构体,以及如何在2.6.31内核上测试。内容涵盖net_device结构、snull_packet数据结构、接收队列管理、中断使能控制和skb_buff套接字缓冲区的使用。

    《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);

出口函数操作与入口相反,主要是注销设备,释放数据缓冲区并释放设备所占用的内存。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值