網絡卡接收和傳送資料包的過程
描述
---- 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