网卡接收和发送数据包的过程

来源:互联网 发布:ubuntu 安装wps 编辑:程序博客网 时间:2024/05/02 10:52

描述
----
1) 当网卡接收到数据帧或发送完数据帧时, 就会产生一个中断.

2) 当网卡成功接收到数据帧时, 驱动程序根据帧长度分配包缓冲区, 将数据帧从网卡读入缓冲区,
然后
插入接收软中断的接收包队列, 并激活接收软中断. 当硬件中断返回时, 接收软中断将执行.
在缺省配置
下, 每个CPU最多可缓冲300个接收包. 当网卡接收包的速度太快, 接收软中断中无法及时处理,
接收包队
列长度达到300时, 系统进入"扼流(throttle)"状态, 所有后继的接收包被丢弃,
直到接收包队列重新变为空.

3) 数据包的发送可以设计成直接发送或环形发送方式. 在直接发送方式下,
数据帧直接写入网卡发送,
网卡的发送中断被忽略.

4) 在环形发送方式下, 发送包首先加入驱动程序的环形发送队列,
再通过发送中断来从发送环中取包发送.
已发送完成的包通过dev_kfree_skb_irq(skb)加入发送软中断的完成队列,
再激活发送软中断对其进行释
放. 设备状态的__LINK_STATE_XOFF标志可用来控制包的发送.
netif_stop_queue(dev)使系统暂停向驱动
程序请求发送包, netif_wake_queue(dev)可激活包的发送.
netif_schedule(dev)将设备dev加入发送软中
断的发送设备队列并激活发送软中断,
发送软中断执行qdisc_run(dev)来清空设备dev的发送包队列.

代码
----
; include/linux/netdevice.h:

static inline void netif_stop_queue(struct net_device *dev)
{
set_bit(__LINK_STATE_XOFF, &dev->state); 暂停发送
}
static inline int netif_queue_stopped(struct net_device *dev)
{
return test_bit(__LINK_STATE_XOFF, &dev->state); 发送是否已经暂停
}
static inline void netif_wake_queue(struct net_device *dev)
{
if (test_and_clear_bit(__LINK_STATE_XOFF, &dev->state)) 启动发送
__netif_schedule(dev); 将dev插入输出设备调度队列,激活发送软中断来运行qdisc_run()
}
static inline void __netif_schedule(struct net_device *dev)
{
if (!test_and_set_bit(__LINK_STATE_SCHED, &dev->state)) {
unsigned long flags;
int cpu = smp_processor_id();

local_irq_save(flags);
dev->next_sched = softnet_data[cpu].output_queue;
softnet_data[cpu].output_queue = dev;
__cpu_raise_softirq(cpu, NET_TX_SOFTIRQ);
local_irq_restore(flags);
}
}
static inline void dev_kfree_skb_irq(struct sk_buff *skb)
{ 在中断中将发送完成的包放入完成队列,等到在发送软中断中释放
if (atomic_dec_and_test(&skb->users)) {
int cpu =smp_processor_id();
unsigned long flags;

local_irq_save(flags);
skb->next = softnet_data[cpu].completion_queue;
softnet_data[cpu].completion_queue = skb;
__cpu_raise_softirq(cpu, NET_TX_SOFTIRQ);
local_irq_restore(flags);
}
}
static inline void qdisc_run(struct net_device *dev)
{
while (!netif_queue_stopped(dev) &&
qdisc_restart(dev)<0) 不断地向驱动程序发包,直到驱动程序关闭队列
/* NOTHING */;
}

; drivers/net/isa-skeleton.c:

struct net_local {
struct net_device_stats stats;
long open_time;/* Useless example local info. */

/* Tx control lock. This protects the transmit buffer ring
* state along with the "tx full" state of the driver. This
* means all netif_queue flow control actions are protected
* by this lock as well.
*/
spinlock_t lock;
};

