本文主要内容:网络数据包接收的上半部实现,主要分析网卡驱动相关部分。
内核版本: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); }