您的位置:首页 > 其它

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

2009-08-12 23:15 405 查看
描述

----

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

{

int			throttle; 扼流标志

int			cng_level; 拥塞水平

int			avg_blog; 平均接收队列长度

struct sk_buff_head	input_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,谢谢.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息