static int net_send_packet(struct sk_buff *skb, struct net_device *dev)
{
struct net_local *np = (struct net_local *)dev->priv;
int ioaddr = dev->base_addr;
short length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; 最小包长(60字节)
unsigned char *buf = skb->data; 包的数据缓冲区

/* If some error occurs while trying to transmit this
* packet, you should return '1' from this function.
* In such a case you _may not_ do anything to the
* SKB, it is still owned by the network queueing
* layer when an error is returned. This means you
* may not modify any SKB fields, you may not free
* the SKB, etc.
*/

#if TX_RING
/* This is the most common case for modern hardware.
* The spinlock protects this code from the TX complete
* hardware interrupt handler. Queue flow control is
* thus managed under this lock as well.
*/
spin_lock_irq(&np->lock);

add_to_tx_ring(np, skb, length); 加入发送环
dev->trans_start = jiffies; 发送时间标记

/* If we just used up the very last entry in the
* TX ring on this device, tell the queueing
* layer to send no more.
*/
if (tx_full(dev))
netif_stop_queue(dev);

/* When the TX completion hw interrupt arrives, this
* is when the transmit statistics are updated.
*/

spin_unlock_irq(&np->lock);
#else
/* This is the case for older hardware which takes
* a single transmit buffer at a time, and it is
* just written to the device via PIO.
*
* No spin locking is needed since there is no TX complete
* event. If by chance your card does have a TX complete
* hardware IRQ then you may need to utilize np->lock here.
*/
hardware_send_packet(ioaddr, buf, length); 将数据写入网卡
np->stats.tx_bytes += skb->len;

dev->trans_start = jiffies;

/* You might need to clean up and record Tx statistics here. */
if (inw(ioaddr) == /*RU*/81)
np->stats.tx_aborted_errors++;
dev_kfree_skb (skb); 释放缓冲包
#endif

return 0;
}

static void net_interrupt(int irq, void *dev_id, struct pt_regs * regs)
{
struct net_device *dev = dev_id;
struct net_local *np;
int ioaddr, status;

ioaddr = dev->base_addr;

np = (struct net_local *)dev->priv; 网卡的私有数据结构
status = inw(ioaddr + 0);

if (status & RX_INTR) {
/* Got a packet(s). */
net_rx(dev);
}
#if TX_RING
if (status & TX_INTR) { 发送完成
/* Transmit complete. */
net_tx(dev);
np->stats.tx_packets++;
netif_wake_queue(dev);
}
#endif
if (status & COUNTERS_INTR) {
/* Increment the appropriate 'localstats' field. */
np->stats.tx_window_errors++;
}
}
static void
net_rx(struct net_device *dev)
{
struct net_local *lp = (struct net_local *)dev->priv;
int ioaddr = dev->base_addr;
int boguscount = 10;

do {
int status = inw(ioaddr); 取帧状态码
int pkt_len = inw(ioaddr); 取帧长

if (pkt_len == 0)/* Read all the frames? */
break;/* Done for now */

if (status & 0x40) {/* There was an error. */
lp->stats.rx_errors++; 接收出错
if (status & 0x20) lp->stats.rx_frame_errors++;
if (status & 0x10) lp->stats.rx_over_errors++;
if (status & 0x08) lp->stats.rx_crc_errors++;
if (status & 0x04) lp->stats.rx_fifo_errors++;
} else {
/* Malloc up new buffer. */
struct sk_buff *skb;

lp->stats.rx_bytes+=pkt_len; 总接收字节数

skb = dev_alloc_skb(pkt_len); 分配数据长度为pkt_len的缓冲区
if (skb == NULL) {
printk(KERN_NOTICE "%s: Memory squeeze, dropping packet./n",
dev->name);
lp->stats.rx_dropped++;
break;
}
skb->dev = dev;

/* 'skb->data' points to the start of sk_buff data area. */
memcpy(skb_put(skb,pkt_len), (void*)dev->rmem_start,
pkt_len); 将以太帧拷贝到缓冲区
/* or */
insw(ioaddr, skb->data, (pkt_len + 1) >> 1);

netif_rx(skb);
lp->stats.rx_packets++;
}
} while (--boguscount);

return;
}
#if TX_RING
void net_tx(struct net_device *dev)
{
struct net_local *np = (struct net_local *)dev->priv;
int entry;

/* This protects us from concurrent execution of
* our dev->hard_start_xmit function above.
*/
spin_lock(&np->lock);

entry = np->tx_old;
while (tx_entry_is_sent(np, entry)) { 释放发送环
struct sk_buff *skb = np->skbs[entry];

np->stats.tx_bytes += skb->len;
dev_kfree_skb_irq (skb);

entry = next_tx_entry(np, entry);
}
np->tx_old = entry;

/* If we had stopped the queue due to a "tx full"
* condition, and space has now been made available,
* wake up the queue.
*/
if (netif_queue_stopped(dev) && ! tx_full(dev))
netif_wake_queue(dev);

spin_unlock(&np->lock);
}
#endif

