1. 程式人生 > >網絡卡接收和傳送資料包的過程

網絡卡接收和傳送資料包的過程

描述
----
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; 傳送時間標記
 
/* I  we just used u  the very last entry i  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 層協議處理函式)

來自hmily8023.cublog.cn