• 数据包接收系列 — 上半部实现(网卡驱动)


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

    内核版本: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);
    }

     

  • 相关阅读:
    PHP Mail 简介
    二级叉的结算另一种方法
    PHP extract() 函数
    array_count_values函数
    对碰结算的方法
    array_walk() 函数
    函数引发二级叉的结算
    array_intersect() php筛选两个数组共有的元素
    php文件锁
    JAVA Math常用方法
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333296.html
Copyright © 2020-2023  润新知