; net/core/dev.c

struct softnet_data
{
intthrottle; 扼流标志
intcng_level; 拥塞水平
intavg_blog; 平均接收队列长度
struct sk_buff_headinput_pkt_queue; 接收包队列
struct net_device*output_queue; 有包要发送设备的队列
struct sk_buff*completion_queue; 已发送完成包的回收队列
} __attribute__((__aligned__(SMP_CACHE_BYTES)));

int netdev_max_backlog = 300;
int no_cong_thresh = 10;
int no_cong = 20;
int lo_cong = 100;
int mod_cong = 290;

int netif_rx(struct sk_buff *skb)
{
int this_cpu = smp_processor_id();
struct softnet_data *queue;
unsigned long flags;

if (skb->stamp.tv_sec == 0)
get_fast_time(&skb->stamp); 设置包的精确接收时戳

/* The code is rearranged so that the path is the most
short when CPU is congested, but is still operating.
*/
queue = &softnet_data[this_cpu]; 取所在CPU的包队列

local_irq_save(flags);

netdev_rx_stat[this_cpu].total++;
if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
if (queue->input_pkt_queue.qlen) {
if (queue->throttle)
goto drop;

enqueue:
dev_hold(skb->dev);
__skb_queue_tail(&queue->input_pkt_queue,skb); 添加到包接收队列
__cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ); 激发接收软中断
local_irq_restore(flags);
#ifndef OFFLINE_SAMPLE
get_sample_stats(this_cpu); 拥塞水平取样
#endif
return softnet_data[this_cpu].cng_level;
}
; 当队列为空时, 扼流才取消
if (queue->throttle) {
queue->throttle = 0;
#ifdef CONFIG_NET_HW_FLOWCONTROL
if (atomic_dec_and_test(&netdev_dropping))
netdev_wakeup();
#endif
}
goto enqueue;
}

if (queue->throttle == 0) {
queue->throttle = 1;
netdev_rx_stat[this_cpu].throttled++; 扼流次数
#ifdef CONFIG_NET_HW_FLOWCONTROL
atomic_inc(&netdev_dropping);
#endif
}

drop:
netdev_rx_stat[this_cpu].dropped++; 所丢弃包的数量
local_irq_restore(flags);

