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