网络驱动开发_linux网络驱动开发 - CSDN
  • 网卡驱动程序框架和编写

    千次阅读 2018-07-28 16:02:41
    网卡驱动程序框架 1.网卡驱动程序“收发功能” 2.编程步骤:  2.1设置net_device结构  2.2硬件相关设置  2.3接收到数据要做的事情  2.4发包函数 网卡驱动程序“收发功能”  只要把上层的数据发给网卡,...

    参考韦东山二期视频资料

    网卡驱动程序框架

    1.网卡驱动程序“收发功能”

    2.编程步骤:

        2.1设置net_device结构

        2.2硬件相关设置

        2.3接收到数据要做的事情

        2.4发包函数

    网卡驱动程序“收发功能”

       只要把上层的数据发给网卡,从网卡来的数据构造成包给上层即可。网卡只需要“socket”编程,不需要打开某设备。

       驱动程序都是以面向对象的思想写的,都有相关的结构体。

    编程步骤

    1.分配某结构体:net_device

    2.设置结构体。

        1,提供一个发包函数:hard_start_xmit()

        2,提供手包的功能:net_interrupt(int irq,void *dev_id)-->netif_rx(skb);

        3,注册结构体:register_netdev(dev)真实驱动中使用的是此注册函数。

        4,硬件相关操作

    看内核中的cs89x0.c这个真实的网卡驱动程序

    int __init init_module(void)
    {
    	struct net_device *dev = alloc_etherdev(sizeof(struct net_local));//分配一个net_device结构体
    	struct net_local *lp;
    	int ret = 0;
    struct net_device *alloc_etherdev(int sizeof_priv)
    {
    	return alloc_netdev(sizeof_priv, "eth%d", ether_setup);//分配时用了eth%d这样的名字
    }
    	return alloc_netdev(sizeof_priv, "eth%d", ether_setup);//分配时用了eth%d这样的名字
    }
    

    设置net_device结构体

        /* 设置默认MAC地址,
         * MAC地址可以由CS8900A外接的EEPROM设定(有些单板没接EEPROM),
         * 或者启动系统后使用ifconfig修改
         */

        dev->dev_addr[0] = 0x08;
        dev->dev_addr[1] = 0x89;
        dev->dev_addr[2] = 0x89;
        dev->dev_addr[3] = 0x89;
        dev->dev_addr[4] = 0x89;
        dev->dev_addr[5] = 0x89;

    ret = cs89x0_probe1(dev, io, 1);//进入probe函数,进行硬件相关的操作

    if (ret)

    goto out;

            dev->open		= net_open;
    	dev->stop		= net_close;
    	dev->tx_timeout		= net_timeout;
    	dev->watchdog_timeo	= HZ;
    	dev->hard_start_xmit 	= net_send_packet;//硬件启动传输,这是发包函数
    	dev->get_stats		= net_get_stats;
    	dev->set_multicast_list = set_multicast_list;
    	dev->set_mac_address 	= set_mac_address;

    retval = register_netdev(dev);

    static irqreturn_t net_interrupt(int irq, void *dev_id)中有个net_rx(dev);

    net_rx(struct net_device *dev)
    {
    struct net_local *lp = netdev_priv(dev);
    struct sk_buff *skb;
    int status, length;


    int ioaddr = dev->base_addr;
    status = readword(ioaddr, RX_FRAME_PORT);
    length = readword(ioaddr, RX_FRAME_PORT);



    if ((status & RX_OK) == 0) {
    count_rx_errors(status, lp);
    return;

    }

    skb = dev_alloc_skb(length + 2);//分配一个skb缓冲

    netif_rx(skb);

     

    #include <linux/module.h>
    #include <linux/errno.h>
    #include <linux/netdevice.h>
    #include <linux/etherdevice.h>
    #include <linux/kernel.h>
    #include <linux/types.h>
    #include <linux/fcntl.h>
    #include <linux/interrupt.h>
    #include <linux/ioport.h>
    #include <linux/in.h>
    #include <linux/skbuff.h>
    #include <linux/slab.h>
    #include <linux/spinlock.h>
    #include <linux/string.h>
    #include <linux/init.h>
    #include <linux/bitops.h>
    #include <linux/delay.h>
    #include <linux/ip.h>
    
    #include <asm/system.h>
    #include <asm/io.h>
    #include <asm/irq.h>
    
    static struct net_device *vnet_dev;
    
    static void emulator_rx_packet(struct sk_buff *skb, struct net_device *dev)
    {
    	/* 参考LDD3 */
    	unsigned char *type;
    	struct iphdr *ih;
    	__be32 *saddr, *daddr, tmp;
    	unsigned char	tmp_dev_addr[ETH_ALEN];
    	struct ethhdr *ethhdr;
    	
    	struct sk_buff *rx_skb;
    		
    	// 从硬件读出/保存数据
    	/* 对调"源/目的"的mac地址 */
    	ethhdr = (struct ethhdr *)skb->data;
    	memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
    	memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
    	memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);
    
    	/* 对调"源/目的"的ip地址 */    
    	ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
    	saddr = &ih->saddr;
    	daddr = &ih->daddr;
    
    	tmp = *saddr;
    	*saddr = *daddr;
    	*daddr = tmp;
    	
    	//((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
    	//((u8 *)daddr)[2] ^= 1;
    	type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
    	//printk("tx package type = %02x\n", *type);
    	// 修改类型, 原来0x8表示ping
    	*type = 0; /* 0表示reply */
    	
    	ih->check = 0;		   /* and rebuild the checksum (ip needs it) */
    	ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
    	
    	// 构造一个sk_buff
    	rx_skb = dev_alloc_skb(skb->len + 2);
    	skb_reserve(rx_skb, 2); /* align IP on 16B boundary */	
    	memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
    
    	/* Write metadata, and then pass to the receive level */
    	rx_skb->dev = dev;
    	rx_skb->protocol = eth_type_trans(rx_skb, dev);
    	rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
    	dev->stats.rx_packets++;
    	dev->stats.rx_bytes += skb->len;
    
    	// 提交sk_buff
    	netif_rx(rx_skb);
    }
    
    static int virt_net_send_packet(struct sk_buff *skb, struct net_device *dev)
    {
    	static int cnt = 0;
    	printk("virt_net_send_packet cnt = %d\n", ++cnt);
    
    	/* 对于真实的网卡, 把skb里的数据通过网卡发送出去 */
    	netif_stop_queue(dev); /* 停止该网卡的队列 */
        /* ...... */           /* 把skb的数据写入网卡 */
    
    	/* 构造一个假的sk_buff,上报 */
    	emulator_rx_packet(skb, dev);
    
    	dev_kfree_skb (skb);   /* 释放skb */
    	netif_wake_queue(dev); /* 数据全部发送出去后,唤醒网卡的队列 */
    
    	/* 更新统计信息 */
    	dev->stats.tx_packets++;
    	dev->stats.tx_bytes += skb->len;
    	
    	return 0;
    }
    
    
    static int virt_net_init(void)
    {
    	/* 1. 分配一个net_device结构体 */
    	vnet_dev = alloc_netdev(0, "vnet%d", ether_setup);;  /* alloc_etherdev */
    
    	/* 2. 设置 */
    	vnet_dev->hard_start_xmit = virt_net_send_packet;
    
    	/* 设置MAC地址 */
        vnet_dev->dev_addr[0] = 0x08;
        vnet_dev->dev_addr[1] = 0x89;
        vnet_dev->dev_addr[2] = 0x89;
        vnet_dev->dev_addr[3] = 0x89;
        vnet_dev->dev_addr[4] = 0x89;
        vnet_dev->dev_addr[5] = 0x11;
    
        /* 设置下面两项才能ping通 */
    	vnet_dev->flags           |= IFF_NOARP;
    	vnet_dev->features        |= NETIF_F_NO_CSUM;	
    
    	/* 3. 注册 */
    	//register_netdevice(vnet_dev);
    	register_netdev(vnet_dev);
    	
    	return 0;
    }
    
    static void virt_net_exit(void)
    {
    	unregister_netdev(vnet_dev);
    	free_netdev(vnet_dev);
    }
    
    module_init(virt_net_init);
    module_exit(virt_net_exit);
    
    MODULE_AUTHOR("thisway.diy@163.com,17653039@qq.com");
    MODULE_LICENSE("GPL");

    ifconfig

    ifconfig vnet0 3.3.3.3

    ping 3.3.3.4

    展开全文
  • 0 引言 随着人们对开放源代码软件热情的日益增高,Linux作为一个功能强大而稳定的开源操作系统,越来越受到成千上万的计算机专家和爱好者的青睐。在嵌入式领域,通过对Linux进行小型化裁剪后,使其能够固化...

    转载 http://blog.chinaunix.net/uid-25984886-id-3077158.html


    0 引言
    随着人们对开放源代码软件热情的日益增高,Linux作为一个功能强大而稳定的开源操作系统,越来越受到成千上万的计算机专家和爱好者的青睐。在嵌入式领域,通过对Linux进行小型化裁剪后,使其能够固化在容量只有几十兆字节的存储器芯片或单片机中,成为应用于特定场合的嵌入式Linux系统。Linux强大的网络支持功能实现了对包括TCP/IP在内的多种协议的支持,满足了面向21世纪的嵌入式系统应用联网的需求。因此,在嵌入式系统开发调试时,网络接口几乎成为不可或缺的模块。
    1 嵌入式Linux网络驱动程序介绍
    Linux网络驱动程序作为Linux网络子系统的一部分,位于TCP/IP网络体系结构的网络接口层,主要实现上层协议栈与网络设备的数据交换。Linux的网络系统主要是基于BSD Unix的套接字(socket)机制,网络设备与字符设备和块设备不同,没有对应地映射到文件系统中的设备节点。
    通常,Linux驱动程序有两种加载方式:一种是静态地编译进内核,内核启动时自动加载;另一种是编写为内核模块,使用insmod命令将模块动态加载到正在运行的内核,不需要时可用rmmod命令将模块卸载。Linux 2.6内核引入了kbuild机制,将外部内核模块的编译同内核源码树的编译统一起来,大大简化了特定的参数和宏的设置。这样将编写好的驱动模块加入内核源码树,只需要修改相应目录的Kconfig文件,把新的驱动加入内核的配置菜单,然后需要修改相应子目录中与模块编译相关的Kbuild Makefile,即可使新的驱动在内核源码树中被编译。在嵌入式系统驱动开发时,常常将驱动程序编写为内核模块,方便开发调试。调试完毕后,就可以将驱动模块编译进内核,并重新编译出支持特定物理设备的Linux内核。
    2 嵌入式Linux网络驱动程序的体系结构和实现原理
    2.1 Linux网络设备驱动的体系结构
    如图1所示,Linux网络驱动程序的体系结构可划分为4个层次。Linux内核源代码中提供了网络设备接口及以上层次的代码,因此移植特定网络硬件的驱动程序的主要工作就是完成设备驱动功能层的相应代码,根据底层具体的硬件特性,定义网络设备接口struct net_device类型的结构体变量,并实现其中相应的操作函数及中断处理程序。
    Linux中所有的网络设备都抽象为一个统一的接口,即网络设备接口,通过struct net_device类型的结构体变量表示网络设备在内核中的运行情况,这里既包括回环(loopback)设备,也包括硬件网络设备接口。内核通过以dev_base为头指针的设备链表来管理所有的网络设备。
     


    2.2 net_device 数据结构
     struct net_device结构体是整个网络驱动结构的核心,其中定义了很多供网络协议接口层调用设备的标准方法,该结构在2.6内核源码树文件中定义,下面只列出其中主要的成员。
    2.2.1全局信息及底层硬件信息
    name:网络设备名称,默认是以太网;
    *next:指向全局链表下一个设备的指针,驱动程序中不修改;
    mem_,rmem_:发送和接收缓冲区的起始,结束位置;
    base_addr,irq:网络设备的I/O基地址,中断号,ifconfig命令可显示和修改;
    hard_header_len:硬件头的长度,以太网中值为14;
    mtu:最大传输单元,以太网中值为1500B;
    dev_addr[MAX_ADDR_LEN]:硬件(MAC)地址长度及设备硬件地址,以太网地址长度是48bit,ether_setup会对其进行正确的设置;

    2.2.2 主要的操作方法
    int (*init)(struct net_device *dev); 设备初始化和向系统注册的函数,仅调用一次;
    int (*open)(struct net_device *dev);设备打开接口函数,当用ifconfig激活网络设备时被调用,注册所用的系统资源(I/O端口,IRQ,DMA等)同时激活硬件并增加使用计数;
    int (*stop)(struct net_device *dev);执行open方法的反操作;
    *hard_start_xmit;初始化数据包传输的函数;
    *hard_header;该函数(在hard_start_xmit前被调用)根据先前检索到的源和目标硬件地址建立硬件头。    eth_header是以太网类型接口的默认函数;
    2.3网络驱动程序的编写及实现原理
    Linux网络系统各个层次之间的数据传送都是通过套接字缓冲区sk_buff完成的,sk_buff数据结构是各层协议数据处理的对象。sk_buff是驱动程序与网络之间交换数据的媒介,驱动程序向网络发送数据时,必须从其中获取数据源和数据长度;驱动程序从网络上接收到数据后也要将数据保存到sk_buff中才能交给上层协议处理。
    对于实际开发以太网驱动程序,可以参照内核源码树中的相应模板程序,重点理解网络驱动的实现原理和程序的结构框架,然后针对开发的特定硬件改写代码,实现相应的操作函数。下面结合作者利用Linux2.6.18内核在深圳优龙公司的FS2410开发板(SAMSUNG S3C2410处理器)上移植编写嵌入式CS8900A网卡驱动程序的实例,说明网络驱动程序的实现原理。
    2.3.1网络设备初始化
    网络设备的初始化是由net_device结构中的init函数实现的,内核加载网络驱动模块后,就会调用初始化过程。实例中初始化函数_init cs8900_probe中主要完成的工作:
    a.调用内核中通用的设置以太网接口的函数ether_setup();
    b.填充net_device结构体变量dev中其它大部分成员;
    c.调用check_mem_region()检测I/O地址空间,然后调用request_mem_region()申请以dev->base_addr为起始地址的16个连续的 I/O地址空间;
    d.通过cs8900_read()探测网卡CS8900A,读取ID信息;
    e.设置CS8900A的INTRQ0作为中断信号输出引脚;
    f.将MAC地址写入CS8900A的IA寄存器中;
    g.通过register_netdev()将CS8900A注册到Linux全局网络设备链表中;
    2.3.2打开(或关闭)网络设备
    系统响应ifconfig命令时,打开(关闭)一个网络接口。ifconfig命令开始会调用ioctl(SIOCSIFADDR)来将地址赋予接口。响应SIOCSIFADDR由内核来完成,与设备无关。接着,ifconfig命令会调用ioctl(SIOCSIFFLAGS)设置dev->flag的IFF_UP位来打开设备,这个调用会使设备的open方法得到调用。(当ifconfig调用ioctl(SIOCSIFFLAGS)清除dev->flag的IFF_UP位时,设备的stop方法将被调用)
    实例中利用cs8900_start()函数打开网络设备,主要完成的工作:
    a.通过set_irq_type()向内核注册网络设备的中断处理程序;
    b.通过cs8900_set()设置CS8900A网卡中各控制寄存器和配置寄存器;
    c.通过内核中netif_start_queue()函数开启网络接口的数据传输队列;
    2.3.3网络数据包的发送
    数据包的发送和接收是网络驱动程序中实现的两个最重要的任务。当网络设备被激活时,net_device结构中的open方法被调用,它负责打开设备并调用net_device结构中的hard_header函数指针建立硬件帧头信息。最后通过函数dev_queue_xmit()来调用net_device结构中的hard_start_xmit方法把存放在sk_buff中的数据发送到网络物理设备。如果发送成功,则在hard_start_xmit中释放sk_buff并返回0;如果硬件设备忙暂时无法处理,则返回1。网络硬件在发送完数据包后会产生中断,把dev->tbusy置0,通知系统可以再次发送。
    实例中,hard_start_xmit方法即为网络设备数据发送函数cs8900_send_start(),该函数实现把数据发送到以太网上,由网络协议接口层函数dev_queue_xmit()对其调用。cs8900_send_start()中主要完成的工作:
    a.发送数据前关闭中断,中止网络设备的数据传输队列;
    b.向CS8900A寄存器TxCMD中写入传送数据命令控制字,向寄存器TxLength中写入待发送数据帧长度;
    c.通过cs8900_read()反复读取CS8900A总线状态寄存器BusST信息,直到其已经准备好接收来自主机的数据;
    d.调用cs8900_frame_write()将待发数据送入CS8900A的sk_buff中,硬件设备会将数据帧发送到以太网上;
    e.记录数据帧的发送时刻,打开中断,释放sk_buff缓存,函数返回0;

    2.3.4网络数据包的接收和中断处理
    网络设备是异步地接收外来的数据包并且主动的“请求”将硬件获得的数据包压入内核。网络设备接收数据包是通过中断实现的。对于网络接口,接收到新数据包,发送完成或者报告错误信息及连接状态等都会触发中断,通常中断处理程序通过检测硬件状态寄存器判断是哪种情况。
    当设备收到数据后会产生一个中断,由硬件通知驱动程序有数据包到达。在中断处理程序中驱动程序申请一块sk_buff(一般定义为skb)缓冲区,然后从硬件读出数据放到申请好的缓冲区里,接下来填充sk_buff中的部分信息:包括接收到数据的设备结构体指针填入skb->dev;收到数据帧的类型填入skb->protocol;把指针skb->mac.raw指向硬件数据并丢弃硬件针头(skb_pull);设置skb->pkt_type,标明链路层数据类型。最后调用协议接口层函数netif_rx() 把接收到的数据包传输到网络上层协议处理。这里,netif_rx()只是负责把数据放入工作队列就返回,真正的处理是在中断返回以后,这样可减少中断处理的时间。几乎每个中断处理程序的编写都要涉及底半部机制,这样可以保证中断的高效处理。
    实例中数据接收函数cs8900_receive()由网络驱动的中断处理函数调用,主要完成如下工作:
    a.通过从I/O口读取RxStatus和RxLength的值,确定接收数据帧的状态信息和长度;
    b.判断接收数据帧的状态是否正常,若异常则记录相关错误信息,然后函数返回;
    c.正常情况下,在内存中申请一块sk_buff缓存,并将数据从CS8900A的片内存储器传送到sk_buff缓存中;d.从数据帧中获取协议头并赋给skb->protocol;
    e.通过调用netif_rx()函数将接收到的数据送往上层协议栈进行处理;
    f.记录接收数据的时间并更新统计信息;
    3将设备驱动模块编译进内核
    设计好模块化的网络驱动程序后,我们就可以编译这个内核模块,并将这个自定义的内核模块作为Linux系统源码的一部分编译出新的系统。下面介绍的内容均在Linux2.6.18内核上编译通过,可以在2.6.x版本内核中通用。如前所述,由于Linux2.6内核引入了kbuild的新机制,使得编译新的内核模块或者将自己编写的内核模块集成到内核源码中都变得非常简单了。
    Linux2.6内核中,编译内核模块首先要在/usr/src下正确配置和构造内核源码树,即把需要版本的内核源码解压在/usr/src/,并在内核源码的主目录下(这里为/usr/src/linux-2.6.18.3),使用make menuconfig或者make gconfig命令配置内核,然后使用make all完整编译内核。
    下面以作者开发的CS8900A网卡驱动为实例,介绍如何将网络设备驱动模块编译进内核。
    a.在系统源码树drivers目录下创建新目录Cs8900;
    b.将编写好的文件cs8900.c和cs8900.h拷贝到drivers/Cs8900目录下;
    c.在drivers/Cs8900目录下,编写Makefile文件:
    #Makefile for CS8900A Network Driver
    obj -$(CONFIG_DRIVER_CS8900A)  +=cs8900.o
    d.在drivers/Cs8900目录下,编写Kconfig文件:
    #Just for CS8900A Network Interface
    menu "CS8900A Network Interface support"
    config DRIVER_CS8900A
    tristate "CS8900A support"
    --------help--------
    This is a network driver module for CS8900A.
    endmenu
    e.在driver目录下的Kconfig文件endmenu语句前,加入一行:
    source "drivers/Cs8900/Kconfig"
    这样在内核源码树的主目录下,通过make menuconfig或者make gconfig命令就可以在Device Drivers选项的下面找到CS8900A Network Interface support选项,并找到CS8900A support的选择菜单,它有三种状态:未选中(不编译)、选中(M)一编译为模块、选中(*)一编译为新系统一部分。
    重新编译内核即可得到支持CS8900A网卡的内核,然后将内核下载到FS2410的开发板上,通过配置网络参数,就可以测试网卡驱动程序的行为了。
    4 结束语
        在这个信息爆炸的时代,人们对于网络的需求愈发强烈,越来越多的嵌入式设备都需要具有以太网的接入功能,因此开发网络驱动程序对于很多嵌入式产品的研发至关重要。具体开发嵌入式Linux网络驱动程序时,可以参照内核中已经支持的网络驱动源代码,在重点理解Linux网络驱动实现原理的基础上,按照模块设计较为固定的开发模式,结合具体物理设备的硬件手册,移植编写需要的模块化的网络驱动程序。



    展开全文
  • Linux网络驱动程序开发实例分析

    千次阅读 2017-05-23 17:25:30
     2.1 网络驱动程序的结构   2.2 网络驱动程序的基本方法   2.3 网络驱动程序中用到的数据结构   2.4 常用的系统支持  三.编写Linux网络驱动程序中可能遇到的问题   3.1 中断共享 
    一.Linux系统设备驱动程序概述 
      1.1 Linux设备驱动程序分类 
      1.2 编写驱动程序的一些基本概念 
    二.Linux系统网络设备驱动程序 
      2.1 网络驱动程序的结构 
      2.2 网络驱动程序的基本方法 
      2.3 网络驱动程序中用到的数据结构 
      2.4 常用的系统支持 
    三.编写Linux网络驱动程序中可能遇到的问题 
      3.1 中断共享 
      3.2 硬件发送忙时的处理 
      3.3 流量控制(flow control) 
      3.4 调试 
    四.进一步的阅读 
    五.杂项 


    一.Linux系统设备驱动程序概述 

    1.1 Linux设备驱动程序分类 
    Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加, 
    主要是驱动程序的增加。在Linux内核的不断升级过程中,驱动程序的结构还是相对稳定 
    。在2.0.xx到2.2.xx的变动里,驱动程序的编写做了一些改变,但是从2.0.xx的驱动到 
    2.2.xx的移植只需做少量的工作。 
    Linux系统的设备分为字符设备(char device),块设备(block device)和网络设备(net 
    work device)三种。字符设备是指存取时没有缓存的设备。块设备的读写都有缓存来支 
    持,并且块设备必须能够随机存取(random access),字符设备则没有这个要求。典型的 
    字符设备包括鼠标,键盘,串行口等。块设备主要包括硬盘软盘设备,CD-ROM等。一个 
    文件系统要安装进入操作系统必须在块设备上。 
    网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket机制 
    。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持 
    对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。 
    1.2 编写驱动程序的一些基本概念 
    无论是什么操作系统的驱动程序,都有一些通用的概念。操作系统提供给驱动程序的支 
    持也大致相同。下面简单介绍一下网络设备驱动程序的一些基本要求。 
    1.2.1 发送和接收 
    这是一个网络设备最基本的功能。一块网卡所做的无非就是收发工作。所以驱动程序里 
    要告诉系统你的发送函数在哪里,系统在有数据要发送时就会调用你的发 送程序。还有 
    驱动程序由于是直接操纵硬件的,所以网络硬件有数据收到最先能得到这个数据的也就 
    是驱动程序,它负责把这些原始数据进行必要的处理然后送给系统。这里,操作系统必 
    须要提供两个机制,一个是找到驱动程序的发送函数,一个是驱动程序把收到的数据送 
    给系统。 
    1.2.2 中断 
    中断在现代计算机结构中有重要的地位。操作系统必须提供驱动程序响应中断的能力。 
    一般是把一个中断处理程序注册到系统中去。操作系统在硬件中断发生后 调用驱动程序 
    的处理程序。Linux支持中断的共享,即多个设备共享一个中断。 
    1.2.3 时钟 
    在实现驱动程序时,很多地方会用到时钟。如某些协议里的超时处理,没有中断机制的 
    硬件的轮询等。操作系统应为驱动程序提供定时机制。一般是在预定的时 间过了以后回 
    调注册的时钟函数。在网络驱动程序中,如果硬件没有中断功能,定时器可以提供轮询 
    (poll)方式对硬件进行存取。或者是实现某些协议时需要的超时重传等。 
    二.Linux系统网络设备驱动程序 
    2.1 网络驱动程序的结构 
    所有的Linux网络驱动程序遵循通用的接口。设计时采用的是面向对象的方法。一个设备 
    就是一个对象(device 结构),它内部有自己的数据和方法。每一个设备的方法被调用时 
    的第一个参数都是这个设备对象本身。这样这个方法就可以存取自身的数据(类似面向对 
    象程序设计时的this引用)。 
    一个网络设备最基本的方法有初始化、发送和接收。 
    ------------------- --------------------- 
    |deliver packets | |receive packets queue| 
    |(dev_queue_xmit()) | |them(netif_rx()) | 
    ------------------- --------------------- 
    | |                       | | 
    ------------------------------------------------------- 
    | methods and variables(initialize,open,close,hard_xmit,| 
    | interrupt handler,config,resources,status...) | 
    ------------------------------------------------------- 
    | |                        | | 
    ----------------- ---------------------- 
    |send to hardware | |receivce from hardware| 
    ----------------- ---------------------- 
    | |                        | | 
    ----------------------------------------------------- 
    | hardware media | 
    ----------------------------------------------------- 
    初始化程序完成硬件的初始化、device中变量的初始化和系统资源的申请。发送程序是 
    在驱动程序的上层协议层有数据要发送时自动调用的。一般驱动程序中不对发送数据进 
    行缓存,而是直接使用硬件的发送功能把数据发送出去。接收数据一般是通过硬件中断 
    来通知的。在中断处理程序里,把硬件帧信息填入一个skbuff结构中,然后调用netif_ 
    rx()传递给上层处理。 
    2.2 网络驱动程序的基本方法 
    网络设备做为一个对象,提供一些方法供系统访问。正是这些有统一接口的方法,掩蔽 
    了硬件的具体细节,让系统对各种网络设备的访问都采用统一的形式,做到硬件无关性 
    。 
    下面解释最基本的方法。 
    2.2.1 初始化(initialize) 
    驱动程序必须有一个初始化方法。在把驱动程序载入系统的时候会调用这个初始化程序 
    。它做以下几方面的工作。检测设备。在初始化程序里你可以根据硬件的特征检查硬件 
    是否存在,然后决定是否启动这个驱动程序。配置和初始化硬件。在初始化程序里你可 
    以完成对硬件资源的配置,比如即插即用的硬件就可以在这个时候进行配置(Linux内核 
    对PnP功能没有很好的支持,可以在驱动程序里完成这个功能)。配置或协商好硬件占用 
    的资源以后,就可以向系统申请这些资源。有些资源是可以和别的设备共享的,如中断 
    。有些是不能共享的,如IO、DMA。接下来你要初始化device结构中的变量。最后,你可 
    以让硬件正式开始工作。 
    2.2.2 打开(open) 
    open这个方法在网络设备驱动程序里是网络设备被激活的时候被调用(即设备状态由dow 
    n-->up)。所以实际上很多在initialize中的工作可以放到这里来做。比如资源的申请, 
    硬件的激活。如果dev->open返回非0(error),则硬件的状态还是down。 
    open方法另一个作用是如果驱动程序做为一个模块被装入,则要防止模块卸载时设备处 
    于打开状态。在open方法里要调用MOD_INC_USE_COUNT宏。 
    2.2.3 关闭(stop) 
    close方法做和open相反的工作。可以释放某些资源以减少系统负担。close是在设备状 
    态由up转为down时被调用的。另外如果是做为模块装入的驱动程序,close里应该调用M 
    OD_DEC_USE_COUNT,减少设备被引用的次数,以使驱动程序可以被卸载。 
    另外close方法必须返回成功(0==success)。 
    2.2.4 发送(hard_start_xmit) 
    所有的网络设备驱动程序都必须有这个发送方法。在系统调用驱动程序的xmit时,发送 
    的数据放在一个sk_buff结构中。一般的驱动程序把数据传给硬件发出去。也有一些特殊 
    的设备比如loopback把数据组成一个接收数据再回送给系统,或者dummy设备直接丢弃数 
    据。 
    如果发送成功,hard_start_xmit方法里释放sk_buff,返回0(发送成功)。如果设备暂时 
    无法处理,比如硬件忙,则返回1。这时如果dev->tbusy置为非0,则系统认为硬件忙, 
    要等到dev->tbusy置0以后才会再次发送。tbusy的置0任务一般由中断完成。硬件在发送 
    结束后产生中断,这时可以把tbusy置0,然后用mark_bh()调用通知系统可以再次发送。 
    在发送不成功的情况下,也可以不置dev->tbusy为非0,这样系统会不断尝试重发。如果 
    hard_start_xmit发送不成功,则不要释放sk_buff。传送下来的sk_buff中的数据已经包 
    含硬件需要的帧头。所以在发送方法里不需要再填充硬件帧头,数据可以直接提交给硬 
    件发送。sk_buff是被锁住的(locked),确保其他程序不会存取它。 
    2.2.5 接收(reception) 
    驱动程序并不存在一个接收方法。有数据收到应该是驱动程序来通知系统的。一般设备 
    收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff(skb),从 
    硬件读出数据放置到申请好的缓冲区里。接下来填充sk_buff中 的一些信息。skb->dev 
     = dev,判断收到帧的协议类型,填入skb->protocol(多协 议的支持)。把指针skb->m 
    ac.raw指向硬件数据然后丢弃硬件帧头(skb_pull)。还要设置skb->pkt_type,标明第二 
    层(链路层)数据类型。可以是以下类型: 
    PACKET_BROADCAST : 链路层广播 
    PACKET_MULTICAST : 链路层组播 
    PACKET_SELF : 发给自己的帧 
    PACKET_OTHERHOST : 发给别人的帧(监听模式时会有这种帧) 
    最后调用netif_rx()把数据传送给协议层。netif_rx()里数据放入处理队列然后返回, 
    真正的处理是在中断返回以后,这样可以减少中断时间。调用netif_rx()以后, 
    驱动程序就不能再存取数据缓冲区skb。 
    2.2.6 硬件帧头(hard_header) 
    硬件一般都会在上层数据发送之前加上自己的硬件帧头,比如以太网(Ethernet)就有14 
    字节的帧头。这个帧头是加在上层ip、ipx等数据包的前面的。驱动程序提供一个hard_ 
    header方法,协议层(ip、ipx、arp等)在发送数据之前会调用这段程序。 
    硬件帧头的长度必须填在dev->hard_header_len,这样协议层回在数据之前保留好硬件 
    帧头的空间。这样hard_header程序只要调用skb_push然后正确填入硬件帧头就可以了。 
     
    在协议层调用hard_header时,传送的参数包括(2.0.xx):数据的sk_buff,device指针 
    ,protocol,目的地址(daddr),源地址(saddr),数据长度(len)。数据长度不要使用s 
    k_buff中的参数,因为调用hard_header时数据可能还没完全组织好。saddr是NULL的话 
    是使用缺省地址(default)。daddr是NULL表明协议层不知道硬件目的地址。如果hard_h 
    eader完全填好了硬件帧头,则返回添加的字节数。如果硬件帧头中的信息还不完全(比 
    如daddr为NULL,但是帧头中需要目的硬件地址。典型的情况是以太网需要地址解析(ar 
    p)),则返回负字节数。hard_header返回负数的情况下,协议层会做进一步的build he 
    ader的工作。目前Linux系统里就是做arp (如果hard_header返回正,dev->arp=1,表明 
    不需要做arp,返回负,dev->arp=0,做arp)。 
    对hard_header的调用在每个协议层的处理程序里。如ip_output。 
    2.2.7 地址解析(xarp) 
    有些网络有硬件地址(比如Ethernet),并且在发送硬件帧时需要知道目的硬件地址。这 
    样就需要上层协议地址(ip、ipx)和硬件地址的对应。这个对应是通过地址解析完成的。 
    需要做arp的的设备在发送之前会调用驱动程序的rebuild_header方法。调用的主要参数 
    包括指向硬件帧头的指针,协议层地址。如果驱动程序能够解析硬件地址,就返回1,如 
    果不能,返回0。 
    对rebuild_header的调用在net/core/dev.c的do_dev_queue_xmit()里。 
    2.2.8 参数设置和统计数据 
    在驱动程序里还提供一些方法供系统对设备的参数进行设置和读取信息。一般只有超级 
    用户(root)权限才能对设备参数进行设置。设置方法有: 
    dev->set_mac_address() 
    当用户调用ioctl类型为SIOCSIFHWADDR时是要设置这个设备的mac地址。一般对mac地址 
    的设置没有太大意义的。 
    dev->set_config() 
    当用户调用ioctl时类型为SIOCSIFMAP时,系统会调用驱动程序的set_config方法。用户 
    会传递一个ifmap结构包含需要的I/O、中断等参数。 
    dev->do_ioctl() 
    如果用户调用ioctl时类型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之间,系统会调用驱 
    动程序的这个方法。一般是设置设备的专用数据。 
    读取信息也是通过ioctl调用进行。除次之外驱动程序还可以提供一个 
    dev->get_stats方法,返回一个enet_statistics结构,包含发送接收的统计信息。ioc 
    tl的处理在net/core/dev.c的dev_ioctl()和dev_ifsioc()里。 
    linuxman@263.net 
    .3 网络驱动程序中用到的数据结构 
    最重要的是网络设备的数据结构。定义在include/linux/netdevice.h里。它的注释已经 
    足够详尽。 
    struct device 

    /* 
    * This is the first field of the "visible" part of this structure 
    * (i.e. as seen by users in the "Space.c" file). It is the name 
    * the interface. 
    */ 
    char *name; 
    /* I/O specific fields - FIXME: Merge these and struct ifmap into one */ 
    unsigned long rmem_end; /* shmem "recv" end */ 
    unsigned long rmem_start; /* shmem "recv" start */ 
    unsigned long mem_end; /* shared mem end */ 
    unsigned long mem_start; /* shared mem start */ 
    unsigned long base_addr; /* device I/O address */ 
    unsigned char irq; /* device IRQ number */ 
    /* Low-level status flags. */ 
    volatile unsigned char start, /* start an operation */ 
    interrupt; /* interrupt arrived */ 
    /* 在处理中断时interrupt设为1,处理完清0。 */ 
    unsigned long tbusy; /* transmitter busy must be long for 
    bitops */ 
    struct device *next; 
    /* The device initialization function. Called only once. */ 
    /* 指向驱动程序的初始化方法。 */ 
    int (*init)(struct device *dev); 
    /* Some hardware also needs these fields, but they are not part of the 
    usual set specified in Space.c. */ 
    /* 一些硬件可以在一块板上支持多个接口,可能用到if_port。 */ 
    unsigned char if_port; /* Selectable AUI, TP,..*/ 
    unsigned char dma; /* DMA channel */ 
    struct enet_statistics* (*get_stats)(struct device *dev); 
    /* 
    * This marks the end of the "visible" part of the structure. All 
    * fields hereafter are internal to the system, and may change at 
    * will (read: may be cleaned up at will). 
    */ 
    /* These may be needed for future network-power-down code. */ 
    /* trans_start记录最后一次成功发送的时间。可以用来确定硬件是否工作正常。*/ 
    unsigned long trans_start; /* Time (in jiffies) of last Tx */ 
    unsigned long last_rx; /* Time of last Rx */ 
    /* flags里面有很多内容,定义在include/linux/if.h里。*/ 
    unsigned short flags; /* interface flags (a la BSD) */ 
    unsigned short family; /* address family ID (AF_INET) */ 
    unsigned short metric; /* routing metric (not used) */ 
    unsigned short mtu; /* interface MTU value */ 
    /* type标明物理硬件的类型。主要说明硬件是否需要arp。定义在 
    include/linux/if_arp.h里。 */ 
    unsigned short type; /* interface hardware type */ 
    /* 上层协议层根据hard_header_len在发送数据缓冲区前面预留硬件帧头空间。*/ 
    unsigned short hard_header_len; /* hardware hdr length */ 
    /* priv指向驱动程序自己定义的一些参数。*/ 
    void *priv; /* pointer to private data */ 
    /* Interface address info. */ 
    unsigned char broadcast[MAX_ADDR_LEN]; /* hw bcast add */ 
    unsigned char pad; /* make dev_addr aligned to 8 
    bytes */ 
    unsigned char dev_addr[MAX_ADDR_LEN]; /* hw address */ 
    unsigned char addr_len; /* hardware address length */ 
    unsigned long pa_addr; /* protocol address */ 
    unsigned long pa_brdaddr; /* protocol broadcast addr */ 
    unsigned long pa_dstaddr; /* protocol P-P other side addr */ 
    unsigned long pa_mask; /* protocol netmask */ 
    unsigned short pa_alen; /* protocol address length */ 
    struct dev_mc_list *mc_list; /* Multicast mac addresses */ 
    int mc_count; /* Number of installed mcasts */ 
    struct ip_mc_list *ip_mc_list; /* IP multicast filter chain */ 
    __u32 tx_queue_len; /* Max frames per queue allowed */ 
    /* For load balancing driver pair support */ 
    unsigned long pkt_queue; /* Packets queued */ 
    struct device *slave; /* Slave device */ 
    struct net_alias_info *alias_info; /* main dev alias info */ 
    struct net_alias *my_alias; /* alias devs */ 
    /* Pointer to the interface buffers. */ 
    struct sk_buff_head buffs[DEV_NUMBUFFS]; 
    /* Pointers to interface service routines. */ 
    int (*open)(struct device *dev); 
    int (*stop)(struct device *dev); 
    int (*hard_start_xmit) (struct sk_buff *skb, 
    struct device *dev); 
    int (*hard_header) (struct sk_buff *skb, 
    struct device *dev, 
    unsigned short type, 
    void *daddr, 
    void *saddr, 
    unsigned len); 
    int (*rebuild_header)(void *eth, struct device *dev, 
    unsigned long raddr, struct sk_buff *skb); 
    #define HAVE_MULTICAST 
    void (*set_multicast_list)(struct device *dev); 
    #define HAVE_SET_MAC_ADDR 
    int (*set_mac_address)(struct device *dev, void *addr); 
    #define HAVE_PRIVATE_IOCTL 
    int (*do_ioctl)(struct device *dev, struct ifreq *ifr, int cmd); 
    #define HAVE_SET_CONFIG 
    int (*set_config)(struct device *dev, struct ifmap *map); 
    #define HAVE_HEADER_CACHE 
    void (*header_cache_bind)(struct hh_cache **hhp, struct device 
    *dev, unsigned short htype, __u32 daddr); 
    void (*header_cache_update)(struct hh_cache *hh, struct device 
    *dev, unsigned char * haddr); 
    #define HAVE_CHANGE_MTU 
    int (*change_mtu)(struct device *dev, int new_mtu); 
    struct iw_statistics* (*get_wireless_stats)(struct device *dev); 
    }; 
    2.4 常用的系统支持 
    2.4.1 内存申请和释放 
    include/linux/kernel.h里声明了kmalloc()和kfree()。用于在内核模式下申请和释放 
    内存。 
    void *kmalloc(unsigned int len,int priority); 
    void kfree(void *__ptr); 
    与用户模式下的malloc()不同,kmalloc()申请空间有大小限制。长度是2的整次方。可 
    以申请的最大长度也有限制。另外kmalloc()有priority参数,通常使用时可以为GFP_K 
    ERNEL,如果在中断里调用用GFP_ATOMIC参数,因为使用GFP_KERNEL 则调用者可能进入 
    sleep状态,在处理中断时是不允许的。 
    kfree()释放的内存必须是kmalloc()申请的。如果知道内存的大小,也可以用kfree_s( 
    )释放。 
    2.4.2 request_irq()、free_irq() 
    这是驱动程序申请中断和释放中断的调用。在include/linux/sched.h里声明。 
    request_irq()调用的定义: 
    int request_irq(unsigned int irq, 
    void (*handler)(int irq, void *dev_id, struct pt_regs *regs), 
    unsigned long irqflags, 
    const char * devname, 
    void *dev_id); 
    irq是要申请的硬件中断号。在Intel平台,范围0--15。handler是向系统登记的中断处 
    理函数。这是一个回调函数,中断发生时,系统调用这个函数,传入的参 数包括硬件中 
    断号,device id,寄存器值。dev_id就是下面的request_irq时传递 给系统的参数dev 
    _id。irqflags是中断处理的一些属性。比较重要的有SA_INTERRUPT, 
    标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_IN 
    TERRUPT)。快速处理程序被调用时屏蔽所有中断。慢速处理程序不屏蔽。还有 一个SA_ 
    SHIRQ属性,设置了以后运行多个设备共享中断。dev_id在中断共享时会用到。一般设置 
    为这个设备的device结构本身或者NULL。中断处理程序可以用dev_id 找到相应的控制这 
    个中断的设备,或者用irq2dev_map找到中断对应的设备。 
    void free_irq(unsigned int irq,void *dev_id); 
    2.4.3 时钟 
    时钟的处理类似中断,也是登记一个时间处理函数,在预定的时间过后,系统会调用这 
    个函数。在include/linux/timer.h里声明。 
    struct timer_list { 
    struct timer_list *next; 
    struct timer_list *prev; 
    unsigned long expires; 
    unsigned long data; 
    void (*function)(unsigned long); 
    }; 
    void add_timer(struct timer_list * timer); 
    int del_timer(struct timer_list * timer); 
    void init_timer(struct timer_list * timer); 
    使用时钟,先声明一个timer_list结构,调用init_timer对它进行初始化。 
    time_list结构里expires是标明这个时钟的周期,单位采用jiffies的单位。 
    jiffies是Linux一个全局变量,代表时间。它的单位随硬件平台的不同而不同。 
    系统里定义了一个常数HZ,代表每秒种最小时间间隔的数目。这样jiffies的单位就是1 
    /HZ。Intel平台jiffies的单位是1/100秒,这就是系统所能分辨的最小时间间隔了。所 
    以expires/HZ就是以秒为单位的这个时钟的周期。 
    function就是时间到了以后的回调函数,它的参数就是timer_list中的data。data这个 
    参数在初始化时钟的时候赋值,一般赋给它设备的device结构指针。 
    在预置时间到系统调用function,同时系统把这个time_list从定时队列里清除。所以如 
    果需要一直使用定时函数,要在function里再次调用add_timer()把这个timer_list加进 
    定时队列。 
    2.4.4 I/O 
    I/O端口的存取使用: 
    inline unsigned int inb(unsigned short port); 
    inline unsigned int inb_p(unsigned short port); 
    inline void outb(char value, unsigned short port); 
    inline void outb_p(char value, unsigned short port); 
    在include/adm/io.h里定义。 
    inb_p()、outb_p()与inb()、outb_p()的不同在于前者在存取I/O时有等待(pause)一适 
    应慢速的I/O设备。 
    为了防止存取I/O时发生冲突,Linux提供对端口使用情况的控制。在使用端口之前,可 
    以检查需要的I/O是否正在被使用,如果没有,则把端口标记为正在使用,使用完后再释 
    放。系统提供以下几个函数做这些工作。 
    int check_region(unsigned int from, unsigned int extent); 
    void request_region(unsigned int from, unsigned int extent,const char *name) 

    void release_region(unsigned int from, unsigned int extent); 
    其中的参数from表示用到的I/O端口的起始地址,extent标明从from开始的端口数目。n 
    ame为设备名称。 
    2.4.5 中断打开关闭 
    系统提供给驱动程序开放和关闭响应中断的能力。是在include/asm/system.h中的两个 
    定义。 
    #define cli() __asm__ __volatile__ ("cli"::) 
    #define sti() __asm__ __volatile__ ("sti"::) 
    2.4.6 打印信息 
    类似普通程序里的printf(),驱动程序要输出信息使用printk()。在include/linux/ke 
    rnel.h里声明。 
    int printk(const char* fmt, ...); 
    其中fmt是格式化字符串。...是参数。都是和printf()格式一样的。 
    2.4.7 注册驱动程序 
    如果使用模块(module)方式加载驱动程序,需要在模块初始化时把设备注册 到系统设备 
    表里去。不再使用时,把设备从系统中卸除。定义在drivers/net/net_init.h里的两个 
    函数完成这个工作。 
    int register_netdev(struct device *dev); 
    void unregister_netdev(struct device *dev); 
    dev就是要注册进系统的设备结构指针。在register_netdev()时,dev结构一般填写前面 
    11项,即到init,后面的暂时可以不用初始化。最重要的是name指针和init方法。name 
    指针空(NULL)或者内容为\或者name[0]为空格(space),则系统把你的设备做为以太网设 
    备处理。以太网设备有统一的命名格式,ethX。对以太网这么特别对待大概和Linux的历 
    史有关。 
    init方法一定要提供,register_netdev()会调用这个方法让你对硬件检测和设置。 
    register_netdev()返回0表示成功,非0不成功。 
    2.4.8 sk_buff 
    Linux网络各层之间的数据传送都是通过sk_buff。sk_buff提供一套管理缓冲区的方法, 
    是Linux系统网络高效运行的关键。每个sk_buff包括一些控制方法和一块数据缓冲区。 
    控制方法按功能分为两种类型。一种是控制整个buffer链的方法, 
    另一种是控制数据缓冲区的方法。sk_buff组织成双向链表的形式,根据网络应用的特点 
    ,对链表的操作主要是删除链表头的元素和添加到链表尾。sk_buff的控制 
    方法都很短小以尽量减少系统负荷。(translated from article written by Alan Cox 

    常用的方法包括: 
    .alloc_skb() 申请一个sk_buff并对它初始化。返回就是申请到的sk_buff。 
    .dev_alloc_skb()类似alloc_skb,在申请好缓冲区后,保留16字节的帧头空间。主要用 
    在Ethernet驱动程序。 
    .kfree_skb() 释放一个sk_buff。 
    .skb_clone() 复制一个sk_buff,但不复制数据部分。 
    .skb_copy()完全复制一个sk_buff。 
    .skb_dequeue() 从一个sk_buff链表里取出第一个元素。返回取出的sk_buff,如果链表 
    空则返回NULL。这是常用的一个操作。 
    .skb_queue_head() 在一个sk_buff链表头放入一个元素。 
    .skb_queue_tail() 在一个sk_buff链表尾放入一个元素。这也是常用的一个操作。网络 
    数据的处理主要是对一个先进先出队列的管理,skb_queue_tail() 
    和skb_dequeue()完成这个工作。 
    .skb_insert() 在链表的某个元素前插入一个元素。 
    .skb_append() 在链表的某个元素后插入一个元素。一些协议(如TCP)对没按顺序到达的 
    数据进行重组时用到skb_insert()和skb_append()。 
    .skb_reserve() 在一个申请好的sk_buff的缓冲区里保留一块空间。这个空间一般是用 
    做下一层协议的头空间的。 
    .skb_put() 在一个申请好的sk_buff的缓冲区里为数据保留一块空间。在 
    alloc_skb以后,申请到的sk_buff的缓冲区都是处于空(free)状态,有一个tail指针指 
    向free空间,实际上开始时tail就指向缓冲区头。skb_reserve() 
    在free空间里申请协议头空间,skb_put()申请数据空间。见下面的图。 
    .skb_push() 把sk_buff缓冲区里数据空间往前移。即把Head room中的空间移一部分到 
    Data area。 
    .skb_pull() 把sk_buff缓冲区里Data area中的空间移一部分到Head room中。 
    -------------------------------------------------- 
    | Tail room(free) | 
    -------------------------------------------------- 
    After alloc_skb() 
    -------------------------------------------------- 
    | Head room | Tail room(free) | 
    -------------------------------------------------- 
    After skb_reserve() 
    -------------------------------------------------- 
    | Head room | Data area | Tail room(free) | 
    -------------------------------------------------- 
    After skb_put() 
    -------------------------------------------------- 
    |Head| skb_ | Data | Tail room(free) | 
    |room| push | | | 
    | | Data area | | 
    -------------------------------------------------- 
    After skb_push() 
    -------------------------------------------------- 
    | Head | skb_ | Data area | Tail room(free) | 
    | | pull | | | 
    | Head room | | | 
    -------------------------------------------------- 
    After skb_pull() 
    三.编写Linux网络驱动程序中需要注意的问题 
    3.1 中断共享 
    Linux系统运行几个设备共享同一个中断。需要共享的话,在申请的时候指明共享方式。 
    系统提供的request_irq()调用的定义: 
    int request_irq(unsigned int irq, 
    void (*handler)(int irq, void *dev_id, struct pt_regs *regs), 
    unsigned long irqflags, 
    const char * devname, 
    void *dev_id); 
    如果共享中断,irqflags设置SA_SHIRQ属性,这样就允许别的设备申请同一个中断。需 
    要注意所有用到这个中断的设备在调用request_irq()都必须设置这个属性。系统在回调 
    每个中断处理程序时,可以用dev_id这个参数找到相应的设备。一 般dev_id就设为dev 
    ice结构本身。系统处理共享中断是用各自的dev_id参数依次调用每一个中断处理程序。 
     
    3.2 硬件发送忙时的处理 
    主CPU的处理能力一般比网络发送要快,所以经常会遇到系统有数据要发,但上一包数据 
    网络设备还没发送完。因为在Linux里网络设备驱动程序一般不做数据缓存,不能发送的 
    数据都是通知系统发送不成功,所以必须要有一个机制在硬件不忙时及时通知系统接着 
    发送下面的数据。 
    一般对发送忙的处理在前面设备的发送方法(hard_start_xmit)里已经描述过,即如果发 
    送忙,置tbusy为1。处理完发送数据后,在发送结束中断里清tbusy,同时用mark_bh() 
    调用通知系统继续发送。 
    但在具体实现我的驱动程序时发现,这样的处理系统好象并不能及时地知道硬件已经空 
    闲了,即在mark_bh()以后,系统要等一段时间才会接着发送。造成发送效率很低。2M线 
    路只有10%不到的使用率。内核版本为2.0.35。 
    我最后的实现是不把tbusy置1,让系统始终认为硬件空闲,但是报告发送不成功。系统 
    会一直尝试重发。这样处理就运行正常了。但是遍循内核源码中的网络驱动程序,似乎 
    没有这样处理的。不知道症结在哪里。 
    3.3 流量控制(flow control) 
    网络数据的发送和接收都需要流量控制。这些控制是在系统里实现的,不需要驱动程序 
    做工作。每个设备数据结构里都有一个参数dev->tx_queue_len,这个参数标明发送时最 
    多缓存的数据包。在Linux系统里以太网设备(10/100Mbps)tx_queue_len一般设置为100 
    ,串行线路(异步串口)为10。实际上如果看源码可以知道,设置了dev->tx_queue_len并 
    不是为缓存这些数据申请了空间。这个参数只是在收到协议层的数据包时判断发送队列 
    里的数据是不是到了tx_queue_len的限度,以决定这一包数据加不加进发送队列。发送 
    时另一个方面的流控是更高层协议的发送窗口(TCP协议里就有发送窗口)。达到了窗口大 
    小,高层协议就不会再发送数据。 
    接收流控也分两个层次。netif_rx()缓存的数据包有限制。另外高层协议也会有一个最 
    大的等待处理的数据量。 
    发送和接收流控处理在net/core/dev.c的do_dev_queue_xmit()和netif_rx()中。 
    3.4 调试 
    很多Linux的驱动程序都是编译进内核的,形成一个大的内核文件。但对调试来说,这是 
    相当麻烦的。调试驱动程序可以用module方式加载。支持模块方式的驱动程序必须提供 
    两个函数:int init_module(void)和void cleanup_module(void)。init_module()在加 
    载此模块时调用,在这个函数里可以register_netdev()注册设备。init_module()返回 
    0表示成功,返回负表示失败。cleanup_module()在驱动程序被卸载时调用,清除占用的 
    资源,调用unregister_netdev()。 
    模块可以动态地加载、卸载。在2.0.xx版本里,还有kerneld自动加载模块,但是2.2.x 
    x中已经取消了kerneld。手工加载使用insmod命令,卸载用rmmod命令,看内核中的模块 
    用lsmod命令。 
    编译驱动程序用gcc,主要命令行参数-DKERNEL -DMODULE。并且作为模块加载的驱动程 
    序,只编译成obj形式(加-c参数)。编译好的目标文件放在/lib/modules/2.x.xx/misc下 
    ,在启动文件里用insmod加载。 
    四.进一步的阅读 
    Linux程序设计资料可以从网上获得。这就是开放源代码的好处。并且没有什么“未公开 
    的秘密”。我编写驱动程序时参阅的主要资料包括: 
    Linux内核源代码 
    <> by Michael K. Johnson 
    <> by Ori Pomerantz 
    by olly in BBS水木清华站 
    可以选择一个模板作为开始,内核源代码里有一个网络驱动程序的模板, 
    drivers/net/skeleton.c。里面包含了驱动程序的基本内容。但这个模板是以以太网设 
    备为对象的,以太网的处理在Linux系统里有特殊“待遇”,所以如果不是以太网设备, 
    有些细节上要注意,主要在初始化程序里。 
    最后,多参照别人写的程序,听听其他开发者的经验之谈大概是最有效的帮助了
    展开全文
  • 一、协议栈层次对比 二、Linux网络子系统 ...它为用户空间提供的应用程序提供了一种访问内核网络子系统的方法(socket)。位于其下面是一个协议无关层,它提供一种通用的方法来使用传输层协议...

     

    转载:http://blog.csdn.net/zqixiao_09/article/details/51146724

    一、协议栈层次对比

     

     

    二、Linux网络子系统

     

     

        Linux网络子系统的顶部是系统调用接口层。它为用户空间提供的应用程序提供了一种访问内核网络子系统的方法(socket)。位于其下面是一个协议无关层,它提供一种通用的方法来使用传输层协议。然后是具体协议的实现,在Linux中包括内核的协议TCP,UDP,当然还有IP。然后是设备无关层,它提供了协议与设备驱动通信的通用接口,最下面是设备的驱动程序。


        设备无关接口将协议与各种网络驱动连接在一起,这一层提供一组通用函数供底层网络设备驱动使用,让它们可以对高层协议栈进行操作。需要从协议层向设备发生数据,需要调用dev_queue_xmit函数,这个函数对数据进行列队,然后交由底层驱动程序的hard_start_xmit方法最终完成传输。接收通常是使用netif_rx执行的。当底层设备程序接收到一个报文(发生中断)时,就会调用netif_rx将数据上传至设备无关层。


    三、设备无关层到驱动层的体系结构

    下图为设备无关层到驱动层的体系结构

     

    1)、网络协议接口层向网络层协议提供提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接受数据。这一层的存在使得上层协议独立于具体的设备。
    2)、网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。
    3)、设备驱动功能层各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,他通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接受操作。
    4)、网络设备与媒介层是完成数据包发送和接受的物理实体,包括网络适配器和具体的传输媒介,网络适配器被驱动功能层中的函数物理上驱动。对于Linux系统而言,网络设备和媒介都可以是虚拟的。

     

    1、网络协议接口层:

    这里主要进行数据包的收发,使用函数原型为:

    [cpp] view plain copy

    1. dev_queue_xmit(struct sk_buff *skb);int netif_rx(struct sk_buff *skb);    

     

    这里使用了一个skb_buff结构体,定义于include/linux/skbuff.h中,它的含义为“套接字缓冲区”,用于在Linux网络子系统各层间传输数据。他是一个双向链表,在老的内核中会有一个list域指向sk_buff_head也就是链表头,但是在我研究的linux2.6.30.4内核中已经不存在了,如下图:

     

     

     

     

    sk_buff中重要的数据成员

    struct device *dev;正在处理该包的设备

    __u32 sadd;r//IP元地址

    __u32 daddr;//IP目的地址

    __u32 raddr;//IP路由器地址

    unsigned char *head;//分配空间的开始

    unsigned char *data;//有效数据的开始

    unsigned char *tail;//有效数据的结束

    unsigned char *end;//分配空间的结束

    unsigned long len;//有效数据的长度


    sk_buff操作
    a -- 分配:分配一个sk_buff结构,供协议栈代码使用

    [cpp] view plain copy

    1. struct sk_buff *alloc_skb(unsigned int len, int priority);  
    2. struct sk_buff *dev_alloc_skb(unsigned int len);    

      分配一个缓冲区。alloc_skb函数分配一个缓冲区并初始化skb->data和skb->tail为skb->head。参数len为数据缓冲区的空间大小,通常以L1_CACHE_BYTES字节(对ARM为32)对齐,参数priority为内存分配的优先级。dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配。

    b -- 释放:

    [cpp] view plain copy

    1. void kfree_skb(struct sk_buff *skb);  
    2. void dev_kfree_skb(struct sk_buff *skb);   

    Linux内核内部使用kfree_skb()函数,而网络设备驱动程序中则最好使用dev_kfree_skb()。

    sk_buff中比较重要的成员是指向数据包中数据的指针,如下图所示:


    用于寻址数据包中数据的指针,head指向已分配空间开头,data指向有效的octet开头,tail指向有效的octet结尾,而end指向tail可以到达的最大地址。如果不这样做而分配一个大小固定的缓冲区,如果buffer不够用,则要申请一个更大的buffer,拷贝进去再增加,这样降低了性能。


    3)变更

    [cpp] view plain copy

    1. unsigned char *skb_put(struct sk_buff *skb, int len);将taill指针向后移动len长度,并返回tail移动之前的值。用于向skb有效数据区域末尾添加数据。  
    2. unsigned char *skb_push(struct sk_buff *skb, int len);将data指针向前移动len长度。并返回移动之后的值。用于向skb有效数据区域前端添加数据(包头)。  
    3. unsigned char *skb_pull(struct sk_buff *skb, int len);  
    4. void skb_reserve(struct sk_buff ×skb, int len);   

     

    下图分别对应了这四个函数,看了这张图应该对这4个函数的作用了然于胸。 

     

     

     

    2、网络设备接口层:

     网络设备接口层的主要功能是为千变万化的网络设备定义了统一,抽象的数据结构net_device结构体,以不变应万变,实现多种硬件在软件层次上的统一。

     

        每一个网络设备都由struct net_device来描述,该结构可使用如下内核函数进行动态分配

    [cpp] view plain copy

    1. struct net_device *alloc_netdev(int sizeof_priv, const char *mask, void(*setup)(struct net_device *))  

    sizeof_priv是私有数据区大小;mask是设备名,setup是初始化函数,在注册该设备时,该函数被调用。也就是net_deivce的init成员。

    [cpp] view plain copy

    1. struct net_device *alloc_etherdev(intsizeof_priv)  

    这个函数和上面的函数不同之处在于内核知道会将该设备做一个以太网设备看待并做一些相关的初始化。

     

    net_device结构可分为全局成员、硬件相关成员、接口相关成员、设备方法成员和公用成员等五个部分

    a -- 主要全局成员

    [cpp] view plain copy

    1. char name[INFAMSIZ]    设备名,如:eh%d  
    2. unsigned long state  设备状态  
    3. unsigned long base_addr  I/O基地址  
    4. unsigned int irq   中断号  

    b -- 主要设备方法

    [cpp] view plain copy

    1. //首先看打开和关闭网络设备的函数:  
    2.   
    3. int (*open)(struct net_device *dev);  
    4. //打开接口。ifconfig激活时,接口将被打开  
    5.   
    6. int (*stop)(struct net_device *dev);    
    7. //停止接口,ifconfig eth% down时调用  
    8. //要注意的是ifconfig是interface config的缩写,通常我们在用户空间输入:  
    9. //ifconfig eth0 up  会调用这里的open函数。  
    10. //在用户空间输入:  
    11. //ifconfig eth0 down  会调用这里的stop函数。  
    12. //在使用ifconfig向接口赋予地址时,要执行两个任务。首先,它通过ioctl(SIOCSIFADDR)(Socket I/O Control Set Interface Address)赋予地址,然后通过ioctl(SIOCSIFFLAGS)(Socket I/O Control Set Interface Flags)设置dev->flag中的IFF_UP标志以打开接口。这个调用会使得设备的open方法得到调用。类似的,在接口关闭时,ifconfig使用ioctl(SIOCSIFFLAGS)来清理IFF_UP标志,然后调用stop函数。  
    13.   
    14. int  (*init)(struct  net_device *dev)  
    15. //初始化函数,该函数在register_netdev时被调用来完成对net_device结构的初始化  
    16.   
    17. int (*hard_start_xmit)(struct sk_buf*skb,struct net_device *dev)  
    18. //数据发送函数  
    19.   
    20. int (*hard_header)(struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len);   
    21. //该方法根据先前检索到的源和目的硬件地址建立硬件头  
    22.   
    23. int (*rebuild_header)(struct sk_buff *skb);  
    24. //以太网的mac地址是固定的,为了高效,第一个包去询问mac地址,得到对应的mac地址后就会作为cache把mac地址保存起来。以后每次发包不用询问了,直接把包的地址拷贝出来。  
    25.   
    26. void (*tx_timeout)(struct net_device *dev);    
    27. //如果数据包发送在超时时间内失败,这时该方法被调用,这个方法应该解决失败的问题,并重新开始发送数据。  
    28.   
    29. struct net_device_stats *(*get_stats)(struct net_device *dev);    
    30. //当应用程序需要获得接口的统计信息时,这个方法被调用。  
    31.   
    32. int (*set_config)(struct net_device *dev, struct ifmap *map);    
    33. //改变接口的配置,比如改变I/O端口和中断号等,现在的驱动程序通常无需该方法。  
    34.   
    35. int (*do_ioctl)(struct net_device *dev, struct ifmap *map);    
    36. //用来实现自定义的ioctl命令,如果不需要可以为NULL。  
    37.   
    38. void (*set_multicast_list)(struct net_device *dev);    
    39. //当设备的组播列表改变或设备标志改变时,该方法被调用。  
    40.   
    41. int (*set_mac_address)(struct net_device *dev, void *addr);    
    42. //如果接口支持mac地址改变,则可以实现该函数。</span>  

     

    3、设备驱动接口层:

    net_device结构体的成员(属性和函数指针)需要被设备驱动功能层的具体数值和函数赋予。对具体的设置xxx,工程师应该编写设备驱动功能层的函数,这些函数型如xxx_open(),xxx_stop(),xxx_tx(),xxx_hard_header(),xxx_get_stats(),xxx_tx_timeout()等。

     

    4、网络设备与媒介层:

    网络设备与媒介层直接对应于实际的硬件设备。

     

    网络设备的注册

    网络设备注册方式与字符驱动不同之处在于它没有主次设备号,并使用下面的函数注册

     

    [cpp] view plain copy

    1. int register_netdev(struct net_deivce*dev)  

    网络设备的注销

    [cpp] view plain copy

    1. void unregister_netdev(struct net_device*dev)  

     

     

     

    四、驱动的实现

    1)初始化(init)

    设备探测工作在init方法中进行,一般调用一个称之为probe方法的函数

    初始化的主要工作时检测设备,配置和初始化硬件,最后向系统申请这些资源。此外填充该设备的dev结构,我们调用内核提供的ether_setup方法来设置一些以太网默认的设置。

     

    2)打开(open)

    open这个方法在网络设备驱动程序里是网络设备被激活时被调用(即设备状态由down变成up)

    实际上很多在初始化的工作可以放到这里来做。比如说资源的申请,硬件的激活。如果dev->open返回非0,则硬件状态还是down,
    注册中断、DMA等;设置寄存器,启动设备;启动发送队列

     一般注册中断都在init中做,但在网卡驱动程序中,注册中断大部分都是放在open中注册,因为要经常关闭和重启网卡

     

    3)关闭(stop)

    stop方法做和open相反的工作

    可以释放某些资源以减少系统负担

    stop是在设备状态由up转为down时被调用

     

    4)发送(hard_start_xmit)

    在系统调用的驱动程序的hard_start_xmit时,发送的数据放在一个sk_buff结构中。一般的驱动程序传给硬件发出去。也有一些特殊的设备比如说loopback把数据组成一个接收数据在传送给系统或者dummy设备直接丢弃数据。

    如果发送成功,hard_start_xmit方法释放sk_buff。如果设备暂时无法处理,比如硬件忙,则返回1。

     

    5)接收

    驱动程序并存在一个接受方法。当有数据收到时驱动程序调用netif_rx函数将skb交交给设备无关层。

    一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff(skb)从硬件中读取数据位置到申请号的缓冲区里。

    接下来填充sk_buff中的一些信息。

    中断有可能是收到数据产生也可能是发送完成产生,中断处理程序要对中断类型进行判断,如果是收到数据中断则开始接收数据,如果是发送完成中断,则处理发送完成后的一些操作,比如说重启发送队列。
    接收流程:
    1、分配skb=dev_alloc_skb(pkt->datalen+2)
    2、从硬件中读取数据到skb
    3、调用netif_rx将数据交给协议栈
     

    中断处理

    网络接口通常支持3种类型的中断:新报文到达中断、报文发送完成中断和出错中断。中断处理程序可通过查看网卡的中断状态寄存器,来分辨出中断类型。

    展开全文
  • NDIS开发[网络驱动开发] NDIS开发1

    千次阅读 2012-07-11 15:10:08
    转自:... NDIS开发[网络驱动开发] NDIS开发(1)  2009-02-25 17:58:43| 分类:VPN编程 |字号 订阅 目 录 1 NDIS中间层驱动程序 2 1.1 NDIS中间层驱动程序(NDIS Inte
  •  Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计。  Linux内核采用分层结构处理网络数据包。分层结构与网络协议的结构匹配,既能简化数据包处理流程,又便于扩展和维护。 ...
  • (原创文章,转载注明出处,谢谢)这些天花...开发环境的建立请参考:QEMU MINI2440 的 Linux FC 下网络配置http://blog.csdn.net/coolbacon/archive/2011/03/16/6252938.aspx关于RTEMS MINI2440的QEMU仿真从UBOOT加载问
  • NIC1394 网卡驱动收包过程: <br />网卡的发包和收包过程 网卡也叫“网络适配器”,英文全称为“Network Interface Card”,简称“NIC”,网卡是局域网中最基本的部件之一,它是连接计算机与网络的...
  • 这一篇详细介绍一下zynq下linux内核中网络驱动的运行过程。 1、基本层次 在linux中,网络可以分为下面三个层次: Linux网络驱动涉及到后面两层,网络协议层中需要了解skb和netif;硬件驱动层也就是mac层,需要...
  • VxWorks设备驱动开发详解

    千次阅读 2011-05-03 09:48:00
    <br />华清远见系列图书 VxWorks设备驱动开发详解 曹桂平 等编著  ISBN 978-7-121-12828-8 2011年3月出版 定价:49.00元 16开 420 页 内 容 提 要 Shell是用户...
  • qnx驱动开发之编程基础

    千次阅读 2016-04-14 15:13:47
    qnx驱动开发之编程基础 包括:线程与同步;QNX微内核进程间通信IPC; QNX时间相关 主题: 1.线程 2.同步 3.消息传递message 4.脉冲pulses 5.事件传送event 6.时间 7.总结1.线程 1.1 进程与线程 线程在进程...
  • 简易HP网络打印机驱动开发(一)

    千次阅读 2010-03-23 21:09:00
    1、打印机接口分类 常见的打印机接口有串口打印机、并口打印机、USB打印机、网络打印机。2、打印机指令集分类指令集名称厂家用途ESC PK指令集EPSON针式打印机(尤其是滚筒方式打印)领域事实上的工业标准PCL指令集...
  • 联系信箱:feixiaoxing @163.com】 对于linux驱动来说,一般的架构还是按照bus-host-device的形式来进行的。比如就拿usb来说,通常如果是新的soc,只需要适配一下host就可以了。但是如果要适配其他的usb外接设备...
  • 本博实时更新《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)的最新进展。 目前已经完成稿件。 2015年8月9日,china-pub开始上线预售: ... 2015年8月20日,各路朋友报喜...
  • windows驱动开发推荐书籍

    千次阅读 2016-03-06 01:55:43
    很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资 料少有关系.大多学的驱动开发资料都以英文为主,这样让很多驱动初学者很头疼.本人从 事驱动开发时间不长也不短,大概也就3~4年时间....
  • 浅谈 Linux 内核开发网络设备驱动

    千次阅读 2017-12-12 21:21:27
    转载自 ...这里Mark一下,和同样从事驱动开发的兄弟们进行分享。网络设备介绍网络设备是计算机体系结构中必不可少的一部分,处理器如果想与外界通信,通常都会选择网络设备作为通信接口。众所周知,在
  • Windows驱动开发入门系列教程

    千次阅读 2016-03-28 11:27:41
    从事驱动开发也有一段时间了,从最初的无头苍蝇到懵懵懂懂,到入门,直至今天,感觉一路走来,走了不少的弯路,只因为没有人引导。前几天,一个朋友问到我怎么学习Windows驱动开发,我就想到把我学习Windows驱动开发...
  • Linux 驱动面试题总结

    万次阅读 多人点赞 2016-03-20 20:51:11
     字符设备:字符设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少实现open,close,read和write系统调用。字符终端、串口、鼠标、键盘、摄像头、声卡...
  • linux设备驱动主要分为三类:字符设备驱动、块设备驱动、网络设备驱动 其中字符设备驱动适合...字符设备驱动开发流程 1.编写驱动代码  1.1 定义一个cdev 来表示你的驱动所对应的设备 struct cdev chrdev;  ...
  • 《Linux设备驱动开发详解:基于最新的Linux 4.0内核》
1 2 3 4 5 ... 20
收藏数 260,467
精华内容 104,186
关键字:

网络驱动开发