kfree_skb(skb);
return NET_RX_DROP;
}
static void get_sample_stats(int cpu)
{
#ifdef RAND_LIE
unsigned long rd;
int rq;
#endif
int blog = softnet_data[cpu].input_pkt_queue.qlen;
int avg_blog = softnet_data[cpu].avg_blog;

avg_blog = (avg_blog >> 1)+ (blog >> 1); 迭代

if (avg_blog > mod_cong) {
/* Above moderate congestion levels. */
softnet_data[cpu].cng_level = NET_RX_CN_HIGH; "风暴"来了(4)
#ifdef RAND_LIE
rd = net_random();
rq = rd % netdev_max_backlog;
if (rq < avg_blog) /* unlucky bastard */
softnet_data[cpu].cng_level = NET_RX_DROP; 有包丢失(1)
#endif
} else if (avg_blog > lo_cong) {
softnet_data[cpu].cng_level = NET_RX_CN_MOD; "风暴"正在形成(3)
#ifdef RAND_LIE
rd = net_random();
rq = rd % netdev_max_backlog;
if (rq < avg_blog) /* unlucky bastard */
softnet_data[cpu].cng_level = NET_RX_CN_HIGH;
#endif
} else if (avg_blog > no_cong)
softnet_data[cpu].cng_level = NET_RX_CN_LOW; "风暴"平息(2)
else /* no congestion */
softnet_data[cpu].cng_level = NET_RX_SUCCESS; 一帆风顺(0)

softnet_data[cpu].avg_blog = avg_blog;
}
static void net_tx_action(struct softirq_action *h) 发送软中断
{
int cpu = smp_processor_id();

if (softnet_data[cpu].completion_queue) { 如果有包队列需要释放
struct sk_buff *clist;

local_irq_disable();
clist = softnet_data[cpu].completion_queue;
softnet_data[cpu].completion_queue = NULL;
local_irq_enable();

while (clist != NULL) { 释放已发送完成的包
struct sk_buff *skb = clist;
clist = clist->next;

BUG_TRAP(atomic_read(&skb->users) == 0);
__kfree_skb(skb);
}
}

if (softnet_data[cpu].output_queue) { 如果有设备队列等待发送
struct net_device *head;

local_irq_disable();
head = softnet_data[cpu].output_queue;
softnet_data[cpu].output_queue = NULL;
local_irq_enable();

while (head != NULL) {
struct net_device *dev = head;
head = head->next_sched;

smp_mb__before_clear_bit();
clear_bit(__LINK_STATE_SCHED, &dev->state);

if (spin_trylock(&dev->queue_lock)) {
qdisc_run(dev); 执行设备的发送规程
spin_unlock(&dev->queue_lock);
} else {
netif_schedule(dev); 重新将dev放入输出设备调度队列
}
}
}
}

 

当网卡接收到一个数据报之后,产生一个中断通知内核,然后内核会调用相关的中断处理函数.一般,中断处理程序做如下工作:
1,把数据报拷贝到一个sk_buff中.
2,初始化sk_buff中的一些成员变量,为以后传输到上层用,特别是skb->protocol,标示了上层的具体协议,后面会调用相应的接收函数.
3,更新网卡的状态.
4,调用netif_rx将数据报送往上层(采用NAPI时,此处将调用netif_rx_schdule).

看netif_rx源代码之前首先要了解一下softnet_data结构.此结构是基于cpu的而不是device,即每个cpu对应一个softnet_data.
struct softnet_data
{            
        /*throttle用于拥塞控制,当拥塞时被设置,此后来的数据包都被丢弃*/
        int throttle;
        /*netif_rx返回的拥塞级别*/
        int cng_level;
        int avg_blog;
        /*input_pkt_queue是skb的队列,接收到的skb全都进入到此队列等待后续处理*/
        struct sk_buff_head input_pkt_queue;
        /*poll_list是一个双向链表,链表的成员是有接收数据等待处理的device*/
        struct list_head poll_list;
        /*net_device链表,成员为有数据报要发送的device*/
        struct net_device *output_queue;
        /*完成发送的数据包等待释放的队列*/
        struct sk_buff *completion_queue;
        /*注意,backlog_dev不是一个指针,而是一个net_device实体,代表了调用net_rx_action时的device*/
        struct net_device backlog_dev;
};

ok,了解了softnet_data结构体后接下来看netif_rx的源代码.

