您的位置:首页 > 理论基础 > 计算机网络

Linux 网络驱动详解

2012-05-31 21:07 239 查看
原文地址:http://blog.csdn.net/chenjin_zhong/article/details/6324334

1.概述

Linux系统多用于服务器上,Linux非常牢固的支持网络。在Linux,网络分为两个层,分别是网络堆栈协议支持层,以及接收和发送网络协议的设备驱动程序层。网络堆栈是硬件中独立出来的部分,主要用来支持TCP/IP等多种协议,而网络设备驱动层是连接网络堆栈协议层和网络硬件的中间层。

网络设备驱动程序的主要功能是:

(1)模块加载或内核启动相关的初始化处理

(2)清除模块时的处理

(3)网络设备的检索和探测

(4)网络设备的初始化和注册

(5)打开或关闭网络设备

(6)发送网络数据

(7)接收网络数据

(8)中断处理(在发送完数据时,硬件向内核产生一个中断,告诉内核数据已经发送完毕,在网络设备接收到数据时,也要发生一个中断,告诉内核,数据已经到达,请及时处理)

(9)超时处理

(10)多播处理

(11)网络设备的控制ioctl

而Linux网络设备驱动的主要功能就是网络设备的初始化,网络设备的配置,数据包的收发。

 

2. Linux网络设备驱动的接口函数

 

net_device结构体存储一个网络接口的重要信息,它是系统中网络设备的代表。

sk_buff是socket buffer,在网络传输过程中起着重要的作用,内核把数据包封装成socket buffer向网络硬件发送,当网络硬件接收到数据包时,再把数据包封装成socket buffer向内核传送。

 

注册网络设备:

int register_netdev(struct net_device *dev);//网络设备与字符设备,块设备不同,没有主,次设备号

注销网络设备:

void unregister_netdev(struct net_device *dev);

 

返回网络设备结构体的private data:

void *netdev_priv(struct net_device *dev);

即返回我们定义的设备结构体。

 

保存设备统计信息的结构体

struct net_device_stats

 

打开发送队列,能够发送数据包,在open()中调用

netif_start_queue(struct net_device *dev);

 

关闭发送队列,在stop()中调用

netif_stop_queue(struct net_device* dev);

 

重新打开队列,一般在关闭队列之后重启队列

netif_wake_queue(struct net_device *dev);

 

当数据到达时,通知内核数据包到达

void netif_rx(struct sk_buff *skb);

 

分配一个sk_buff结构体

struct sk_buff *dev_alloc_skb(unsigned int len);

 

释放sk_buff结构体

void dev_kfree_skb(struct sk_buff *skb);

 

从数据的尾部扩展len长度的空间,为了把数据放到skb的尾部

unsigned char* skb_put(struct sk_buff *skb,int len);

 

 

SIOCDEVPRIVATE 可用ioctl执行的16个命令的第一个命令

最后一个是SIOCDEVPRIVATE+15

 

 

 

3.下面给出一个虚拟硬件的网络驱动的例子

 

#undef PDEBUG             /* undef it, just in case */

#ifdef SNULL_DEBUG

#  ifdef __KERNEL__

     /* This one if debugging is on, and kernel space */

#    define PDEBUG(fmt, args...) printk( KERN_DEBUG "snull: " fmt, ## args)

#  else

     /* This one for user space */

#    define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)

#  endif

#else

#  define PDEBUG(fmt, args...) /* not debugging: nothing */

#endif

#undef PDEBUGG

#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */

/* These are the flags in the statusword */

#define SNULL_RX_INTR 0x0001

#define SNULL_TX_INTR 0x0002

/* Default timeout period */

#define SNULL_TIMEOUT 6   /* In jiffies */

#include <linux/module.h>

#include <linux/sched.h>

#include <linux/kernel.h> /* printk() */

#include <linux/slab.h> /* kmalloc() */

#include <linux/errno.h>  /* error codes */

#include <linux/types.h>  /* size_t */

#include <linux/interrupt.h> /* mark_bh */

#include <linux/in.h>

#include <linux/netdevice.h>   /* struct device, and other headers */

#include <linux/etherdevice.h> /* eth_type_trans */

#include <linux/ip.h>          /* struct iphdr */

