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

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

内核版本: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\n", dev->name, axsr);
    if (lp->loading == 1 && ! lp->txing)
        pr_warning("%s: Inconsistent state loading while not in tx\n", 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\n", 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.\n", 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***\n", 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.\n", pkt_len);

    if (pkt_len < 60 || pkt_len > 1536) { /* 数据包长度错误 */
        if (el_debug)
            pr_debug("%s: bogus packet, length=%d\n", 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.\n", 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);
}

 

 

转载于:https://www.cnblogs.com/aiwz/p/6333296.html

发布了0 篇原创文章 · 获赞 5 · 访问量 4万+
展开阅读全文
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览