/*
*主要工作:
*1,初始化sk_buff的一些域值,比如数据报接收的时间截.
*2,把接收到的数据报入input_pkt_queue接收队列,并且通知内核然后触发相应的软中断,即NET_RX_SOFTIRQ.
* 2.1:当队列为空时,调用netif_rx_schedule,触发软中断.
* 2.2:当队列非空时,直接将数据报入队列,因为此时已经调用了软中断,所以无需再调用.
*3,更新相关状态信息.
*执行完后,流程来到net_rx_action
*/
int netif_rx(struct sk_buff *skb)
{
    int this_cpu = smp_processor_id();
    struct softnet_data *queue;
    unsigned long flags;
    //如果接收到的数据包时间截未设置,设置时间截
    if (skb->stamp.tv_sec == 0)
        do_gettimeofday(&skb->stamp);
    queue = &softnet_data[this_cpu];
    local_irq_save(flags); //disable irqs on local cpu
    netdev_rx_stat[this_cpu].total++;
    if (queue->input_pkt_queue.qlen = netdev_max_backlog) {
        if (queue->input_pkt_queue.qlen) {
            if (queue->throttle)
                goto drop;
enqueue:
            dev_hold(skb->dev); //即automic_inc(&(dev)->refcnt)累加设备引入计数器
            __skb_queue_tail(&queue->input_pkt_queue,skb); //把skb入input_pkt_queue队列,此处只是指针的指向,而不是数据报的拷贝,目的是节省时间.
            local_irq_restore(flags); //eable irqs on local cpu
#ifndef OFFLINE_SAMPLE
            get_sample_stats(this_cpu);
#endif
            return queue->cng_level;//返回拥塞等级
        }
     //驱动程序不断的调用netif_rx,将数据包入队操作,当qlen==0时,执行下面代码
     //如果设置了拥塞位,将其设为0
        if (queue->throttle) {
            queue->throttle = 0;
#ifdef CONFIG_NET_HW_FLOWCONTROL
            if (atomic_dec_and_test(&netdev_dropping))
                netdev_wakeup();
#endif
        }
        /*
         netif_rx_schedule主要完成两件事
         1):将接收skb的device加入"处理数据包的设备"的链表当中
         2):触发软中断函数,进行数据包接收处理,接收软中断的处理函数为net_rx_action
        */
        netif_rx_schedule(&queue->backlog_dev);//只有当input_pkt_queue为空时才调用,非空时只是将数据报入队列,因为如果队列非空,则已经调用了NET_RX_SOFTIRQ软中断,所以没必要在执行netif_rx_schedule去调用软中断了.
        goto enqueue;
    }
    /*如果队列无空闲空间,设置拥塞位*/
    if (queue->throttle == 0) {
        queue->throttle = 1;
        netdev_rx_stat[this_cpu].throttled++;
#ifdef CONFIG_NET_HW_FLOWCONTROL
        atomic_inc(&netdev_dropping);
#endif
    }
drop:
    netdev_rx_stat[this_cpu].dropped++;
    local_irq_restore(flags);
    kfree_skb(skb);
    return NET_RX_DROP;
}

看完netif_rx后有一个问题,当队列为空的时候会调用netif_rx_schedule,此函数将会把接收skb的device连接到poll_list链表,但如果队列非空时,为什么直接把数据放到input_pkt_queue里了?
想了想,应该是因为这样:因为采用NAPI技术的网卡接收到数据报后不会调用netif_rx,而直接调用netif_rx_schedule,所以调用netif_rx的都是非NAPI的网卡,那么默认的包处理函数都是process_backlog,也就是说,所有数据报的处理函数是一样的,所以当队列非空时,直接将接收到的skb放入接收队列即可.
以上纯粹是个人的理解,不知道对不对,如果不对,有知道的朋友希望告诉下,谢谢.

netif_rx返回后,由netif_rx_schedule调用软中断处理函数,所以控制权转交到net_rx_action.

