Chinaunix首页 | 论坛 | 博客
  • 博客访问: 159732
  • 博文数量: 41
  • 博客积分: 2246
  • 博客等级: 大尉
  • 技术积分: 375
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-27 15:45
文章分类
文章存档

2012年(4)

2011年(1)

2010年(11)

2009年(25)

分类: LINUX

2009-09-06 23:16:21

网络设备驱动相比字符型设备的驱动要复杂一些,除了总体上驱动的框架有一些相似外,有很多地方都是不同,但网络设备驱动有一个很大的特点就是有固定的框架 可以遵循,具体的框架会在后边详细的叙述,这里主要分析网络设备驱动的结构,和整个tcp/ip网络结构一样,整个网络设备驱动也是一个分层的结构。具体 如下:


1.网络协议接口层
 
网络协议接口层,只提供了两个抽象函数dev_queue_xmit()与 netif_rx(),之所以称之为抽象函数,是因为这两个函数抽象了很多底层的操作,不管是那个芯片它在网络协议结构的操作函数都是这两个函数,采用这 样的抽象后,给上层带来了很多的方便,给上层协议提供统一的数据包收发接口,无论上层是ARP协议还是IP协议,都通过dev_queue_xmit() 函数发送数据,通过netif_rx()函数接收数据。此层使上层协议独立于具体的设备。
相关数据结构sk_buff:
sk_buff 称为“套接字缓冲区”,用于在Linux网络子系统中各层之间传递数据。是Linux网络子系统数据传递的“中枢神经”。sk_buff定义位置 为:include/linux/skbuff.h,这个数据结构定义了很多用于网络操作的函数,更多的设计整个协议的实现,包括各层报文的头信息,以及 报文的帧格式,下边这个这个结构体是有关报文header信息的,下边的代码摘自kernel2.6.29/include/linux /skbuff.h
 

struct sk_buff {
    /* These two members must be first. */
    struct sk_buff        *next;
    struct sk_buff        *prev;

    struct sock        *sk;
    ktime_t            tstamp;
    struct net_device    *dev;

    union {
        struct dst_entry    *dst;
        struct rtable        *rtable;
    };
#ifdef CONFIG_XFRM
    struct    sec_path    *sp;
#endif
    /*
     * This is the control buffer. It is free to use for every
     * layer. Please put your private variables there. If you
     * want to keep them across layers you have to do a skb_clone()
     * first. This is owned by whoever has the skb queued ATM.
     */

    char            cb[48];

    unsigned int        len,
                data_len;
    __u16            mac_len,
                hdr_len;
    union {
        __wsum        csum;
        struct {
            __u16    csum_start;
            __u16    csum_offset;
        };
    };
    __u32            priority;
    __u8            local_df:1,
                cloned:1,
                ip_summed:2,
                nohdr:1,
                nfctinfo:3;
    __u8            pkt_type:3,
                fclone:2,
                ipvs_property:1,
                peeked:1,
                nf_trace:1;
    __be16            protocol;

    void            (*destructor)(struct sk_buff *skb);
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
    struct nf_conntrack    *nfct;
    struct sk_buff        *nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
    struct nf_bridge_info    *nf_bridge;
#endif

    int            iif;
    __u16            queue_mapping;
#ifdef CONFIG_NET_SCHED
    __u16            tc_index;    /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
    __u16            tc_verd;    /* traffic control verdict */
#endif
#endif
#ifdef CONFIG_IPV6_NDISC_NODETYPE
    __u8            ndisc_nodetype:2;
#endif
#if defined(CONFIG_MAC80211) || defined(CONFIG_MAC80211_MODULE)
    __u8            do_not_encrypt:1;
    __u8            requeue:1;
#endif
    /* 0/13/14 bit hole */

#ifdef CONFIG_NET_DMA
    dma_cookie_t        dma_cookie;
#endif
#ifdef CONFIG_NETWORK_SECMARK
    __u32            secmark;
#endif

    __u32            mark;

    __u16            vlan_tci;

    sk_buff_data_t        transport_header;//传输层帧头
    sk_buff_data_t        network_header;//网络层帧头
    sk_buff_data_t        mac_header;//数据链路层帧头
    /* These elements must be at the end, see alloc_skb() for details. */
    sk_buff_data_t        tail;//下边的tail、end、head、data是与缓冲区相关
    sk_buff_data_t        end;
    unsigned char        *head,*data;
    unsigned int        truesize;
    atomic_t        users;
};

sk_buff中的数据缓冲区的指针
Linux必须分配用于容纳数据包的缓冲区,sk_buff中定义了4个指向这片缓冲区的不同位置的指针head、data、tail、end。
head:指针指向内存中已分配的用于存储网路数据的缓冲区起始地址,sk_buff和相关数据块在分配后,该指针的值就固定了。
data:指针指向对应当前协议层有效数据的起始地址。每个协议的有效数据含义不同。
tail:指向对应当前协议层有效数据负载的结尾地址,与data对应。
end:指向内存分配的数据缓冲区的结尾地址,与head指针对应。和head一样,sk_buff和相关数据块被分配后,end指针也就固定了。如图:


套接字缓冲区sk_buff相关操作:
分配空间:
 struct sk_buff *dev_alloc_skb(unsigned len)
释放空间:
  dev_kfree_skb(struct sk_buff *skb)
  dev_kfree_skb_irq(struct sk_buff *skb)
  dev_kfree_skb_any(struct sk_buff *skb
)
put操作:
unsigned char *skb_put(struct sk_buff *skb, unsigned int len);
unsigned char *__skb_put(struct sk_buff *skb, unsigned int len);

