1 中断函数
static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance)
所有有网卡产生的中断都会引起该中断函数的调用,这是在
static int rtl8139_open (struct net_device *dev)
{
struct rtl8139_private *tp = netdev_priv(dev);
int retval;
void __iomem *ioaddr = tp->mmio_addr;
/* 注册中断处理函数 ,中断号为共享 */
retval = request_irq (dev->irq, rtl8139_interrupt, IRQF_SHARED, dev->name, dev);
完成的。
中断函数处理的事件可以大致分为几类:
A 数据包到达产生的中断(RxAckBits = RxFIFOOver | RxOverflow | RxOK);
B 异常事件,通常都是出错的情况(RxAckBits = RxFIFOOver | RxOverflow | RxOK)
C发送完成事件(TxOK | TxErr)
下面我们看看具体的代码
static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance)
{
/* 参数dev_instance是在上面注册中断处理函数的时候传入的 */
struct net_device *dev = (struct net_device *) dev_instance;
/* tp 为网卡驱动自定义的驱动特有的数据,和dev一起分配的 */
struct rtl8139_private *tp = netdev_priv(dev);
void __iomem *ioaddr = tp->mmio_addr;
u16 status, ackstat;
int link_changed = 0; /* avoid bogus "uninit" warning */
int handled = 0;
/* 对驱动数据加锁*/
spin_lock (&tp->lock);
/*读中断状态寄存器,获取中断状态*/
status = RTL_R16 (IntrStatus);
/* 这时由共享此中断号的其它设备产生的中断 */
if (unlikely((status & rtl8139_intr_mask) == 0))
goto out;
handled = 1;
/* 硬件错误 */
if (unlikely(status == 0xFFFF))
goto out;
/* 设备已关闭*/
if (unlikely(!netif_running(dev))) {
/* 屏蔽所有中断*/
RTL_W16 (IntrMask, 0);
goto out;
}
/* Acknowledge all of the current interrupt sources ASAP, but
an first get an additional status bit from CSCR. */
if (unlikely(status & RxUnderrun))
link_changed = RTL_R16 (CSCR) & CSCR_LinkChangeBit;
ackstat = status & ~(RxAckBits | TxErr);
if (ackstat)
RTL_W16 (IntrStatus, ackstat);
下一步处理数据包到达事件
/* Receive packets are processed by poll routine.
If not running start it now. */
if (status & RxAckBits){
if (netif_rx_schedule_prep(dev, &tp->napi)) {
RTL_W16_F (IntrMask, rtl8139_norx_intr_mask);
__netif_rx_schedule(dev, &tp->napi);
}
}
先强调下8139网卡驱动的数据接收方式采用的是Linux内核的NAPI新机制。
NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后 POLL 的方法来轮询数据。随着网络的接收速度的增加,NIC 触发的中断能做到不断减少。
使用 NAPI 先决条件:
驱动可以继续使用老的 2.4 内核的网络驱动程序接口,NAPI 的加入并不会导致向前兼容性的丧失,但是 NAPI 的使用至少要得到下面的保证:
1. 要使用 DMA 的环形输入队列(也就是 ring_dma,这个在 2.4 驱动中关于 Ethernet 的部分有详细的介绍),或者是有足够的内存空间缓存驱动获得的包。
2. 在发送/接收数据包产生中断的时候有能力关断 NIC 中断的事件处理,并且在关断 NIC 以后,并不影响数据包接收到网络设备的环形缓冲区(以下简称 rx-ring)处理队列中。 (这就是锁NAPI机制并不是所有的网卡都能支持的,只有在关闭中断的情况下,任然能通过DMA接收数据包的网卡才可以)
NAPI 对数据包到达的事件的处理采用轮询方法,在数据包达到的时候,NAPI 就会强制执行dev->poll 方法。而和不像以前的驱动那样为了减少包到达时间的处理延迟,通常采用中断的方法来进行。
--------------------------------------------------------------------------------
如果定义了采用NAPI模式接收数据包,则进入这个调用点。首先调用netif_rx_schedule_prep(dev),确定设备处于运行,而且设备还没有被添加到网络层的 POLL 处理队列中,在调用 netif_rx_schedule之前会调用这个函数。接下来调用__netif_rx_schedule(dev),将设备的 POLL 方法添加到网络层次的 POLL 处理队列中去,排队并且准备接收数据包,在使用之前需要调用 netif_rx_reschedule_prep,并且返回的数为 1,并且触发一个 NET_RX_SOFTIRQ 的软中断通知网络层接收数据包。处理完成。
下面介绍一下__netif_rx_schedule(netdev)函数的作用:
static inline void __netif_rx_schedule(struct net_device *dev)
{
unsigned long flags;
/* 获取当前CPU。 */
int cpu = smp_processor_id();
local_irq_save(flags);
dev_hold(dev);
/*将当前设备加入CPU相关全局队列softnet_data的轮询设备列表中,不过值得注意的是,这个列表中的设备不一定都执行轮询接收数据包,这里的poll_list只是表示当前设备需要接收数据,具体采用中断还是轮询的方式,取决于设备提供的poll方法。*/
list_add_tail(&dev->poll_list, &softnet_data[cpu].poll_list);
/*
调用函数产生网络接收软中断。也就是系统将运行net_rx_action()处理网络数据。
*/
__cpu_raise_softirq(cpu, NET_RX_SOFTIRQ);
local_irq_restore(flags);
}
接下来是多出错事件以及数据包发送完成事件的处理
/* Check uncommon events with one test. */
if (unlikely(status & (PCIErr | PCSTimeout | RxUnderrun | RxErr)))
rtl8139_weird_interrupt (dev, tp, ioaddr,
status, link_changed);
if (status & (TxOK | TxErr)) {
rtl8139_tx_interrupt (dev, tp, ioaddr);
if (status & TxErr)
RTL_W16 (IntrStatus, TxErr);
}
出错事件的处理较为简单,只是对一些出错计数的处理。下面我们看看发送完成事件的处理
2 发送完成事件处理
static void rtl8139_tx_interrupt (struct net_device *dev,
struct rtl8139_private *tp,
void __iomem *ioaddr)
{
unsigned long dirty_tx, tx_left;
assert (dev != NULL);
assert (ioaddr != NULL);
/*dirty_tx是最近发送数据包时,没有经中断处理的最早数据包所对应的
发送描述符*/
dirty_tx = tp->dirty_tx;
/* cur_tx是最近发送完成的最后一个数据包对应的发送描述符,所以在
此次中断中要处理的就是和dirty_tx之间的发送描述符*/
tx_left = tp->cur_tx - dirty_tx;
while (tx_left > 0) {
/* 环形缓冲区,最大为NUM_TX_DESC,取模得到真实值*/
int entry = dirty_tx % NUM_TX_DESC;
int txstatus;
/*当前发送描述符的发送状态(一个寄存器为32bit)*/
txstatus = RTL_R32 (TxStatus0 + (entry * sizeof (u32)));
/*还没有发送*/
if (!(txstatus & (TxStatOK | TxUnderrun | TxAborted)))
break; /* It still hasn't been Txed */
/* Note: TxCarrierLost is always asserted at 100mbps. */
if (txstatus & (TxOutOfWindow | TxAborted)) {
/* There was an major error, log it. */
if (netif_msg_tx_err(tp))
printk(KERN_DEBUG "%s: Transmit error, Tx status %8.8x./n",
dev->name, txstatus);
dev->stats.tx_errors++;
if (txstatus & TxAborted) {
dev->stats.tx_aborted_errors++;
RTL_W32 (TxConfig, TxClearAbt);
RTL_W16 (IntrStatus, TxErr);
wmb();
}
if (txstatus & TxCarrierLost)
dev->stats.tx_carrier_errors++;
if (txstatus & TxOutOfWindow)
dev->stats.tx_window_errors++;
} else {
if (txstatus & TxUnderrun) {
/* Add 64 to the Tx FIFO threshold. */
if (tp->tx_flag < 0x00300000)
tp->tx_flag += 0x00020000;
dev->stats.tx_fifo_errors++;
}
dev->stats.collisions += (txstatus >> 24) & 15;
dev->stats.tx_bytes += txstatus & 0x7ff;
dev->stats.tx_packets++;
}
dirty_tx++;
tx_left--;
}
#ifndef RTL8139_NDEBUG
if (tp->cur_tx - dirty_tx > NUM_TX_DESC) {
printk (KERN_ERR "%s: Out-of-sync dirty pointer, %ld vs. %ld./n",
dev->name, dirty_tx, tp->cur_tx);
dirty_tx += NUM_TX_DESC;
}
#endif /* RTL8139_NDEBUG */
/* only wake the queue if we did work, and the queue is stopped */
if (tp->dirty_tx != dirty_tx) {
tp->dirty_tx = dirty_tx;
mb();
netif_wake_queue (dev);
}
}
3 软中断处理函数
由于在前面的中断处理程序中调用了__cpu_raise_softirq(cpu, NET_RX_SOFTIRQ),所以CPU会在中断处理完成后的适当的时候调用软中断处理函数,也就是我们在系统初始化的过程中注册的net_rx_action函数。
static void net_rx_action(struct softirq_action *h)
{
/*获取每个CPU的softnet_data结构,然后取得其poll_list */
struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
unsigned long start_time = jiffies;
int budget = netdev_budget;
void *have;
local_irq_disable();
/* 处理poll_list上关联的每一个设备*/
while (!list_empty(list)) {
struct napi_struct *n;
int work, weight;
/* If softirq window is exhuasted then punt.
*
* Note that this is a slight policy change from the
* previous NAPI code, which would allow up to 2
* jiffies to pass before breaking out. The test
* used to be "jiffies - start_time > 1".
*/
if (unlikely(budget <= 0 || jiffies != start_time))
goto softnet_break;
local_irq_enable();
/* Even though interrupts have been re-enabled, this
* access is safe because interrupts can only add new
* entries to the tail of this list, and only ->poll()
* calls can remove this head entry from the list.
*/
n = list_entry(list->next, struct napi_struct, poll_list);
have = netpoll_poll_lock(n);
weight = n->weight;
/* This NAPI_STATE_SCHED test is for avoiding a race
* with netpoll's poll_napi(). Only the entity which
* obtains the lock and sees NAPI_STATE_SCHED set will
* actually make the ->poll() call. Therefore we avoid
* accidently calling ->poll() when NAPI is not scheduled.
*/
work = 0;
/* 调用每个设备的pool方法接收数据*/
if (test_bit(NAPI_STATE_SCHED, &n->state))
work = n->poll(n, weight);
WARN_ON_ONCE(work > weight);
budget -= work;
local_irq_disable();
/* Drivers must not modify the NAPI state if they
* consume the entire weight. In such cases this code
* still "owns" the NAPI instance and therefore can
* move the instance around on the list at-will.
*/
if (unlikely(work == weight)) {
/* 设备运行出错,或自己退出poll_list,就删除它*/
if (unlikely(napi_disable_pending(n)))
__napi_complete(n);
else
/* 该设备还有要接收的数据没被处理,因为轮询算法
被移动到poll_llst尾部等待处理
*/
list_move_tail(&n->poll_list, list);
}
netpoll_poll_unlock(have);
}
out:
local_irq_enable();
#ifdef CONFIG_NET_DMA
/*
* There may not be any more sk_buffs coming right now, so push
* any pending DMA copies to hardware
*/
if (!cpus_empty(net_dma.channel_mask)) {
int chan_idx;
for_each_cpu_mask_nr(chan_idx, net_dma.channel_mask) {
struct dma_chan *chan = net_dma.channels[chan_idx];
if (chan)
dma_async_memcpy_issue_pending(chan);
}
}
#endif
return;
softnet_break:
__get_cpu_var(netdev_rx_stat).time_squeeze++;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;
}
通常,在网卡收发数据的时候,需要维护一个缓冲区队列,来缓存可能存在的突发数据,类似于前面的DMA环形缓冲区。
队列层中,包含了一个叫做struct softnet_data:
struct softnet_data
{
/*throttle 用于拥塞控制,当拥塞发生时,throttle将被设置,后续进入的数据包将被丢弃*/
int throttle;
/*netif_rx函数返回的拥塞级别*/
int cng_level;
int avg_blog;
/*softnet_data 结构包含一个指向接收和传输队列的指针,input_pkt_queue成员指向准备传送
给网络层的sk_buffs包链表的首部的指针,这个队列中的包是由netif_rx函数递交的*/
struct sk_buff_head input_pkt_queue;
struct list_head poll_list;
struct net_device *output_queue;
struct sk_buff *completion_queue;
struct net_device backlog_dev; /* Sorry. 8) */
};
内核使用了一个同名的变量softnet_data,它是一个Per-CPU变量,每个CPU都有一个。
--------------------------------------------------------------------------------
下一步进入设备的poll函数。需要注意的是,如果是NAPI的网卡驱动的话,poll函数是在驱动中注册的,驱动实现的;如果是非NAPI的话,就是内核定义的process_backlog函数,至于process_backlog是如何添加到poll_list中的,这里暂时不管,先看看8139驱动的poll 函数是如何实现的。
4 8139 poll函数实现
static int rtl8139_poll(struct napi_struct *napi, int budget)
{
struct rtl8139_private *tp = container_of(napi, struct rtl8139_private, napi);
struct net_device *dev = tp->dev;
void __iomem *ioaddr = tp->mmio_addr;
int work_done;
spin_lock(&tp->rx_lock);
work_done = 0;
/* 在 rtl8139_rx中将接送到的数据拷贝出来并传递给上层协议驱动。*/
if (likely(RTL_R16(IntrStatus) & RxAckBits))
work_done += rtl8139_rx(dev, tp, budget);
/*说明没有多余的数据到达,则恢复接收中断,并把此设备从poll_list中清除*/
if (work_done < budget) {
unsigned long flags;
/*
* Order is important since data can get interrupted
* again when we think we are done.先关中断,在写中断屏蔽位
*/
spin_lock_irqsave(&tp->lock, flags);
RTL_W16_F(IntrMask, rtl8139_intr_mask);
__netif_rx_complete(dev, napi);
spin_unlock_irqrestore(&tp->lock, flags);
}
spin_unlock(&tp->rx_lock);
return work_done;
}
从rtl8139_rx的代码也可以看出,当数据包接收出错或者是没有更多的数据包可以接收时,work_done才不会达到budget,这时,应该让网卡重新回到
中断的状态,以等待数据包的到来。另外一种情况就是work_done等于budget,很可能是因为还有数据包要接收,所以在net_rx_action函数中,只是
把该网卡设备移到队列的尾部,以期待在下次循环中再次调用其poll函数。
5 看看rtl8139_rx的实现
static int rtl8139_rx(struct net_device *dev, struct rtl8139_private *tp,
int budget)
{
void __iomem *ioaddr = tp->mmio_addr;
int received = 0;
/* 网卡不断的把数据放进环形接收缓冲区, CPU 读出来的时候,读到哪里的顺序需要 自 己维护,tp->cur_rx记录上次读到哪里,这里将接着从上次的地方拷贝。*/
unsigned char *rx_ring = tp->rx_ring;
unsigned int cur_rx = tp->cur_rx;
unsigned int rx_size = 0;
DPRINTK ("%s: In rtl8139_rx(), current %4.4x BufAddr %4.4x,"
" free to %4.4x, Cmd %2.2x./n", dev->name, (u16)cur_rx,
RTL_R16 (RxBufAddr),
RTL_R16 (RxBufPtr), RTL_R8 (ChipCmd));
/*轮询寄存器,当ChipCmd RxBufEmpty 位没被网卡设置的时
候,则说明环形缓冲区中有接收到的数据等待处理*/
while (netif_running(dev) && received < budget
&& (RTL_R8 (ChipCmd) & RxBufEmpty) == 0) {
u32 ring_offset = cur_rx % RX_BUF_LEN;
u32 rx_status;
unsigned int pkt_size;
struct sk_buff *skb;
rmb();
/* 获取接收状态以及接收数据的长度*/
/* read size+status of next frame from DMA ring buffer */
rx_status = le32_to_cpu (*(__le32 *) (rx_ring + ring_offset));
rx_size = rx_status >> 16;
/* 实际数据包的长度,减去4个字节的CRC*/
pkt_size = rx_size - 4;
if (netif_msg_rx_status(tp))
printk(KERN_DEBUG "%s: rtl8139_rx() status %4.4x, size %4.4x,"
" cur %4.4x./n", dev->name, rx_status,
rx_size, cur_rx);
#if RTL8139_DEBUG > 2
{
int i;
DPRINTK ("%s: Frame contents ", dev->name);
for (i = 0; i < 70; i++)
printk (" %2.2x",
rx_ring[ring_offset + i]);
printk ("./n");
}
#endif
/*当EarlyRX 允许的时候,可能会发生这种情况,一个完整的
数据包的一部分已经通过DMA 传送到了内存中,而另外一部
分还在网卡内部FIFO 中,网卡的DMA 操作还在进行中*/
/* Packet copy from FIFO still in progress.
* Theoretically, this should never happen
* since EarlyRx is disabled.
*/
if (unlikely(rx_size == 0xfff0)) {
if (!tp->fifo_copy_timeout)
tp->fifo_copy_timeout = jiffies + 2;
else if (time_after(jiffies, tp->fifo_copy_timeout)) {
DPRINTK ("%s: hung FIFO. Reset.", dev->name);
rx_size = 0;
goto no_early_rx;
}
if (netif_msg_intr(tp)) {
printk(KERN_DEBUG "%s: fifo copy in progress.",
dev->name);
}
tp->xstats.early_rx++;
break;
}
no_early_rx:
tp->fifo_copy_timeout = 0;
/* If Rx err or invalid rx_size/rx_status received
* (which happens if we get lost in the ring),
* Rx process gets reset, so we abort any further
* Rx processing.
*/
if (unlikely((rx_size > (MAX_ETH_FRAME_SIZE+4)) ||
(rx_size < 8) ||
(!(rx_status & RxStatusOK)))) {
rtl8139_rx_err (rx_status, dev, tp, ioaddr);
received = -1;
goto out;
}
/* Malloc up new buffer, compatible with net-2e. */
/* Omit the four octet CRC from the length. */
skb = netdev_alloc_skb(dev, pkt_size + NET_IP_ALIGN);
if (likely(skb)) {
skb_reserve (skb, NET_IP_ALIGN); /* 16 byte align the IP fields. */
#if RX_BUF_IDX == 3
wrap_copy(skb, rx_ring, ring_offset+4, pkt_size);
#else
skb_copy_to_linear_data (skb, &rx_ring[ring_offset + 4], pkt_size);
#endif
skb_put (skb, pkt_size);
skb->protocol = eth_type_trans (skb, dev);
dev->last_rx = jiffies;
dev->stats.rx_bytes += pkt_size;
dev->stats.rx_packets++;
数据包从这里进入上层
netif_receive_skb (skb);
} else {
if (net_ratelimit())
printk (KERN_WARNING
"%s: Memory squeeze, dropping packet./n",
dev->name);
dev->stats.rx_dropped++;
}
received++;
/* 前一个4是头部的状态和长度的4个字节,后面的3是为了对齐*/
cur_rx = (cur_rx + rx_size + 4 + 3) & ~3;
RTL_W16 (RxBufPtr, (u16) (cur_rx - 16));
/*清除中断状态位*/
rtl8139_isr_ack(tp);
}
if (unlikely(!received || rx_size == 0xfff0))
rtl8139_isr_ack(tp);
#if RTL8139_DEBUG > 1
DPRINTK ("%s: Done rtl8139_rx(), current %4.4x BufAddr %4.4x,"
" free to %4.4x, Cmd %2.2x./n", dev->name, cur_rx,
RTL_R16 (RxBufAddr),
RTL_R16 (RxBufPtr), RTL_R8 (ChipCmd));
#endif
tp->cur_rx = cur_rx;
/*
* The receive buffer should be mostly empty.
* Tell NAPI to reenable the Rx irq.
*/
if (tp->fifo_copy_timeout)
received = budget;
out:
return received;
}
rtl8139_rx把数据从网卡接收缓存中拷贝出来。数据在环形缓冲区的存放格式如下:| 长度| 状态位| 内容| 长度| 状态位| 内容| ...
长度和状态位一共4个字节
6 最后就是netif_receive_skb了,数据包从此离开链路层,向上层遨游
/**
* netif_receive_skb - process receive buffer from network
* @skb: buffer to process
*
* netif_receive_skb() is the main receive data processing function.
* It always succeeds. The buffer may be dropped during processing
* for congestion control or by the protocol layers.
*
* This function may only be called from softirq context and interrupts
* should be enabled.
*
* Return values (usually ignored):
* NET_RX_SUCCESS: no congestion
* NET_RX_DROP: packet was dropped
*/
int netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
struct net_device *orig_dev;
struct net_device *null_or_orig;
int ret = NET_RX_DROP;
__be16 type;
if (skb->vlan_tci && vlan_hwaccel_do_receive(skb))
return NET_RX_SUCCESS;
/* if we've gotten here through NAPI, check netpoll */
if (netpoll_receive_skb(skb))
return NET_RX_DROP;
if (!skb->tstamp.tv64)
net_timestamp(skb);
if (!skb->iif)
skb->iif = skb->dev->ifindex;
null_or_orig = NULL;
orig_dev = skb->dev;
if (orig_dev->master) {
if (skb_bond_should_drop(skb))
null_or_orig = orig_dev; /* deliver only exact match */
else
skb->dev = orig_dev->master;
}
__get_cpu_var(netdev_rx_stat).total++;
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
skb->mac_len = skb->network_header - skb->mac_header;
pt_prev = NULL;
rcu_read_lock();
/* Don't receive packets in an exiting network namespace */
if (!net_alive(dev_net(skb->dev)))
goto out;
#ifdef CONFIG_NET_CLS_ACT
if (skb->tc_verd & TC_NCLS) {
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
goto ncls;
}
#endif
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||
ptype->dev == orig_dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
#ifdef CONFIG_NET_CLS_ACT
skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
ncls:
#endif
skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
type = skb->protocol;
list_for_each_entry_rcu(ptype,
&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
if (ptype->type == type &&
(ptype->dev == null_or_orig || ptype->dev == skb->dev ||
ptype->dev == orig_dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
if (pt_prev) {
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
} else {
kfree_skb(skb);
/* Jamal, now you will not able to escape explaining
* me how you were going to use this. :-)
*/
ret = NET_RX_DROP;
}
out:
rcu_read_unlock();
return ret;
5),netif_receive_skb(skb)
这是一个辅助函数,用于在poll中处理接收到的帧。它主要是向各个已注册的协议处理例程发送一个SKB。
每个协议的类型由一个packet_type结构表示:
struct packet_type {
__be16 type;/* This is really htons(ether_type). */
struct net_device *dev; /* NULL is wildcarded here */
int (*func) (struct sk_buff *,
struct net_device *,
struct packet_type *,
struct net_device *);
struct sk_buff *(*gso_segment)(struct sk_buff *skb,
int features);
int (*gso_send_check)(struct sk_buff *skb);
void *af_packet_priv;
struct list_head list;
};
它的主要域为:type, 为要处理的协议
func, 为处理这个协议的例程
所用到的协议在系统或模块加载的时候初始化,如IP协议:
static struct packet_type ip_packet_type = {
.type = __constant_htons(ETH_P_IP),
.func = ip_rcv,
.gso_send_check = inet_gso_send_check,
.gso_segment = inet_gso_segment,
};
static int __init inet_init(void)
{
……
dev_add_pack(&ip_packet_type);
……
}
void dev_add_pack(struct packet_type *pt)
{
int hash;
spin_lock_bh(&ptype_lock);
if (pt->type == htons(ETH_P_ALL)) {
netdev_nit++;
list_add_rcu(&pt->list, &ptype_all);
} else {
hash = ntohs(pt->type) & 15;
list_add_rcu(&pt->list, &ptype_base[hash]);
}
spin_unlock_bh(&ptype_lock);
}
可以看到,dev_add_pack()是将一个协议类型结构链入某一个链表, 当协议类型为
ETH_P_ALL时,它将被链入ptype_all链表,这个链表是用于sniffer这样一些程序的,它接收所有NIC收到的包。还有一个是HASH链表ptype_base,用于各种协议,它是一个16个元素的数组,dev_add_pack()会根据协议类型将这个packet_type链入相应的HASH链表中。
而ptype_base与ptype_all的组织结构如下,一个为HASH链表,一个为双向链表:
int netif_receive_skb(struct sk_buff *skb)
{
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
type = skb->protocol;
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
if (ptype->type == type &&
(!ptype->dev || ptype->dev == skb->dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
if (pt_prev) {
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
return ret;
}
netif_receive_skb()的主要作用体现在两个遍历链表的操作中,其中之一为遍历ptype_all链,这些为注册到内核的一些sniffer,将上传给这些sniffer,另一个就是遍历ptype_base,这个就是具体的协议类型。假高如上图如示,当eth1接收到一个IP数据包时,它首先分别发送一份副本给两个ptype_all链表中的packet_type,它们都由package_rcv处理,然后再根据HASH值,在遍历另一个HASH表时,发送一份给类型为ETH_P_IP的类型,它由ip_rcv处理。如果这个链中还注册有其它IP层的协议,它也会同时发送一个副本给它。
其中,这个是由deliver_skb(skb, pt_prev, orig_dev)去完成的:
static __inline__ int deliver_skb(struct sk_buff *skb,
struct packet_type *pt_prev,
struct net_device *orig_dev)
{
atomic_inc(&skb->users);
return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
可以看到,它只是一个包装函数,它只去执行相应packet_type里的func处理函数,如对于ETH_P_IP类型,由上面可以看到,它执行的就是ip_rcv了。
至此,一个以太网帧的链路层接收过程就全部完成,再下去就是网络层的处理了。