/*
*接收软中断NET_RX_SOFTIRQ的处理函数
*当调用poll_list中的device时,如果网卡采用的是NAPI技术,则调用网卡自定义的poll函数,如果是非NAPI(目前大多数网卡都是采用此技术),则调用默认的process_backlog函数.
*由于此笔记研究的是非NAPI,所以控制权转交到process_backlog函数.
*/
static void net_rx_action(struct softirq_action *h)
{
    int this_cpu = smp_processor_id();
    struct softnet_data *queue = &softnet_data[this_cpu]; //获得与cpu相关的softnet_data,因为每个cpu只有一个softnet_data
    unsigned long start_time = jiffies;
    int budget = netdev_max_backlog; //默认值为300,系统每次从队列中最多取出300个skb处理
    br_read_lock(BR_NETPROTO_LOCK);
    local_irq_disable();
    while (!list_empty(&queue->poll_list)) {
        struct net_device *dev;
        //当处理时间持续超过一个时钟滴答时,会再出发一个中断NET_RX_SOFTIRQ
        if (budget = 0 || jiffies - start_time > 1)
            goto softnet_break;
        local_irq_enable();
        //取得poll_list链表中的设备
        dev = list_entry(queue->poll_list.next, struct net_device, poll_list);
        //调用设备的poll函数,处理接收数据包,采用轮寻技术的网卡,将调用它真实的poll函数
        //而对于采用传统中断处理的设备,它们调用的都将是backlog_dev的process_backlog函数
        //如果一次poll未处理完全部数据报,则将device移至链表尾部,等待下一次调用.
        if (dev->quota = 0 || dev->poll(dev, &budget)) {
            //由于要对softnet_data进行操作,则必须禁止中断.
            local_irq_disable();
            //把device从表头移除,移到链表尾
            list_del(&dev->poll_list);
            list_add_tail(&dev->poll_list, &queue->poll_list);
            if (dev->quota  0)
                dev->quota += dev->weight;
            else
                dev->quota = dev->weight;
        } else {
            dev_put(dev); //当device中的数据报被处理完毕(网卡采用NAPI技术时),递减device的引用计数 dcreases the reference count .
            local_irq_disable();
        }
    }
    local_irq_enable();
    br_read_unlock(BR_NETPROTO_LOCK);
    return;
softnet_break:
    netdev_rx_stat[this_cpu].time_squeeze++;
    //如果此时又有中断发生,出发NET_RX_SOFTIRQ中断,再此调用net_rx_action函数.
    __cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ);
    local_irq_enable();
    br_read_unlock(BR_NETPROTO_LOCK);
}

软中断处理函数的主体既是调用数据报的处理函数,网卡采用NAPI技术的情况下,则调用device本身定义的poll函数,如果是非NAPI,则调用的是默认的process_backlog函数,既然本笔记研究的是非NAPI的情况,那么下面来研究下process_backlog函数.