#include <linux/tcp.h>         /* struct tcphdr */

#include <linux/skbuff.h>

#include <linux/if_ether.h>

#include <linux/in6.h>

#include <asm/uaccess.h>

#include <asm/checksum.h>

static int lockup = 0;

static int timeout = SNULL_TIMEOUT;

struct net_device snull_devs[2];//这里定义两个设备,一个是snull0,一个是snull1

//网络设备结构体,作为net_device->priv

struct snull_priv {

    struct net_device_stats stats;//有用的统计信息

    int status;//网络设备的状态信息,是发完数据包,还是接收到网络数据包

    int rx_packetlen;//接收到的数据包长度

    u8 *rx_packetdata;//接收到的数据

    int tx_packetlen;//发送的数据包长度

    u8 *tx_packetdata;//发送的数据

    struct sk_buff *skb;//socket buffer结构体,网络各层之间传送数据都是通过这个结构体来实现的

    spinlock_t lock;//自旋锁

};

void snull_tx_timeout (struct net_device *dev);

//网络接口的打开函数

int snull_open(struct net_device *dev)

 {

    printk("call snull_open/n");

    memcpy(dev->dev_addr, "/0SNUL0", ETH_ALEN);//分配一个硬件地址,ETH_ALEN是网络设备硬件地址的长度

  

    netif_start_queue(dev);//打开传输队列,这样才能进行数据传输

    return 0;

}

int snull_release(struct net_device *dev)

{

    printk("call snull_release/n");

    netif_stop_queue(dev); //当网络接口关闭的时候,调用stop方法,这个函数表示不能再发送数据

    return 0;

}

//接包函数

void snull_rx(struct net_device *dev, int len, unsigned char *buf)

{

  

    struct sk_buff *skb;

    struct snull_priv *priv = (struct snull_priv *) dev->priv;

 

    /*

     * The packet has been retrieved from the transmission

     * medium. Build an skb around it, so upper layers can handle it

     */

    skb = dev_alloc_skb(len+2);//分配一个socket buffer,并且初始化skb->data,skb->tail和skb->head

    if (!skb) {

        printk("snull rx: low on mem - packet dropped/n");

        priv->stats.rx_dropped++;

        return;

    }

    skb_reserve(skb, 2); /* align IP on 16B boundary */

    memcpy(skb_put(skb, len), buf, len);//skb_put是把数据写入到socket buffer

    /* Write metadata, and then pass to the receive level */

    skb->dev = dev;

    skb->protocol = eth_type_trans(skb, dev);//返回的是协议号

    skb->ip_summed = CHECKSUM_UNNECESSARY; //此处不校验

    priv->stats.rx_packets++;//接收到包的个数+1

   

    priv->stats.rx_bytes += len;//接收到包的长度

    netif_rx(skb);//通知内核已经接收到包,并且封装成socket buffer传到上层

    return;

}

  

      

/*

 * The typical interrupt entry point

 */

//中断处理,此程序中没有硬件,因此,没有真正的硬件中断,只是模拟中断,在发送完网络数据包之后,会产生中断

//用来通知内核已经发送完数据包,当新的数据包到达网络接口时,会发生中断,通知新的数据包已经到来了

void snull_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{

 

    int statusword;//用来标识是发送完毕还是接收到新的数据包

    struct snull_priv *priv;

    /*

     * As usual, check the "device" pointer for shared handlers.

     * Then assign "struct device *dev"

     */

    struct net_device *dev = (struct net_device *)dev_id;

    /* ... and check with hw if it's really ours */

    if (!dev /*paranoid*/ ) return;

    /* Lock the device */

    priv = (struct snull_priv *) dev->priv;

    spin_lock(&priv->lock);

    /* retrieve statusword: real netdevices use I/O instructions */

    statusword = priv->status;

    if (statusword & SNULL_RX_INTR) {//如果是接收

        /* send it to snull_rx for handling */

        snull_rx(dev, priv->rx_packetlen, priv->rx_packetdata);

    }

    if (statusword & SNULL_TX_INTR) {//如果发送完毕

        /* a transmission is over: free the skb */

        priv->stats.tx_packets++;

        priv->stats.tx_bytes += priv->tx_packetlen;

        dev_kfree_skb(priv->skb);//释放skb 套接字缓冲区

    }

    /* Unlock the device and we are done */

    spin_unlock(&priv->lock);

    return;

}

