数据包接收系列 — 上半部实现(网卡驱动)
来源:互联网 发布:电子数据调查分析师 编辑:程序博客网 时间:2024/05/25 21:32
本文主要内容:网络数据包接收的上半部实现,主要分析网卡驱动相关部分。
内核版本: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);
- 数据包接收系列 — 上半部实现(网卡驱动)
- 数据包接收系列 — 上半部实现(网卡驱动)
- 数据包接收系列 — 上半部实现(内核接口)
- 数据包接收系列 — 上半部实现(内核接口)
- 网卡驱动的数据包发送接收
- 数据包接收系列 — 下半部实现(软中断)
- 数据包接收系列 — 下半部实现(软中断)
- 数据包接收系列 — 下半部实现(软中断)
- 学习Linux-4.12内核网路协议栈(2.2)——接口层数据包的接收(上半部)
- 网卡如何接收数据包
- 网卡如何接收数据包
- 网卡驱动和队列层中的数据包接收
- 网卡驱动和队列层中的数据包接收
- 网卡驱动和队列层中的数据包接收
- 数据包接收系列 — NAPI的原理和实现
- 数据包接收系列 — NAPI的原理和实现
- 数据包接收系列 — NAPI的原理和实现
- 数据包接收系列 — NAPI的原理和实现
- linux项目发布常见命令
- Android.mk文件语法规范
- Android 自定义view实现水波纹效果
- WindowManagerService动画分析
- HDU5246 超级赛亚ACMer 排序+贪心+二分
- 数据包接收系列 — 上半部实现(网卡驱动)
- JAVA-反射-getGenericSuperclass()介绍
- 云服务列表
- 管理虚拟网络服务: 实践出真知
- what to do for the future?
- OpenGL 学习 显示列表
- 硬中断和软中断
- 用CornerStone配置SVN,HTTP及svn简单使用说明
- Linux启动ssh服务