zoukankan      html  css  js  c++  java
  • 数据包接收系列 — 上半部实现(网卡驱动)

    本文主要内容:网络数据包接收的上半部实现,主要分析网卡驱动相关部分。

    内核版本:2.6.37

    Author:zhangskd @ csdn blog

    网卡概述

    (1) 网卡收包

    网线上的物理帧首先被网卡芯片获取,网卡芯片会检查物理帧的CRC,保证完整性。

    然后网卡芯片将物理帧头去掉,得到MAC包。

    网卡芯片会检查MAC包内的目的MAC地址,如果和本网卡的MAC地址不一样则丢弃(混杂模式除外)。

    之后网卡芯片将MAC帧拷贝到网卡内部的缓冲区,触发硬中断。

    网卡的驱动程序通过硬中断处理函数,构建sk_buff,把它拷贝到内存中,接下来交给内核处理。

    在这个过程中,网卡芯片对物理帧进行了MAC匹配过滤,以减小系统负荷。

    (2) 网卡发包

    网卡驱动程序将IP包添加14字节的MAC头,构成MAC包。

    MAC包中含有发送端和接收端的MAC地址,由于是驱动程序创建MAC头,所以可以随便输入地址

    进行主机伪装。

    驱动程序将MAC包拷贝到网卡芯片内部的缓冲区,接下来由网卡芯片处理。

    网卡芯片将MAC包再次封装为物理帧,添加头部同步信息和CRC校验,然后丢到网线上,就完成

    一个IP报的发送了,所有接到网线上的网卡都可以看到该物理帧。

    (3) 网卡数据结构

    网卡用net_device来表示,用register_netdevice()注册到系统中,注册过的网卡可以通过

    unregister_netdevice()注销掉。

    struct net_device {
        char name[IFNAME]; /* 网卡名称,如eth0 */
        ...
        unsigned long mem_end; /* shared mem end,共享内存结束地址 */
        unsigned long mem_start; /* shared mem start ,共享内存起始地址 */
        unsigned long base_addr; /* device I/O address,设备内存映射到I/O内存的起始地址 */
        unsigned int irq; /* device IRQ number,硬中断编号 */
        ...
        unsigned long state; /* 网卡的状态 */
        ...
        struct list_head dev_list;
        struct list_head napi_list; /* NAPI使用 */
        ...
        unsigned long features; /* 网卡功能标识 */
        ...
        int ifindex; /* Interface index. Unique device identifier. 设备ID */
        ...
        struct net_device_stats stats; /* 统计变量 */
        /* dropped packets by core network.
         * Do not use this in drivers.
         * 被内核丢弃的数据包个数。
         */
        atomic_long_t rx_dropped;
        ...
        /* Management operations */
        const struct net_device_ops *netdev_ops; /* 网卡的操作函数集 */
        const struct ethtool_ops *ethtool_ops; /* 配置网卡 */
        ...
        unsigned int promiscuity; /* 混杂模式计数器 */
        ...
        struct netdev_queue *ingress_queue;
        struct netdev_queue *_tx;
        ...
    };
    
    /* 网卡的操作函数集 */
    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);
        /* Called when a packet needs to be transmitted */
        netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev);
        ...
    };
    

     

    (4) 网卡中断处理函数

    产生中断的每个设备都有一个相应的中断处理程序,是设备驱动程序的一部分。

    每个网卡都有一个中断处理程序,用于通知网卡该中断已经被接收了,以及把网卡缓冲区的

    数据包拷贝到内存中。

    当网卡接收来自网络的数据包时,需要通知内核数据包到了。网卡立即发出中断:嗨,内核,

    我这里有最新的数据包了。内核通过执行网卡已注册的中断处理函数来做出应答。

    中断处理程序开始执行,通知硬件,拷贝最新的网络数据包到内存,然后读取网卡更多的数据包。

    这些都是重要、紧迫而又与硬件相关的工作。内核通常需要快速的拷贝网络数据包到系统内存,

    因为网卡上接收网络数据包的缓存大小固定,而且相比系统内存也要小得多。所以上述拷贝动作

    一旦被延迟,必然造成网卡缓存溢出 - 进入的数据包占满了网卡的缓存,后续的包只能被丢弃。

    当网络数据包被拷贝到系统内存后,中断的任务算是完成了,这时它把控制权交还给被系统中断

    前运行的程序。处理和操作数据包的其他工作在随后的下半部中进行。

    上半部的实现

    接收数据包的上半部处理流程为:

    el_interrupt() // 网卡驱动

        |--> el_receive() // 网卡驱动

                    |--> netif_rx() // 内核接口

                               |--> enqueue_to_backlog() // 内核接口

    这里以3c501网卡驱动为例来进行分析(这是个古董级网卡,实现简单:)

    el_interrupt

    3c501的网卡中断处理函数为el_interrupt(),调用inb()来获取当前中断处理结果,如果是RX_GOOD,

    表明网卡成功接收了数据包,则调用el_receive()来进行接收处理。

    /**
     * el_interrupt:
     * @irq: Interrupt number
     * @dev_id: The 3c501 that burped
     */
    
    static irqreturn_t el_interrupt(int irq, void *dev_id)
    {
        struct net_device *dev = dev_id;
        struct net_local *lp;
        int ioaddr;
        int axsr; /* Aux. status reg. */
    
        ioaddr = dev->base_addr; /* I/O映射地址 */
        lp = netdev_priv(dev); /* 网卡的私有数据 */
        spin_lock(&lp->lock); /* 上锁 */
    
        /* What happened? */
        axsr = inb(AX_STATUS);
    
        /* log it */
        if (el_debug > 3)
            pr_debug("%s: el_interrupt() aux = %#02x
    ", dev->name, axsr);
        if (lp->loading == 1 && ! lp->txing)
            pr_warning("%s: Inconsistent state loading while not in tx
    ", dev->name);
    
        if (lp->txing) { /* 处于发送模式,这里不研究 */
            /* Board in transmit mode. */
            ...
    
        } else {
            /* In receive mode. 处于接收模式 */
            int rxsr = inb(RX_STATUS); /* 获取中断处理的结果 */
            if (el_debug > 5)
                pr_debug("%s: rxsr=%02x txsr=%02x rp=%04x
    ", dev->name, rxsr,
                    inb(TX_STATUS), inw(RX_LOW));
    
            /* Just reading rx_status fixes most errors. */
            if (rxsr & RX_MISSED) /* 没有接收到数据包 */
                dev->stats.rx_missed_errors++;
            else if (rxsr & RX_RUNT) { /* 数据包长度错误 */
                /* Handled to avoid board lock-up. */
                dev->stats.rx_length_errors++;
                if (el_debug > 5)
                    pr_debug("%s: runt.
    ", dev->name);
    
            } else if (rxsr & RX_GOOD) {
                /* Receive worked, 成功接收数据包 */
                el_receive(dev); /* 接收函数 */
    
            } else {
                /* Nothing? Something is broken! */
                if (el_debug > 2)
                    pr_debug("%s: No packet seen, rxsr=%02x **resetting 3c501***
    ", dev->name, rxsr);
    
                el_reset(dev); /* 网卡出错,重置 */
            }
        }
    
        /* Move into receive mode */
        outb(AX_RX, AX_CMD);
        outw(0x00, RX_BUF_CLR);
        inb(RX_STATUS);
        inb(TX_STATUS);
        spin_unlock(&lp->lock);
    out:
        return IRQ_HANDLED;
    }
    

    网卡的私有数据

    /* Board-specific info in netdev_priv(dev). */
    struct net_local {
        int tx_pkt_start; /* The length of the current Tx packet. */
        int collisions; /* Tx collisions this packet */
        int loading; /* Spot buffer load collisions */
        int txing; /* True if card is in TX mode */
        spinlock_t lock; /* Serializing lock */
    };
    
    /**
     * netdev_priv - access network device private data
     * @dev: network device
     * Get network device private data
     */
    static inline void *netdev_priv(const struct net_device *dev)
    {
        return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);
    }

    el_receive

    el_receive()首先申请一个sk_buff,然后把网卡缓冲区中的数据包拷贝到skb->data。

    调用eth_type_trans()来判断数据包使用的三层协议(skb->protocol)、数据包的类型(skb->pkt_type)。

    最后调用内核入口函数 — netif_rx()继续处理。

    /**
     * el_receive:
     * @dev: Device to pull the packets from
     *
     * We have a good packet. Well, not really "good", just mostly not broken.
     * We must check everything to see if it is good. In particular we occasionally
     * get wild packet sizes from the card. If the packet seems sane we PIO it off
     * the card and queue it for the protocol layers.
     */ 
    
    static void el_receive(struct net_device *dev)
    {
        int ioaddr = dev->base_addr;
        int pkt_len;
        struct sk_buff *skb;
    
        pkt_len = inw(RX_LOW);
        if (el_debug > 4)
            pr_debug("el_receive %d.
    ", pkt_len);
    
        if (pkt_len < 60 || pkt_len > 1536) { /* 数据包长度错误 */
            if (el_debug)
                pr_debug("%s: bogus packet, length=%d
    ", dev->name, pkt_len);
            dev->stats.rx_over_errors++;
            return;
        }
    
        /* Command mode so we can empty the buffer */
        outb(AX_SYS, AX_CMD);
    
        skb = dev_alloc_skb(pkt_len + 2); /* 申请一个skb,2字节用于对齐IP */
    
        /* Start of frame */
        outw(0x00, GP_LOW);
    
        if (skb == NULL) {
            pr_info("%s: Memory squeeze, dropping packet.
    ", dev->name);
            dev->stats.rx_dropped++;
            return;
    
        } else {
            /* 因为mac头是14字节,预留2字节可使IP以16字节对齐 */
            skb_reserve(skb, 2); 
    
            /* 扩展skb的data room,并把网卡缓存的数据包拷贝到skb->data中 */
            insb(DATAPORT, skb_put(skb, pkt_len), pkt_len);
    
            /* 使用哪种三层协议,一般是IP协议 */
            skb->protocol = eth_type_trans(skb, dev);
    
            netif_rx(skb); /* 旧的接收处理函数 */
    
            dev->stats.rx_packets++;
            dev->stats.rx_bytes += pkt_len;
        }
    
    }
    

    以太网协议头

    #define ETH_ALEN 6 /* Octets in one ethernet addr */
    #define ETH_HLEN 14 /* Total octects in header */
    
    struct ethhdr {
        unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
        unsigned char h_source[ETH_ALEN]; /* source ether addr */
        unsigned short h_proto; /* packet type ID field */
    };
    

    eth_type_trans()进行二层协议的简单处理,主要是判断数据包的类型(skb->pkt_type)

    是单播/广播/多播,返回三层协议类型(skb->protocol),一般是IP协议。

    /**
     * eth_type_trans - determine the packet's protocol ID.
     * @skb: received socket data
     * @dev: receiving network device
     * The rule here is that we assume 802.3 if the type field is short enough to be a length.
     * This is normal practice and works for any now in use protocol.
     */
    
    __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)
    {
        struct ethhdr *eth;
        skb->dev = dev; /* 记录接收网卡设备 */
        skb_reset_mac_header(skb); /* 赋值skb->mac_header */
    
        /* skb->data指向三层协议头,skb->len -= 14 */
        skb_pull_inline(skb, ETH_HLEN);
        eth = eth_hdr(skb);
    
        /* 记录数据包类型skb->pkt_type */  
        if (unlikely(is_multicast_ether_addr(eth->h_dest))) { /* 是否为多播 */
            if (! compare_ether_addr_64bits(eth->h_dest, dev->broadcast)) /* 是否为广播 */
                skb->pkt_type = PACKET_BROADCAST;
            else
                skb->pkt_type = PACKET_MULTICAST;
    
        } else if (unlikely(compare_ether_addr_64bits(eth->h_dest, dev->dev_addr)))
            skb->pkt_type = PACKET_OTHERHOST; /* 其它网卡的 */
    
        /* 判断使用的三层协议 */
        if (netdev_uses_dsa_tags(dev))
            return htons(ETH_P_DSA);
        if (netdev_uses_trailer_tags(dev))
            return htons(ETH_P_TRAILER);
    
        if (ntohs(eth->h_proto) >= 1536) /* 大于1536即为协议ID */
            return eth->h_proto;
    
        if (skb->len >= 2 && *(unsigned short *)(skb->data) == 0xFFFF)
            return htons(ETH_P_802_3);
        return htons(ETH_P_802_2);
    }

     

  • 相关阅读:
    401. Binary Watch
    46. Permutations
    61. Rotate List
    142. Linked List Cycle II
    86. Partition List
    234. Palindrome Linked List
    19. Remove Nth Node From End of List
    141. Linked List Cycle
    524. Longest Word in Dictionary through Deleting
    android ListView详解
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333296.html
Copyright © 2011-2022 走看看