作用:tail指针下移len长度,并增加sk_buff中len的值,返回改变后的tail值。主要用于在尾部追加数据。
push操作:
unsigned char *skb_push(struct sk_buff *skb, unsigned int len);
unsigned char *__skb_push(struct sk_buff *skb, unsigned int len);
作用:将data指针上移,同时增加sk_buff中的len。主要用于在数据包发送时添加头部。函数带__和不带__的区别在于:带__的会检测放入缓冲区的数据,后则不会。
pull操作:
 unsigned char *skb_pull(struct sk_buff *skb, unsigned int len);
作用:将data指针下移,并减少sk_buff中的len值。这个操作一般用于下层协议向上层协议移交数据包,使data指针指向上一层协议的协议头
reserve操作:
 void skb_reserve(struct sk_buff *skb, unsigned int len);
作用:将data和tail指针同时下移,这个操作主要用于在存储空间的头部预留len长度的空隙。

2.网络设备接口层

 我们知道,通常的驱动编写就是填充一个设备相关的结构体,网络设备也是这样,在2.6.29的内核中,需要填写结构体如下:

kernel2.6.29/include/linux/netdevice.h

struct net_device_ops {
    int            (*ndo_init)(struct net_device *dev);
    void            (*ndo_uninit)(struct net_device *dev);
    int            (*ndo_open)(struct net_device *dev);
    int            (*ndo_stop)(struct net_device *dev);
    int            (*ndo_start_xmit) (struct sk_buff *skb,
                         struct net_device *dev);
    u16            (*ndo_select_queue)(struct net_device *dev,
                         struct sk_buff *skb);
#define HAVE_CHANGE_RX_FLAGS
    void            (*ndo_change_rx_flags)(struct net_device *dev,
                         int flags);
#define HAVE_SET_RX_MODE
    void            (*ndo_set_rx_mode)(struct net_device *dev);
#define HAVE_MULTICAST
    void            (*ndo_set_multicast_list)(struct net_device *dev);
#define HAVE_SET_MAC_ADDR
    int            (*ndo_set_mac_address)(struct net_device *dev,
                         void *addr);
#define HAVE_VALIDATE_ADDR
    int            (*ndo_validate_addr)(struct net_device *dev);
#define HAVE_PRIVATE_IOCTL
    int            (*ndo_do_ioctl)(struct net_device *dev,
                     struct ifreq *ifr, int cmd);
#define HAVE_SET_CONFIG
    int            (*ndo_set_config)(struct net_device *dev,
                     struct ifmap *map);
#define HAVE_CHANGE_MTU
    int            (*ndo_change_mtu)(struct net_device *dev,
                         int new_mtu);
    int            (*ndo_neigh_setup)(struct net_device *dev,
                         struct neigh_parms *);
#define HAVE_TX_TIMEOUT
    void            (*ndo_tx_timeout) (struct net_device *dev);

    struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);

    void            (*ndo_vlan_rx_register)(struct net_device *dev,
                         struct vlan_group *grp);
    void            (*ndo_vlan_rx_add_vid)(struct net_device *dev,
                         unsigned short vid);
    void            (*ndo_vlan_rx_kill_vid)(struct net_device *dev,
                         unsigned short vid);
#ifdef CONFIG_NET_POLL_CONTROLLER
#define HAVE_NETDEV_POLL
    void (*ndo_poll_controller)(struct net_device *dev);
#endif
};

在我们实现这些函数中的一部分后,就实现了驱动的功能了,为千变万化的网络设备定义统一的、抽象的数据结构net_device结构体,以不变应万 变,实现多种硬件在软件层次上的统一。net_device结构体在内核中指代一个网络设备,网络设备驱动只需填充其结构体就可以实现内核与具体硬件操作 函数的挂接。实际驱动的编写过程中,我们并不需要实现全部的函数,实际上,我们只要根据具体的需要实现上边的部分就可以了。

net_device结构体的相关成员

网络设备的名称:
char name[IFNAMESIZ]
设备初始化指针:
int (*init)(struct net_device *dev);

硬件信息:
unsigned long mem_end;
unsigned long mem_start;

二者分别定义了设备所用的共享内存 的起始和结束地址
unsigned long base_addr;网络设备的I/O基地址
unsigned char irq;设备使用的中断号
unsigned char if_port;多端口设备中端口选择
unsigned char dma;分配给设备的DMA通道

接口信息
unsigned short hard_header_len;网络设备的硬件头长度,在以太网设备的初始化函数中,该成员被赋值为ETH_HLEN,即14
unsigned short type;接口的硬件类型
unsigned mtu;最大传输单元
unsigned char dev_addr[MAX_ADDR_LEN];
unsigned char broadcase[MAX_ADDR_LEN];
二者分别用于存放设备的硬件地址和广播地址,以太网设备的广播地址为6个0xFF
unsigned short flags;网络接口标志


设备操作函数
打开、关闭
int (*open)(struct net_device *dev);
int (*stop)(struct net_device *dev);
启动数据包发送
int (*hard_start_xmit)(struct sk_buff *skb, struct net_device *dev);
获得网络设备状态
struct net_device_status * (*get_status)(struct net_device *dev);
设备I/O控制
int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
配置接口,I/O地址,中断号
int (*set_config)(struct net_device *dev, struct ifmap *map);
设置MAC地址
int (*set_mac_address)(struct net_device *dev, void *addr);

辅助成员
unsigned long trans_start;
unsigned long last_rx;

二者分别为:最后一次开始发送的时间戳、最后一次接收到数据包的时间戳,这两个时间戳记录都是jiffies,驱动程序应维护这两个成员。
void *priv;私有信息指针
spinlock_t xmit_lock;
int xmit_lock_owner;

xmit_lock是避免hard_start_xmit函数同时多次调用的自旋锁。xmit_lock_owner指向拥有此自旋锁的CPU的编号。
阅读(1634) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~