/*
*把数据报从input_pkt_queue中去出来后,交由netif_receive_skb处理,netif_receive_skb为真正的包处理函数.
*注意,无论是基于NAPI还是非NAPI,最后的包处理函数都是netif_receive_skb.
*/
static int process_backlog(struct net_device *blog_dev, int *budget)
{
    int work = 0;
   
    //quota为一次处理数据包的数量,blog_dev->quota的值由netif_rx_schedule初始化为全局变量weight_p的值,默认值为64
    int quota = min(blog_dev->quota, *budget);
    int this_cpu = smp_processor_id();
    struct softnet_data *queue = &softnet_data[this_cpu];
    unsigned long start_time = jiffies;
  //循环取出skb,交由netif_receive_skb处理,直至队列为空
    for (;;) {
        struct sk_buff *skb;
        struct net_device *dev;
        local_irq_disable();
        skb = __skb_dequeue(&queue->input_pkt_queue);
        if (skb == NULL)
            goto job_done;
        local_irq_enable();
        dev = skb->dev;
        //注意此处,取出数据报后,直接交由netif_receive_skb处理.
        netif_receive_skb(skb);
        dev_put(dev); //dev引用计数-1
        work++;
        if (work >= quota || jiffies - start_time > 1)
        break;
#ifdef CONFIG_NET_HW_FLOWCONTROL
        if (queue->throttle && queue->input_pkt_queue.qlen  no_cong_thresh ) {
            if (atomic_dec_and_test(&netdev_dropping)) {
                queue->throttle = 0;
                netdev_wakeup();
                break;
            }
        }
#endif
    }
    //更新quota
    blog_dev->quota -= work;
    *budget -= work;
    return -1;
job_done:
    blog_dev->quota -= work;
    *budget -= work;
    list_del(&blog_dev->poll_list);
    clear_bit(__LINK_STATE_RX_SCHED, &blog_dev->state);
    if (queue->throttle) {
        queue->throttle = 0;
#ifdef CONFIG_NET_HW_FLOWCONTROL
        if (atomic_dec_and_test(&netdev_dropping))
            netdev_wakeup();
#endif
    }
    local_irq_enable();
    return 0;
}
注意,就像注释中所说,无论NAPI还是非NAPI,最后的包处理函数都是netif_receive_skb.所以此函数比较重要.下面来分析下此函数.
/*
  netif_receive_skb作用:
    对每一个接收到的skb,到已注册的协议类型中去匹配,先是匹配ptype_all链表,ptype_all中注册的struct packet_type表示要接收处理所有协议的数据,对于
    匹配到的struct packet_tpye结构(dev为NULL或者dev等于skb的dev),调用其func成员,把skb传递给它处理.
    匹配完ptype_all后,再匹配ptype_base数组中注册的协议类型,skb有一个成员protocol,其值即为以太网首部中的帧类型,在ptype_base中匹配到协议相同,并且
    dev符合要求的,调用其func成员即可.
    此函数中数据报的流向:sniffer(如果有)->Diverter(分流器)->bridge(如果有)->l3协议的处理函数(比如ip协议的ip_rcv)
*/
int netif_receive_skb(struct sk_buff *skb)
{
    //packet_type结构体见下面
    struct packet_type *ptype, *pt_prev;
    int ret = NET_RX_DROP;
    unsigned short type = skb->protocol;
   
    //如果数据包没设置时间截,设置之
    if (skb->stamp.tv_sec == 0)
        do_gettimeofday(&skb->stamp);
    skb_bond(skb); //使skb->dev指向主设备,多个interfaces可以在一起集中管理,这时候要有个头头管理这些接口,如果skb对应的device来自这样一个group,
     //则传递到L3层之前应使skb->dev指向master
    netdev_rx_stat[smp_processor_id()].total++;
#ifdef CONFIG_NET_FASTROUTE
    if (skb->pkt_type == PACKET_FASTROUTE) {
        netdev_rx_stat[smp_processor_id()].fastroute_deferred_out++;
        return dev_queue_xmit(skb);
    }
#endif
    skb->h.raw = skb->nh.raw = skb->data;
    pt_prev = NULL;
   
    //ptype_all是双向链表,ptpye_base是一个哈希表
    //初始化时协议类型为ETH_P_ALL时,将packet_type结构加入到ptype_all列表中   
    //如果不是,模15后加到ptype_base数组中,此数组相当于hash链表
   
    //这里针对协议类型为ETH_P_ALL的情况进行处理,对于ip协议来说
    //类型定义为ETH_P_IP,因此不在这里处理
   
    for (ptype = ptype_all; ptype; ptype = ptype->next) {
  //处理器处理所有的网络设备接收的包或找到设备匹配的包处理器
      if (!ptype->dev || ptype->dev == skb->dev) {
            if (pt_prev) {
                //老版本内核时
                if (!pt_prev->data) {
                 /* Deliver skb to an old protocol, which is not threaded well
                       or which do not understand shared skbs.
                */
                    ret = deliver_to_old_ones(pt_prev, skb, 0);
                } else {
                    //发给相应的处理函数,下面两条相当于deliver_skb
                    //有协议对skb处理,所以use加1
                    atomic_inc(&skb->users);
                    //传递的是skb的指针,所以可以看出是原数据报本身.即如果在此修改skb,则会影响到后面的数据流向.
                    ret = pt_prev->func(skb, skb->dev, pt_prev);
                }
            }
            pt_prev = ptype;
        }
    }
#ifdef CONFIG_NET_DIVERT
  /*如果配置有DIVERT(分流器),则交由分流器处理*/
    if (skb->dev->divert && skb->dev->divert->divert)
        ret = handle_diverter(skb);
#endif /* CONFIG_NET_DIVERT */
    /*如果配置有BRIDGE或者有BRIDGE模块,则交由桥处理*/        
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
    if (skb->dev->br_port != NULL &&
     br_handle_frame_hook != NULL) {
        return handle_bridge(skb, pt_prev);
    }
#endif
  //这里针对各种协议进行处理,eg:ip包的类型为ETH_P_IP,因此在这里处理
    //&15的意思是模15
    for (ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) {
        if (ptype->type == type &&
         (!ptype->dev || ptype->dev == skb->dev)) {
            if (pt_prev) { //pt_prev指向具体的协议类型
                if (!pt_prev->data) {
                    ret = deliver_to_old_ones(pt_prev, skb, 0);
                } else {
                    atomic_inc(&skb->users);
                    ret = pt_prev->func(skb, skb->dev, pt_prev);
                }
            }
            pt_prev = ptype;
        }
    }
  //1:当上面的两个数组只有一个元素时
  //2:访问上面两个数组的最后一个ptype_type,执行下面的语句
    if (pt_prev) {
        if (!pt_prev->data) {
            ret = deliver_to_old_ones(pt_prev, skb, 1);
        } else {
            //当只有一个协议的时候,user不加1
            ret = pt_prev->func(skb, skb->dev, pt_prev);
        }
    } else { ///////表示搜索完ptype_all和ptype_base后没找到匹配的,free掉skb
        kfree_skb(skb);
        /* Jamal, now you will not able to escape explaining
         * me how you were going to use this. :-)
         */
        ret = NET_RX_DROP;
    }
  return ret;
}
其中packet_type的定义如下:
struct packet_type
    {
        unsigned short type;
        //一般的dev设置为NULL,表示将接收从任何设备接收的数据包
        struct net_device *dev;
        int (*func)(struct sk_buff*,strcut net_device*,
                                                                struct packet_type *); //接收处理函数
        void *data;//private to the packet type
        struct packet_type *next;
    };