/*

 * Transmit a packet (low level interface)

 */

//真正的处理的发送数据包

//模拟从一个网络向另一个网络发送数据包

void snull_hw_tx(char *buf, int len, struct net_device *dev)

{

 

 

   /*

     * This function deals with hw details. This interface loops

     * back the packet to the other snull interface (if any).

     * In other words, this function implements the snull behaviour,

     * while all other procedures are rather device-independent

     */

    struct iphdr *ih;//ip头部

    struct net_device *dest;//目标设备结构体,net_device存储一个网络接口的重要信息,是网络驱动程序的核心

    struct snull_priv *priv;

    u32 *saddr, *daddr;//源设备地址与目标设备地址

    /* I am paranoid. Ain't I? */

    if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {

        printk("snull: Hmm... packet too short (%i octets)/n",

               len);

        return;

    }

    /*

     * Ethhdr is 14 bytes, but the kernel arranges for iphdr

     * to be aligned (i.e., ethhdr is unaligned)

     */

    ih = (struct iphdr *)(buf+sizeof(struct ethhdr));

    saddr = &ih->saddr;

    daddr = &ih->daddr;

//在同一台机器上模拟两个网络,不同的网段地址,进行发送网络数据包与接收网络数据包

    ((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) ^是位异或操作符把第三个部分的网络地址与1进行异或,由于同一网络的数据不进行转发*/

    ((u8 *)daddr)[2] ^= 1;

    ih->check = 0;         /* and rebuild the checksum (ip needs it) */

    ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);

    if (dev == snull_devs)

        PDEBUGG("%08x:%05i --> %08x:%05i/n",

               ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source),

               ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest));

    else

        PDEBUGG("%08x:%05i <-- %08x:%05i/n",

               ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest),

               ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source));

    /*

     * Ok, now the packet is ready for transmission: first simulate a

     * receive interrupt on the twin device, then  a

     * transmission-done on the transmitting device

     */

    dest = snull_devs + (dev==snull_devs ? 1 : 0);//如果dev是0,那么dest就是1,如果dev是1,那么dest是0

    priv = (struct snull_priv *) dest->priv;//目标dest中的priv

    priv->status = SNULL_RX_INTR;

    priv->rx_packetlen = len;

    priv->rx_packetdata = buf;

    snull_interrupt(0, dest, NULL);

    priv = (struct snull_priv *) dev->priv;

    priv->status = SNULL_TX_INTR;

    priv->tx_packetlen = len;

    priv->tx_packetdata = buf;

    if (lockup && ((priv->stats.tx_packets + 1) % lockup) == 0) {

        /* Simulate a dropped transmit interrupt */

        netif_stop_queue(dev);

 

        PDEBUG("Simulate lockup at %ld, txp %ld/n", jiffies,

                        (unsigned long) priv->stats.tx_packets);

    }

    else

        snull_interrupt(0, dev, NULL);

}

 

/*

 * Transmit a packet (called by the kernel)

 */

//发包函数

int snull_tx(struct sk_buff *skb, struct net_device *dev)

{

 

    int len;

    char *data;

    struct snull_priv *priv = (struct snull_priv *) dev->priv;

    if ( skb == NULL) {

        PDEBUG("tint for %p,  skb %p/n", dev,  skb);

        snull_tx_timeout (dev);

        if (skb == NULL)

            return 0;

    }

    len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len;//ETH_ZLEN是所发的最小数据包的长度

    data = skb->data;//将要发送的数据包中数据部分

    dev->trans_start = jiffies; //保存当前的发送时间

    priv->skb = skb;

    snull_hw_tx(data, len, dev);//真正的发送函数

   return 0; /* Our simple device can not fail */

}

/*

 * Deal with a transmit timeout.

 */

//一旦超出watchdog_timeo就会调用snull_tx_timeout

void snull_tx_timeout (struct net_device *dev)