注意,在网络初始化代码里有这么一条语句:struct packet_type * ptype_all =NULL.就是说ptype_all在初始化的时候为空,用于指向Eth_P_ALL类型的packet_type结构,注册在这里的函数,可以接收到所有的数据报.包括输出的数据包,看代码可知,由于传递的是一个指针,所以在此修改skb的一切操作将会影响后面的处理过程.
如果想要在数据包传递到网络层之前对数据报进行处理,则可以考虑的地点可以有如下几个:
sniffer,diverter,bridge. 其中sniffer本人已经亲自实现过,即在ptype_all中注册自己的函数,可以接收到所有出去或者进入的数据包.
最后,数据包会传到l3层,如果是ip协议,则相应的处理函数为ip_rcv,到此数据报从网卡到l3层的接收过程已经完毕.即总的路线是:netif_rx-->net_rx_action-->process_backlog-->netif_receive_skb-->sniffer(如果有)-->diverter(如果有)-->bridge(如果有)-->ip_rcv(或者其他的l3层协议处理函数)
ps1:弄了好几天,终于算把这个数据包的接收过程系统的过了一遍,看过源代码,感觉收获不小,但是觉得知道的还不是很透彻,其中的设计理念等以后有机会一定要好好的研究研究.由于本次涉及到的东西相对来说比较多,而且涉及到内核网络部分,本人很菜,难免有理解错误的地方,如果有错误,请看到的朋友能够及时给予指正,不胜感激.
ps2:从在网上查找资料,看相关书籍到通读这一过程的源代码,本人花费了很多时间,因此对本篇笔记很是喜爱和重视,如果有需要转载的朋友,请注明来自hmily8023.cublog.cn,谢谢.