{

   printk("call snull_tx_timeout/n");

    struct snull_priv *priv = (struct snull_priv *) dev->priv;

    PDEBUG("Transmit timeout at %ld, latency %ld/n", jiffies,

                    jiffies - dev->trans_start);

    priv->status = SNULL_TX_INTR;

    snull_interrupt(0, dev, NULL);//超时后发生中断

    priv->stats.tx_errors++;//发送的错误数

    netif_wake_queue(dev); //为了再次发送数据,调用此函数,重新启动发送队列

    return;

}

 

/*

 * Ioctl commands

 */

int snull_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)

{

 

    PDEBUG("ioctl/n");

    return 0;

}

/*

 * Return statistics to the caller

 */

struct net_device_stats *snull_stats(struct net_device *dev)

{

    struct snull_priv *priv = (struct snull_priv *) dev->priv;

    return &priv->stats;//得到统计资料信息

}

//设备初始化函数

int snull_init(struct net_device *dev)

{

   printk("call snull_init/n");

    /*

     * Then, assign other fields in dev, using ether_setup() and some

     * hand assignments

     */

    ether_setup(dev);//填充一些以太网中的设备结构体的项

    dev->open            = snull_open;

    dev->stop            = snull_release;

    //dev->set_config      = snull_config;

    dev->hard_start_xmit = snull_tx;

    dev->do_ioctl        = snull_ioctl;

    dev->get_stats       = snull_stats;

    //dev->change_mtu      = snull_change_mtu;

   // dev->rebuild_header  = snull_rebuild_header;

    //dev->hard_header     = snull_header;

    dev->tx_timeout     = snull_tx_timeout;//超时处理

    dev->watchdog_timeo = timeout;

    /* keep the default flags, just add NOARP */

    dev->flags           |= IFF_NOARP;

    dev->hard_header_cache = NULL;      /* Disable caching */

    SET_MODULE_OWNER(dev);

    /*

     * Then, allocate the priv field. This encloses the statistics

     * and a few private fields.

     */

//为priv分配内存

    dev->priv = kmalloc(sizeof(struct snull_priv), GFP_KERNEL);

  if (dev->priv == NULL)

       return -ENOMEM;

    memset(dev->priv, 0, sizeof(struct snull_priv));

    spin_lock_init(& ((struct snull_priv *) dev->priv)->lock);

    return 0;

}

struct net_device snull_devs[2] = {

    { init: snull_init, },  /* init, nothing more */

    { init: snull_init, }

};

int snull_init_module(void)

{

   int i,result=0;

   strcpy(snull_devs[0].name,"snull0");//net_device结构体中的name表示设备名

   strcpy(snull_devs[1].name,"snull1");//即定义了两个设备,snull0与snull1

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

     return 0;

}

void snull_cleanup(void)

{

    int i;

 

    for (i=0; i<2;  i++) {

        kfree(snull_devs[i].priv);

        unregister_netdev(snull_devs+i);

    }

    return;

}

module_init(snull_init_module);

module_exit(snull_cleanup);

 

 

分析:

这个例子中包括了以下部分:

(1)网络设备初始化 snull_init

(2)发送数据包函数snull_tx,而真正的发送数据包函数是snull_hw_tx,在snull_hw_tx,目标设备dest收到数据包产生中断,然后再向源设备发送数据包,发送完之后也产生中断

(3)接收数据包的函数snull_rx

(4)中断处理snull_interrupt

(5)网络超时处理snull_timeout

(6)网络设备的打开snull_open

 

 

测试:

(1)生成snull.ko, insmod snull.ko

(2)为两个网络设备分配IP:

ifconfig snull0 192.168.0.1

ifconfig snull1 192.168.1.2

可以看出,两个网络设备在不同的网段

 

ping 192.168.0.2 由于目标daddr经过((u8 *)daddr)[2] ^= 1,变成 192.168.1.2,相当于ping 192.168.1.2.

而源IP 192.168.0.1 经过((u8 *)saddr)[2] ^= 1,变成192.168.1.1,那么dest设备发送的数据包地址是192.168.1.1,相当于发送给192.168.0.1

如果不经过这样处理,直接ping 192.168.1.2 是不能ping 通的,由于不在同一个网段上。

可以测试一下car /var/log/messages.

 

 

关于Linux网络驱动就介绍到这里了。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息